feat: upgrade tankopediaEphemeral

This commit is contained in:
TrèsAbhi committed 2025-09-01 18:51:04 -05:00
1 parent b6c3fdc4e9
commit ac29d1ead0
28 files changed
+165 -168

No files matched your search

+49 -9
View File
@@ -2,24 +2,49 @@ import { produce } from 'immer';
import { merge } from 'lodash-es'; import { merge } from 'lodash-es';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
export class Varuna<Type> { interface ProviderProps<Arguments> {
args: Arguments;
}
export class Varuna<Type, Arguments = void> {
private listeners = new Set<(state: Type) => void>(); private listeners = new Set<(state: Type) => void>();
public initial: Type;
private initialized = false;
private _initial?: Type;
private _state?: Type;
constructor( constructor(
public state: Type, private creator: Type | ((...args: Arguments[]) => Type),
persistence?: string, private persistence?: string,
) { ) {
if (persistence) { if (typeof creator !== 'function') this.initialize(creator);
const dehydrated = localStorage.getItem(persistence); }
Provider({ args }: ProviderProps<Arguments>) {
if (typeof this.creator !== 'function') {
throw new Error('Provider must be used with a function creator');
}
this.initialize((this.creator as (...args: Arguments[]) => Type)(args));
return null;
}
private initialize(initial: Type) {
let data = initial;
if (this.persistence) {
const dehydrated = localStorage.getItem(this.persistence);
if (dehydrated) { if (dehydrated) {
const rehydrated = JSON.parse(dehydrated) as unknown; const rehydrated = JSON.parse(dehydrated) as unknown;
this.state = merge(this.state, rehydrated); data = merge(data, rehydrated);
} }
} }
this.initial = this.state; this.initialized = true;
this._initial = data;
this._state = data;
} }
private dispatch() { private dispatch() {
@@ -46,11 +71,26 @@ export class Varuna<Type> {
this.set(produce(this.state, mutator)); this.set(produce(this.state, mutator));
} }
private assertInitialized() {
if (!this.initialized) throw new Error('State used before initialization');
}
get state() {
this.assertInitialized();
return this._state as Type;
}
set(state: Type) { set(state: Type) {
this.state = state; this.assertInitialized();
this._state = state;
this.dispatch(); this.dispatch();
} }
get initial() {
this.assertInitialized();
return this._initial as Type;
}
use<Slice = Type>( use<Slice = Type>(
slicer: (state: Type) => Slice = (state) => state as unknown as Slice, slicer: (state: Type) => Slice = (state) => state as unknown as Slice,
) { ) {
@@ -21,11 +21,9 @@ export function ArmorPlateDisplay() {
const highlightArmor = TankopediaEphemeral.use( const highlightArmor = TankopediaEphemeral.use(
(state) => state.highlightArmor, (state) => state.highlightArmor,
); );
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const developerMode = App.useDeferred((state) => state.developerMode, false); const developerMode = App.useDeferred((state) => state.developerMode, false);
const duelStore = Duel.useStore(); const duelStore = Duel.useStore();
const input = useRef<HTMLInputElement>(null); const input = useRef<HTMLInputElement>(null);
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
if (highlightArmor === undefined) return null; if (highlightArmor === undefined) return null;
@@ -95,7 +93,7 @@ export function ArmorPlateDisplay() {
if (event.key === 'Enter') { if (event.key === 'Enter') {
input.current?.blur(); input.current?.blur();
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.highlightArmor = undefined; draft.highlightArmor = undefined;
const { protagonist } = duelStore.getState(); const { protagonist } = duelStore.getState();
const tank = draft.model; const tank = draft.model;
@@ -119,7 +117,7 @@ export function ArmorPlateDisplay() {
}); });
} else if (event.key === 'Escape') { } else if (event.key === 'Escape') {
input.current?.blur(); input.current?.blur();
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.highlightArmor = undefined; draft.highlightArmor = undefined;
}); });
} }
@@ -137,7 +135,7 @@ export function ArmorPlateDisplay() {
mt="2" mt="2"
color="red" color="red"
onClick={() => { onClick={() => {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
if (!highlightArmor.editingPlate) return; if (!highlightArmor.editingPlate) return;
const index = resolveArmorIndex(highlightArmor.name); const index = resolveArmorIndex(highlightArmor.name);
@@ -145,8 +143,7 @@ export function ArmorPlateDisplay() {
if (index === undefined) return; if (index === undefined) return;
draft.highlightArmor = undefined; draft.highlightArmor = undefined;
const initialState = const initialState = TankopediaEphemeral.initial;
tankopediaEphemeralStore.getInitialState();
const { protagonist } = duelStore.getState(); const { protagonist } = duelStore.getState();
const tank = draft.model; const tank = draft.model;
const initialTank = initialState.model; const initialTank = initialState.model;
@@ -43,7 +43,6 @@ export function PrimaryArmorSceneComponent({
}: PrimaryArmorSceneComponentProps) { }: PrimaryArmorSceneComponentProps) {
const tankopediaPersistentStore = TankopediaPersistent.useStore(); const tankopediaPersistentStore = TankopediaPersistent.useStore();
const duelStore = Duel.useStore(); const duelStore = Duel.useStore();
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
const material = new ShaderMaterial({ const material = new ShaderMaterial({
fragmentShader, fragmentShader,
vertexShader, vertexShader,
@@ -82,7 +81,7 @@ export function PrimaryArmorSceneComponent({
useEffect(() => { useEffect(() => {
function handleShellChange() { function handleShellChange() {
const duel = duelStore.getState(); const duel = duelStore.getState();
const tankopediaEphemeral = tankopediaEphemeralStore.getState(); const tankopediaEphemeral = TankopediaEphemeral.state;
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell; const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
material.uniforms.caliber.value = shell.caliber; material.uniforms.caliber.value = shell.caliber;
@@ -136,7 +135,7 @@ export function PrimaryArmorSceneComponent({
noInvalidate = false, noInvalidate = false,
) { ) {
const duel = duelStore.getState(); const duel = duelStore.getState();
const tankopediaEphemeral = tankopediaEphemeralStore.getState(); const tankopediaEphemeral = TankopediaEphemeral.state;
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell; const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
const penetration = shell.penetration.near; const penetration = shell.penetration.near;
const hasCalibratedShells = hasEquipment( const hasCalibratedShells = hasEquipment(
@@ -168,10 +167,7 @@ export function PrimaryArmorSceneComponent({
const unsubscribes = [ const unsubscribes = [
duelStore.subscribe((state) => state.antagonist.shell, handleShellChange), duelStore.subscribe((state) => state.antagonist.shell, handleShellChange),
tankopediaEphemeralStore.subscribe( TankopediaEphemeral.on((state) => state.customShell, handleShellChange),
(state) => state.customShell,
handleShellChange,
),
tankopediaPersistentStore.subscribe( tankopediaPersistentStore.subscribe(
(state) => state.greenPenetration, (state) => state.greenPenetration,
handleGreenPenetrationChange, handleGreenPenetrationChange,
@@ -29,7 +29,6 @@ export function SpacedArmorSubExternal({
variant, variant,
clip, clip,
}: SpacedArmorSubExternalProps) { }: SpacedArmorSubExternalProps) {
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
const duelStore = Duel.useStore(); const duelStore = Duel.useStore();
const material = new ShaderMaterial({ const material = new ShaderMaterial({
fragmentShader, fragmentShader,
@@ -50,7 +49,7 @@ export function SpacedArmorSubExternal({
useEffect(() => { useEffect(() => {
function handleShellChange() { function handleShellChange() {
const duel = duelStore.getState(); const duel = duelStore.getState();
const tankopediaEphemeral = tankopediaEphemeralStore.getState(); const tankopediaEphemeral = TankopediaEphemeral.state;
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell; const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
material.uniforms.penetration.value = shell.penetration.near; material.uniforms.penetration.value = shell.penetration.near;
} }
@@ -67,7 +66,7 @@ export function SpacedArmorSubExternal({
} }
function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) { function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
const duel = duelStore.getState(); const duel = duelStore.getState();
const tankopediaEphemeral = tankopediaEphemeralStore.getState(); const tankopediaEphemeral = TankopediaEphemeral.state;
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell; const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
const penetration = shell.penetration.near; const penetration = shell.penetration.near;
const hasCalibratedShells = hasEquipment( const hasCalibratedShells = hasEquipment(
@@ -91,10 +90,7 @@ export function SpacedArmorSubExternal({
const unsubscribes = [ const unsubscribes = [
duelStore.subscribe((state) => state.antagonist.shell, handleShellChange), duelStore.subscribe((state) => state.antagonist.shell, handleShellChange),
tankopediaEphemeralStore.subscribe( TankopediaEphemeral.on((state) => state.customShell, handleShellChange),
(state) => state.customShell,
handleShellChange,
),
duelStore.subscribe( duelStore.subscribe(
(state) => state.protagonist.equipmentMatrix, (state) => state.protagonist.equipmentMatrix,
handleProtagonistEquipmentChange, handleProtagonistEquipmentChange,
@@ -30,7 +30,6 @@ export function SpacedArmorSubSpaced({
node, node,
thickness, thickness,
}: SpacedArmorSubSpacedProps) { }: SpacedArmorSubSpacedProps) {
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
const duelStore = Duel.useStore(); const duelStore = Duel.useStore();
const material = new ShaderMaterial({ const material = new ShaderMaterial({
fragmentShader, fragmentShader,
@@ -52,7 +51,7 @@ export function SpacedArmorSubSpaced({
useEffect(() => { useEffect(() => {
function handleShellChange() { function handleShellChange() {
const duel = duelStore.getState(); const duel = duelStore.getState();
const tankopediaEphemeral = tankopediaEphemeralStore.getState(); const tankopediaEphemeral = TankopediaEphemeral.state;
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell; const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
material.uniforms.penetration.value = shell.penetration.near; material.uniforms.penetration.value = shell.penetration.near;
@@ -77,7 +76,7 @@ export function SpacedArmorSubSpaced({
} }
function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) { function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
const duel = duelStore.getState(); const duel = duelStore.getState();
const tankopediaEphemeral = tankopediaEphemeralStore.getState(); const tankopediaEphemeral = TankopediaEphemeral.state;
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell; const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
const penetration = shell.penetration.near; const penetration = shell.penetration.near;
const hasCalibratedShells = hasEquipment( const hasCalibratedShells = hasEquipment(
@@ -101,10 +100,7 @@ export function SpacedArmorSubSpaced({
const unsubscribes = [ const unsubscribes = [
duelStore.subscribe((state) => state.antagonist.shell, handleShellChange), duelStore.subscribe((state) => state.antagonist.shell, handleShellChange),
tankopediaEphemeralStore.subscribe( TankopediaEphemeral.on((state) => state.customShell, handleShellChange),
(state) => state.customShell,
handleShellChange,
),
duelStore.subscribe( duelStore.subscribe(
(state) => state.protagonist.equipmentMatrix, (state) => state.protagonist.equipmentMatrix,
handleProtagonistEquipmentChange, handleProtagonistEquipmentChange,
@@ -76,7 +76,6 @@ export function SpacedArmorSceneComponent({
clip, clip,
...props ...props
}: SpacedArmorSceneComponentProps) { }: SpacedArmorSceneComponentProps) {
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
const duelStore = Duel.useStore(); const duelStore = Duel.useStore();
const camera = useThree((state) => state.camera); const camera = useThree((state) => state.camera);
@@ -88,7 +87,7 @@ export function SpacedArmorSceneComponent({
remainingPenetrationInput?: number, remainingPenetrationInput?: number,
) => { ) => {
const { antagonist, protagonist } = duelStore.getState(); const { antagonist, protagonist } = duelStore.getState();
const { customShell } = tankopediaEphemeralStore.getState(); const { customShell } = TankopediaEphemeral.state;
const shell = customShell ?? antagonist.shell; const shell = customShell ?? antagonist.shell;
const cameraNormal = camera.position.clone().sub(point).normalize(); const cameraNormal = camera.position.clone().sub(point).normalize();
const shot: Shot = { const shot: Shot = {
@@ -391,7 +390,9 @@ export function SpacedArmorSceneComponent({
const shot = shoot(event.point, event.intersections, true)!; const shot = shoot(event.point, event.intersections, true)!;
tankopediaEphemeralStore.setState({ shot }); TankopediaEphemeral.mutate((draft) => {
draft.shot = shot;
});
}} }}
/> />
); );
@@ -46,7 +46,6 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin); const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin); const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
const canvas = useThree((state) => state.gl.domElement); const canvas = useThree((state) => state.gl.domElement);
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const duelStore = Duel.useStore(); const duelStore = Duel.useStore();
const maskOrigin = const maskOrigin =
gunModelDefinition.mask === undefined gunModelDefinition.mask === undefined
@@ -155,10 +154,10 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
position.set(event.clientX, event.clientY); position.set(event.clientX, event.clientY);
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.controlsEnabled = false; draft.controlsEnabled = false;
}); });
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.shot = undefined; draft.shot = undefined;
draft.highlightArmor = undefined; draft.highlightArmor = undefined;
}); });
@@ -194,7 +193,7 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
modelTransformEvent.dispatch({ pitch, yaw }); modelTransformEvent.dispatch({ pitch, yaw });
} }
function handlePointerUp() { function handlePointerUp() {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.controlsEnabled = true; draft.controlsEnabled = true;
}); });
window.removeEventListener('pointermove', handlePointerMove); window.removeEventListener('pointermove', handlePointerMove);
@@ -245,10 +244,10 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
function onPointerDown(event: ThreeEvent<PointerEvent>) { function onPointerDown(event: ThreeEvent<PointerEvent>) {
event.stopPropagation(); event.stopPropagation();
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.controlsEnabled = false; draft.controlsEnabled = false;
}); });
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.shot = undefined; draft.shot = undefined;
draft.highlightArmor = undefined; draft.highlightArmor = undefined;
}); });
@@ -287,7 +286,7 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
modelTransformEvent.dispatch({ pitch, yaw }); modelTransformEvent.dispatch({ pitch, yaw });
} }
function handlePointerUp() { function handlePointerUp() {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.controlsEnabled = true; draft.controlsEnabled = true;
}); });
window.removeEventListener('pointermove', handlePointerMove); window.removeEventListener('pointermove', handlePointerMove);
@@ -327,10 +326,10 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
function onPointerDown(event: ThreeEvent<PointerEvent>) { function onPointerDown(event: ThreeEvent<PointerEvent>) {
event.stopPropagation(); event.stopPropagation();
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.controlsEnabled = false; draft.controlsEnabled = false;
}); });
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.shot = undefined; draft.shot = undefined;
draft.highlightArmor = undefined; draft.highlightArmor = undefined;
}); });
@@ -368,7 +367,7 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
modelTransformEvent.dispatch({ pitch, yaw }); modelTransformEvent.dispatch({ pitch, yaw });
} }
function handlePointerUp() { function handlePointerUp() {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.controlsEnabled = true; draft.controlsEnabled = true;
}); });
@@ -69,8 +69,6 @@ export function StaticArmorSceneComponent({
onPointerDown, onPointerDown,
...props ...props
}: StaticArmorSceneComponentProps) { }: StaticArmorSceneComponentProps) {
const mutateTankopediaEphemeralStore = TankopediaEphemeral.useMutation();
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
const camera = useThree((state) => state.camera); const camera = useThree((state) => state.camera);
const x = thickness / thicknessRange.value; const x = thickness / thicknessRange.value;
const xClamped = clamp(x, 0, 1); const xClamped = clamp(x, 0, 1);
@@ -166,7 +164,7 @@ export function StaticArmorSceneComponent({
} }
const unsubscribes = [ const unsubscribes = [
tankopediaEphemeralStore.subscribe( TankopediaEphemeral.on(
(state) => state.highlightArmor?.name, (state) => state.highlightArmor?.name,
handleHighlightArmor, handleHighlightArmor,
), ),
@@ -238,7 +236,7 @@ export function StaticArmorSceneComponent({
event.stopPropagation(); event.stopPropagation();
const { editStatic } = tankopediaEphemeralStore.getState(); const { editStatic } = TankopediaEphemeral.state;
const bounds = new Box3().setFromObject(event.object); const bounds = new Box3().setFromObject(event.object);
const point = bounds.min const point = bounds.min
@@ -259,7 +257,7 @@ export function StaticArmorSceneComponent({
const thicknessAngled = const thicknessAngled =
thickness / Math.sin(Math.PI / 2 - angle); thickness / Math.sin(Math.PI / 2 - angle);
mutateTankopediaEphemeralStore((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.highlightArmor = { draft.highlightArmor = {
type: props.type, type: props.type,
name, name,
@@ -18,13 +18,12 @@ export function GuessBackground() {
const guessState = GuessEphemeral.use((state) => state.guessState); const guessState = GuessEphemeral.use((state) => state.guessState);
const tank = GuessEphemeral.use((state) => state.tank); const tank = GuessEphemeral.use((state) => state.tank);
const mutateDuel = Duel.useMutation(); const mutateDuel = Duel.useMutation();
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
useEffect(() => { useEffect(() => {
mutateDuel((state) => { mutateDuel((state) => {
state.protagonist = tankToDuelMember(tank, provisionDefinitions); state.protagonist = tankToDuelMember(tank, provisionDefinitions);
}); });
mutateTankopediaEphemeral((state) => { TankopediaEphemeral.mutate((state) => {
state.model = modelDefinitions.models[tank.id]; state.model = modelDefinitions.models[tank.id];
}); });
}, [tank]); }, [tank]);
@@ -13,7 +13,6 @@ export function CompareOptions(props: FlexProps) {
(state) => state.relativeAgainst, (state) => state.relativeAgainst,
); );
const tank = Duel.use((state) => state.protagonist.tank); const tank = Duel.use((state) => state.protagonist.tank);
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const { strings } = useLocale(); const { strings } = useLocale();
return ( return (
@@ -33,7 +32,7 @@ export function CompareOptions(props: FlexProps) {
size="1" size="1"
value={`${relativeAgainst}`} value={`${relativeAgainst}`}
onValueChange={(value) => { onValueChange={(value) => {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.relativeAgainst = Number(value) as TankopediaRelativeAgainst; draft.relativeAgainst = Number(value) as TankopediaRelativeAgainst;
}); });
}} }}
@@ -10,7 +10,6 @@ export function StatSearch(props: FlexProps) {
const { strings } = useLocale(); const { strings } = useLocale();
const [searching, setSearching] = useState(false); const [searching, setSearching] = useState(false);
const input = useRef<HTMLInputElement>(null); const input = useRef<HTMLInputElement>(null);
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const count = useRef<HTMLDivElement>(null); const count = useRef<HTMLDivElement>(null);
const [index, setIndex] = useState(0); const [index, setIndex] = useState(0);
const search = useCallback(() => { const search = useCallback(() => {
@@ -34,14 +33,14 @@ export function StatSearch(props: FlexProps) {
count.current.textContent = ''; count.current.textContent = '';
setSearching(false); setSearching(false);
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.statSearch = undefined; draft.statSearch = undefined;
}); });
return; return;
} }
setSearching(false); setSearching(false);
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.statSearch = input.current!.value; draft.statSearch = input.current!.value;
}); });
}, 500), }, 500),
@@ -14,7 +14,6 @@ import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
export function Skills() { export function Skills() {
const skillLevels = TankopediaEphemeral.use((state) => state.skills); const skillLevels = TankopediaEphemeral.use((state) => state.skills);
const mutateTankopediaTemporary = TankopediaEphemeral.useMutation();
const { strings } = useLocale(); const { strings } = useLocale();
return ( return (
@@ -46,7 +45,7 @@ export function Skills() {
variant="ghost" variant="ghost"
color="red" color="red"
onClick={() => { onClick={() => {
mutateTankopediaTemporary((draft) => { TankopediaEphemeral.mutate((draft) => {
Object.keys(draft.skills).forEach((skill) => { Object.keys(draft.skills).forEach((skill) => {
draft.skills[skill] = 0; draft.skills[skill] = 0;
}); });
@@ -58,7 +57,7 @@ export function Skills() {
<Button <Button
variant="ghost" variant="ghost"
onClick={() => { onClick={() => {
mutateTankopediaTemporary((draft) => { TankopediaEphemeral.mutate((draft) => {
Object.keys(draft.skills).forEach((skill) => { Object.keys(draft.skills).forEach((skill) => {
draft.skills[skill] = 7; draft.skills[skill] = 7;
}); });
@@ -72,7 +71,7 @@ export function Skills() {
<CrewSkillManager <CrewSkillManager
skillLevels={skillLevels} skillLevels={skillLevels}
onChange={(skills) => { onChange={(skills) => {
mutateTankopediaTemporary((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.skills = skills; draft.skills = skills;
}); });
}} }}
@@ -20,7 +20,6 @@ export function CustomShellButton() {
const customShell = TankopediaEphemeral.use((state) => state.customShell); const customShell = TankopediaEphemeral.use((state) => state.customShell);
const gun = Duel.use((state) => state.protagonist.gun); const gun = Duel.use((state) => state.protagonist.gun);
const antagonistShell = Duel.use((state) => state.antagonist.shell); const antagonistShell = Duel.use((state) => state.antagonist.shell);
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const { strings } = useLocale(); const { strings } = useLocale();
return ( return (
@@ -28,7 +27,7 @@ export function CustomShellButton() {
onOpenChange={(open) => { onOpenChange={(open) => {
if (!open || customShell) return; if (!open || customShell) return;
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.customShell = antagonistShell; draft.customShell = antagonistShell;
}); });
}} }}
@@ -97,7 +96,7 @@ export function CustomShellButton() {
} }
} }
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.customShell = modified; draft.customShell = modified;
}); });
}} }}
@@ -134,7 +133,7 @@ export function CustomShellButton() {
defaultValue={customShell.caliber} defaultValue={customShell.caliber}
type="number" type="number"
onChange={(event) => { onChange={(event) => {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.customShell!.caliber = event.target.valueAsNumber; draft.customShell!.caliber = event.target.valueAsNumber;
}); });
}} }}
@@ -170,7 +169,7 @@ export function CustomShellButton() {
defaultValue={customShell.penetration.near} defaultValue={customShell.penetration.near}
type="number" type="number"
onChange={(event: ChangeEvent<HTMLInputElement>) => { onChange={(event: ChangeEvent<HTMLInputElement>) => {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.customShell!.penetration.near = draft.customShell!.penetration.near =
event.target.valueAsNumber; event.target.valueAsNumber;
}); });
@@ -226,7 +225,7 @@ export function CustomShellButton() {
onValueChange={([value]) => { onValueChange={([value]) => {
console.log('asd'); console.log('asd');
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.customShell!.normalization = value; draft.customShell!.normalization = value;
}); });
}} }}
@@ -255,7 +254,7 @@ export function CustomShellButton() {
step={Number.EPSILON} step={Number.EPSILON}
defaultValue={[customShell.ricochet ?? 90]} defaultValue={[customShell.ricochet ?? 90]}
onValueChange={throttle(([value]) => { onValueChange={throttle(([value]) => {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.customShell!.ricochet = value; draft.customShell!.ricochet = value;
}); });
}, 1000)} }, 1000)}
@@ -274,7 +273,7 @@ export function CustomShellButton() {
defaultValue={customShell.explosion_radius ?? 0} defaultValue={customShell.explosion_radius ?? 0}
type="number" type="number"
onChange={(event: ChangeEvent<HTMLInputElement>) => { onChange={(event: ChangeEvent<HTMLInputElement>) => {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.customShell!.explosion_radius = draft.customShell!.explosion_radius =
event.target.valueAsNumber; event.target.valueAsNumber;
}); });
@@ -310,7 +309,7 @@ export function CustomShellButton() {
<Button <Button
color="red" color="red"
onClick={() => { onClick={() => {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.customShell = antagonistShell; draft.customShell = antagonistShell;
}); });
}} }}
@@ -7,7 +7,6 @@ import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemera
export function DynamicArmorSwitcher() { export function DynamicArmorSwitcher() {
const tank = Duel.use((state) => state.protagonist.tank); const tank = Duel.use((state) => state.protagonist.tank);
const mutateDuel = Duel.useMutation(); const mutateDuel = Duel.useMutation();
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const isDynamicArmorActive = Duel.use((state) => const isDynamicArmorActive = Duel.use((state) =>
state.protagonist.consumables.includes(73), state.protagonist.consumables.includes(73),
); );
@@ -33,7 +32,7 @@ export function DynamicArmorSwitcher() {
} }
} }
}); });
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.shot = undefined; draft.shot = undefined;
}); });
}} }}
@@ -26,9 +26,7 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
(state) => state.showPrimaryArmor, (state) => state.showPrimaryArmor,
); );
const mutateTankopediaPersistent = TankopediaPersistent.useMutation(); const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const editStatic = TankopediaEphemeral.use((state) => state.editStatic); const editStatic = TankopediaEphemeral.use((state) => state.editStatic);
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
const { strings } = useLocale(); const { strings } = useLocale();
return ( return (
@@ -55,7 +53,7 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
mutateTankopediaPersistent((draft) => { mutateTankopediaPersistent((draft) => {
draft.showPrimaryArmor = !draft.showPrimaryArmor; draft.showPrimaryArmor = !draft.showPrimaryArmor;
}); });
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.highlightArmor = undefined; draft.highlightArmor = undefined;
}); });
}} }}
@@ -96,7 +94,7 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
mutateTankopediaPersistent((draft) => { mutateTankopediaPersistent((draft) => {
draft.showSpacedArmor = !draft.showSpacedArmor; draft.showSpacedArmor = !draft.showSpacedArmor;
}); });
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.highlightArmor = undefined; draft.highlightArmor = undefined;
}); });
}} }}
@@ -135,7 +133,7 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
mutateTankopediaPersistent((draft) => { mutateTankopediaPersistent((draft) => {
draft.showExternalModules = !draft.showExternalModules; draft.showExternalModules = !draft.showExternalModules;
}); });
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.highlightArmor = undefined; draft.highlightArmor = undefined;
}); });
}} }}
@@ -168,7 +166,7 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
gap="2" gap="2"
style={{ cursor: 'pointer' }} style={{ cursor: 'pointer' }}
onClick={() => { onClick={() => {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.editStatic = !draft.editStatic; draft.editStatic = !draft.editStatic;
draft.highlightArmor = undefined; draft.highlightArmor = undefined;
}); });
@@ -186,9 +184,9 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
size="1" size="1"
variant="ghost" variant="ghost"
onClick={() => { onClick={() => {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.editStatic = false; draft.editStatic = false;
draft.model = tankopediaEphemeralStore.getInitialState().model; draft.model = TankopediaEphemeral.initial.model;
}); });
}} }}
> >
@@ -73,7 +73,6 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
const hasEnhancedArmor = useEquipment(110); const hasEnhancedArmor = useEquipment(110);
const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets); const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets);
const mutateTankopediaPersistent = TankopediaPersistent.useMutation(); const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const { strings, unwrap } = useLocale(); const { strings, unwrap } = useLocale();
const revealed = TankopediaEphemeral.use((state) => state.revealed); const revealed = TankopediaEphemeral.use((state) => state.revealed);
const disturbed = TankopediaEphemeral.use((state) => state.disturbed); const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
@@ -196,7 +195,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
mutateDuel((draft) => { mutateDuel((draft) => {
draft.antagonist.shell = thisShell; draft.antagonist.shell = thisShell;
}); });
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.shot = undefined; draft.shot = undefined;
draft.customShell = undefined; draft.customShell = undefined;
}); });
@@ -235,7 +234,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
? 0 ? 0
: 1; : 1;
}); });
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.shot = undefined; draft.shot = undefined;
}); });
}} }}
@@ -260,7 +259,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
? 0 ? 0
: -1; : -1;
}); });
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.shot = undefined; draft.shot = undefined;
}); });
}} }}
@@ -367,7 +366,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
variant="classic" variant="classic"
value={`${disturbed ? display : -1}`} value={`${disturbed ? display : -1}`}
onValueChange={(value) => { onValueChange={(value) => {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.disturbed = true; draft.disturbed = true;
draft.display = Number(value); draft.display = Number(value);
}); });
@@ -14,14 +14,13 @@ export function Title() {
? '2rem' ? '2rem'
: `min(16vh, ${125 / name.length}vw)` : `min(16vh, ${125 / name.length}vw)`
: `min(12vh, ${75 / name.length}vw)`; : `min(12vh, ${75 / name.length}vw)`;
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
return ( return (
<Flex <Flex
onPointerDown={(event) => { onPointerDown={(event) => {
event.preventDefault(); event.preventDefault();
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.revealed = true; draft.revealed = true;
}); });
}} }}
@@ -32,9 +32,7 @@ export function Controls({
zoomable = true, zoomable = true,
enableRotate = true, enableRotate = true,
}: ControlsProps) { }: ControlsProps) {
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const duelStore = Duel.useStore(); const duelStore = Duel.useStore();
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
const camera = useThree((state) => state.camera as PerspectiveCamera); const camera = useThree((state) => state.camera as PerspectiveCamera);
const canvas = useThree((state) => state.gl.domElement); const canvas = useThree((state) => state.gl.domElement);
const orbitControls = useRef<OrbitControlsClass>(null); const orbitControls = useRef<OrbitControlsClass>(null);
@@ -102,7 +100,7 @@ export function Controls({
}); });
useEffect(() => { useEffect(() => {
const unsubscribeTankopediaEphemeral = tankopediaEphemeralStore.subscribe( const unsubscribeTankopediaEphemeral = TankopediaEphemeral.on(
(state) => state.controlsEnabled, (state) => state.controlsEnabled,
(enabled) => { (enabled) => {
if (orbitControls.current) orbitControls.current.enabled = enabled; if (orbitControls.current) orbitControls.current.enabled = enabled;
@@ -222,7 +220,7 @@ export function Controls({
} }
function disturb() { function disturb() {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.disturbed = true; draft.disturbed = true;
}); });
} }
@@ -268,7 +266,7 @@ export function Controls({
enableZoom={zoomable} enableZoom={zoomable}
zoomSpeed={zoomable ? undefined : 0} zoomSpeed={zoomable ? undefined : 0}
ref={orbitControls} ref={orbitControls}
enabled={tankopediaEphemeralStore.getState().controlsEnabled} enabled={TankopediaEphemeral.state.controlsEnabled}
enableDamping={false} enableDamping={false}
autoRotateSpeed={1 / 4} autoRotateSpeed={1 / 4}
/> />
@@ -6,10 +6,9 @@ import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemera
export function InitialFogReveal() { export function InitialFogReveal() {
const fog = useThree((state) => state.scene.fog as Fog | null); const fog = useThree((state) => state.scene.fog as Fog | null);
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
useEffect(() => { useEffect(() => {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.revealed = true; draft.revealed = true;
}); });
@@ -16,14 +16,12 @@ import { ModelTankWrapper } from '../../../../../Armor/components/ModelTankWrapp
export function TankModel() { export function TankModel() {
const duelStore = Duel.useStore(); const duelStore = Duel.useStore();
const protagonist = Duel.use((draft) => draft.protagonist); const protagonist = Duel.use((draft) => draft.protagonist);
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
const track = Duel.use((state) => state.protagonist.track); const track = Duel.use((state) => state.protagonist.track);
const turret = Duel.use((state) => state.protagonist.turret); const turret = Duel.use((state) => state.protagonist.turret);
const canvas = useThree((state) => state.gl.domElement); const canvas = useThree((state) => state.gl.domElement);
const hullContainer = useRef<Group>(null); const hullContainer = useRef<Group>(null);
const turretContainer = useRef<Group>(null); const turretContainer = useRef<Group>(null);
const gunContainer = useRef<Group>(null); const gunContainer = useRef<Group>(null);
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const tankModelDefinition = useTankModelDefinition(); const tankModelDefinition = useTankModelDefinition();
const turretModelDefinition = const turretModelDefinition =
tankModelDefinition.turrets[protagonist.turret.id]; tankModelDefinition.turrets[protagonist.turret.id];
@@ -63,13 +61,12 @@ export function TankModel() {
function onPointerDown(event: ThreeEvent<PointerEvent>) { function onPointerDown(event: ThreeEvent<PointerEvent>) {
if ( if (
isTrack && isTrack &&
tankopediaEphemeralStore.getState().display === TankopediaEphemeral.state.display === TankopediaDisplay.Model
TankopediaDisplay.Model
) { ) {
position.set(event.clientX, event.clientY); position.set(event.clientX, event.clientY);
event.stopPropagation(); event.stopPropagation();
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.controlsEnabled = false; draft.controlsEnabled = false;
}); });
@@ -88,7 +85,7 @@ export function TankModel() {
translateTexture(deltaX + deltaY); translateTexture(deltaX + deltaY);
} }
function handlePointerUp() { function handlePointerUp() {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.controlsEnabled = true; draft.controlsEnabled = true;
}); });
@@ -130,10 +127,10 @@ export function TankModel() {
position.set(event.clientX, event.clientY); position.set(event.clientX, event.clientY);
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.controlsEnabled = false; draft.controlsEnabled = false;
}); });
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.shot = undefined; draft.shot = undefined;
draft.highlightArmor = undefined; draft.highlightArmor = undefined;
}); });
@@ -169,7 +166,7 @@ export function TankModel() {
modelTransformEvent.dispatch({ pitch, yaw }); modelTransformEvent.dispatch({ pitch, yaw });
} }
function handlePointerUp() { function handlePointerUp() {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.controlsEnabled = true; draft.controlsEnabled = true;
}); });
window.removeEventListener('pointermove', handlePointerMove); window.removeEventListener('pointermove', handlePointerMove);
@@ -213,10 +210,10 @@ export function TankModel() {
function onPointerDown(event: ThreeEvent<PointerEvent>) { function onPointerDown(event: ThreeEvent<PointerEvent>) {
event.stopPropagation(); event.stopPropagation();
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.controlsEnabled = false; draft.controlsEnabled = false;
}); });
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.shot = undefined; draft.shot = undefined;
draft.highlightArmor = undefined; draft.highlightArmor = undefined;
}); });
@@ -255,7 +252,7 @@ export function TankModel() {
modelTransformEvent.dispatch({ pitch, yaw }); modelTransformEvent.dispatch({ pitch, yaw });
} }
function handlePointerUp() { function handlePointerUp() {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.controlsEnabled = true; draft.controlsEnabled = true;
}); });
window.removeEventListener('pointermove', handlePointerMove); window.removeEventListener('pointermove', handlePointerMove);
@@ -22,7 +22,6 @@ const skeletonMaterial = new ShaderMaterial({
export function TransitionSkeleton() { export function TransitionSkeleton() {
const protagonist = Duel.use((draft) => draft.protagonist); const protagonist = Duel.use((draft) => draft.protagonist);
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
const track = Duel.use((state) => state.protagonist.track); const track = Duel.use((state) => state.protagonist.track);
const turret = Duel.use((state) => state.protagonist.turret); const turret = Duel.use((state) => state.protagonist.turret);
const hullContainer = useRef<Group>(null); const hullContainer = useRef<Group>(null);
@@ -34,7 +33,7 @@ export function TransitionSkeleton() {
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id]; const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
const { gltf } = useModel(protagonist.tank.id); const { gltf } = useModel(protagonist.tank.id);
const nodes = Object.values(gltf.nodes); const nodes = Object.values(gltf.nodes);
const revealed = tankopediaEphemeralStore((state) => state.revealed); const revealed = TankopediaEphemeral.use((state) => state.revealed);
useTankTransform(track, turret, turretContainer, gunContainer); useTankTransform(track, turret, turretContainer, gunContainer);
@@ -49,7 +49,6 @@ export const fogAnimationTime = 2.5;
export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>( export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
({ thicknessRange, naked }, ref) => { ({ thicknessRange, naked }, ref) => {
const fog = useRef(new Fog('black', forNear1, fogFar1)); const fog = useRef(new Fog('black', forNear1, fogFar1));
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const canvas = useRef<HTMLCanvasElement>(null); const canvas = useRef<HTMLCanvasElement>(null);
const hasImprovedVerticalStabilizer = useEquipment(122); const hasImprovedVerticalStabilizer = useEquipment(122);
const hasDownImprovedVerticalStabilizer = useEquipment(124); const hasDownImprovedVerticalStabilizer = useEquipment(124);
@@ -177,13 +176,13 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
useEffect(() => { useEffect(() => {
if (display !== TankopediaDisplay.DynamicArmor) { if (display !== TankopediaDisplay.DynamicArmor) {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.shot = undefined; draft.shot = undefined;
}); });
} }
if (display !== TankopediaDisplay.StaticArmor) { if (display !== TankopediaDisplay.StaticArmor) {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.highlightArmor = undefined; draft.highlightArmor = undefined;
}); });
} }
@@ -201,7 +200,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
shadows="soft" shadows="soft"
onPointerDown={handlePointerDown} onPointerDown={handlePointerDown}
onPointerMissed={() => { onPointerMissed={() => {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.shot = undefined; draft.shot = undefined;
draft.highlightArmor = undefined; draft.highlightArmor = undefined;
}); });
@@ -38,7 +38,6 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
}, [protagonist]); }, [protagonist]);
const duelStore = Duel.useStore(); const duelStore = Duel.useStore();
const mutateDuel = Duel.useMutation(); const mutateDuel = Duel.useMutation();
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
useEffect(() => { useEffect(() => {
if (disturbed) { if (disturbed) {
@@ -55,7 +54,7 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
mutateDuel((draft) => { mutateDuel((draft) => {
draft.antagonist.shell = shells[index]; draft.antagonist.shell = shells[index];
}); });
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.customShell = undefined; draft.customShell = undefined;
}); });
} }
@@ -21,7 +21,6 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
const { locale, strings } = useLocale(); const { locale, strings } = useLocale();
const master = Duel.use((state) => state.protagonist.tank); const master = Duel.use((state) => state.protagonist.tank);
const xpMultiplier = TankopediaEphemeral.use((state) => state.xpMultiplier); const xpMultiplier = TankopediaEphemeral.use((state) => state.xpMultiplier);
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const container = useRef<HTMLDivElement>(null); const container = useRef<HTMLDivElement>(null);
const lines = useMemo(() => { const lines = useMemo(() => {
function extend(line: Line): Line[] { function extend(line: Line): Line[] {
@@ -148,7 +147,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
cursor: 'pointer', cursor: 'pointer',
}} }}
onClick={() => { onClick={() => {
mutateTankopediaEphemeral((draft) => { TankopediaEphemeral.mutate((draft) => {
draft.xpMultiplier = multiplier; draft.xpMultiplier = multiplier;
}); });
}} }}
@@ -43,11 +43,12 @@ function Container() {
const model = modelDefinitions.models[tank.id]; const model = modelDefinitions.models[tank.id];
return ( return (
<TankopediaEphemeral.Provider data={model}> <>
<TankopediaEphemeral.Provider args={model} />
<Duel.Provider data={{ tank, model, provisionDefinitions }}> <Duel.Provider data={{ tank, model, provisionDefinitions }}>
<Content /> <Content />
</Duel.Provider> </Duel.Provider>
</TankopediaEphemeral.Provider> </>
); );
} }
@@ -41,28 +41,27 @@ export function Page({ id, skeleton, locale, guide }: PageProps) {
return ( return (
<LocaleProvider locale={locale}> <LocaleProvider locale={locale}>
<TankopediaEphemeral.Provider data={model}> <TankopediaEphemeral.Provider args={model} />
<App.Provider> <App.Provider>
<TankopediaPersistent.Provider> <TankopediaPersistent.Provider>
<Duel.Provider <Duel.Provider
data={{ tank, model, provisionDefinitions: provisionDefinitions }} data={{ tank, model, provisionDefinitions: provisionDefinitions }}
> >
<PageWrapper p="0" maxWidth="unset" color="purple" gap="9" pb="9"> <PageWrapper p="0" maxWidth="unset" color="purple" gap="9" pb="9">
<HeroSection skeleton={skeleton} /> <HeroSection skeleton={skeleton} />
<MetaSection /> <MetaSection />
<CalloutsSection /> <CalloutsSection />
{tank.type === TankType.RESEARCHABLE && !tank.deprecated && ( {tank.type === TankType.RESEARCHABLE && !tank.deprecated && (
<TechTreeSection skeleton={skeleton} /> <TechTreeSection skeleton={skeleton} />
)} )}
<CharacteristicsSection skeleton={skeleton} /> <CharacteristicsSection skeleton={skeleton} />
<GameModeSection /> <GameModeSection />
{guide && <GuideSection guide={guide} />} {guide && <GuideSection guide={guide} />}
<VideoSection skeleton={skeleton} /> <VideoSection skeleton={skeleton} />
</PageWrapper> </PageWrapper>
</Duel.Provider> </Duel.Provider>
</TankopediaPersistent.Provider> </TankopediaPersistent.Provider>
</App.Provider> </App.Provider>
</TankopediaEphemeral.Provider>
</LocaleProvider> </LocaleProvider>
); );
} }
@@ -28,9 +28,8 @@ export function Page({ id }: PageProps) {
<App.Provider> <App.Provider>
<TankopediaPersistent.Provider> <TankopediaPersistent.Provider>
<Duel.Provider data={{ provisionDefinitions, model, tank }}> <Duel.Provider data={{ provisionDefinitions, model, tank }}>
<TankopediaEphemeral.Provider data={model}> <TankopediaEphemeral.Provider args={model} />
<Content /> <Content />
</TankopediaEphemeral.Provider>
</Duel.Provider> </Duel.Provider>
</TankopediaPersistent.Provider> </TankopediaPersistent.Provider>
</App.Provider> </App.Provider>
@@ -4,13 +4,11 @@ import {
type ModelDefinition, type ModelDefinition,
} from '@blitzkit/core'; } from '@blitzkit/core';
import type { Vector3 } from 'three'; import type { Vector3 } from 'three';
import { create } from 'zustand'; import { Varuna } from 'varuna';
import { subscribeWithSelector } from 'zustand/middleware';
import type { ArmorType } from '../components/Armor/components/SpacedArmorScene'; import type { ArmorType } from '../components/Armor/components/SpacedArmorScene';
import type { ExternalModuleVariant } from '../components/Armor/components/SpacedArmorSceneComponent'; import type { ExternalModuleVariant } from '../components/Armor/components/SpacedArmorSceneComponent';
import type { XP_MULTIPLIERS } from '../components/Tankopedia/TechTreeSection'; import type { XP_MULTIPLIERS } from '../components/Tankopedia/TechTreeSection';
import { awaitableSkillDefinitions } from '../core/awaitables/skillDefinitions'; import { awaitableSkillDefinitions } from '../core/awaitables/skillDefinitions';
import { createContextualStore } from '../core/zustand/createContextualStore';
import { TankopediaDisplay } from './tankopediaPersistent/constants'; import { TankopediaDisplay } from './tankopediaPersistent/constants';
export interface ShotLayerBase { export interface ShotLayerBase {
@@ -109,20 +107,17 @@ interface TankopediaEphemeral {
const skillDefinitions = await awaitableSkillDefinitions; const skillDefinitions = await awaitableSkillDefinitions;
export const TankopediaEphemeral = createContextualStore( export const TankopediaEphemeral = new Varuna<
(model: ModelDefinition) => { TankopediaEphemeral,
return create<TankopediaEphemeral>()( ModelDefinition
subscribeWithSelector<TankopediaEphemeral>(() => ({ >((model) => ({
disturbed: false, disturbed: false,
revealed: false, revealed: false,
relativeAgainst: TankopediaRelativeAgainst.Class, relativeAgainst: TankopediaRelativeAgainst.Class,
editStatic: false, editStatic: false,
skills: createDefaultSkills(skillDefinitions), skills: createDefaultSkills(skillDefinitions),
model, model,
controlsEnabled: true, controlsEnabled: true,
xpMultiplier: 1, xpMultiplier: 1,
display: TankopediaDisplay.Model, display: TankopediaDisplay.Model,
})), }));
);
},
);