import * as Pixi from "pixi.js"; import { Vector2 } from "../../lib/util/geometry/vector2"; import { ChunkGenConstants, GameState, IntentName} from "../../data/GameState"; import { generatePointNodeTexture } from "../textures/PointNodeTexture"; import { ZLevelGenFactory } from "../../game/WorldGenStateFactory"; import { Const, Lazy } from "../../lib/util/misc"; import { UpdaterGeneratorType2 } from "../../lib/util/updaterGenerator"; import { ZLevelComponent, ZLevelComponentProps } from "./ZLevelComponent"; import { engageLifecycle, LifecycleHandlerBase } from "./LifecycleHandler"; import { FixedCameraStageComponent } from "./FixedCameraStageComponent"; import { TooltipInfo } from "./TooltipComponent"; type State = { pointNodeTexture: Lazy<Pixi.Texture>; tick: number; playerCurrentZ: number; tooltip: TooltipInfo; } type Props = { args: { renderer: Pixi.Renderer, markForceUpdate: (childInstance: any) => void, }, updaters: UpdaterGeneratorType2<GameState>, delta: number, gameState: Const<GameState>, appSize: Vector2 } class RootComponent2 extends LifecycleHandlerBase<Props, State> { public container: Pixi.Container; public state: State; private stateUpdaters: UpdaterGeneratorType2<State>; protected fireStateUpdaters: () => void; /* children */ // Contains HUD, and other entities that don't move when game camera moves private fixedCameraStage: FixedCameraStageComponent; // Contains game entities that move when game camera pans/zooms. Highly encouraged to have further subdivions. private actionStage: Pixi.Container; // Contains a few entities that doesn't move when game camera moves, but located behind action stage entities, e.g. static backgrounds private backdropStage: Pixi.Container; // public keyboard: KeyboardState; private zLevel: ZLevelComponent | undefined; private zLevelPropsFactory: (p: Props, s: State) => ZLevelComponentProps; private backdrop: Pixi.Graphics; constructor(props: Props) { super(props); this.container = new Pixi.Container(); this.container.sortableChildren = true; ({ state: this.state, stateUpdaters: this.stateUpdaters, fireStateUpdaters: this.fireStateUpdaters } = this.useState<State, RootComponent2>(this, { pointNodeTexture: new Lazy(() => generatePointNodeTexture(props.args.renderer)), tick: 0, playerCurrentZ: 0, tooltip: { visible: false, position: undefined, text: '', } })); const fixedCameraStagePropsFactory = (props: Props, state: State) => { return { args: { renderer: props.args.renderer, markForceUpdate: this.markForceUpdate, }, delta: props.delta, gameState: props.gameState, appSize: props.appSize, tick: state.tick, tooltip: { ...state.tooltip } }; } this.fixedCameraStage = new FixedCameraStageComponent(fixedCameraStagePropsFactory(props, this.state)); this.addChild({ childClass: FixedCameraStageComponent, instance: this.fixedCameraStage, propsFactory: fixedCameraStagePropsFactory, }) this.actionStage = new Pixi.Sprite(); this.actionStage.zIndex = 0; this.actionStage.sortableChildren = true; this.container.addChild(this.actionStage); this.backdropStage = new Pixi.Sprite(); this.backdropStage.zIndex = -1; this.backdropStage.sortableChildren = true; this.container.addChild(this.backdropStage); this.backdrop = new Pixi.Graphics(); this.backdropStage.addChild(this.backdrop); this.backdrop.beginFill(0xabcdef, 1); // backdrop.alpha = 0.5; // if alpha == 0, Pixi does not register this as a hittable area this.backdrop.interactive = true; // backdrop.interactiveChildren = true; // not sure what this does this.backdrop.drawRect(0, 0, props.appSize.x, props.appSize.y); this.zLevelPropsFactory = (props: Props, state: State): ZLevelComponentProps => { return { delta: props.delta, args: { pointNodeTexture: state.pointNodeTexture.get(), markForceUpdate: this.markForceUpdate, }, z: state.playerCurrentZ, updaters: props.updaters, tooltipUpdaters: this.stateUpdaters.tooltip, position: props.appSize.multiply(0.5), zLevelGen: props.gameState.worldGen.zLevels[state.playerCurrentZ], selectedPointNode: props.gameState.playerUI.selectedPointNode, allocatedPointNodeSubset: props.gameState.playerSave.allocatedPointNodeSet, }; } this.zLevel = new ZLevelComponent(this.zLevelPropsFactory(props, this.state)); this.actionStage.addChild(this.zLevel.container); this.registerChild({ childClass: ZLevelComponent, instance: this.zLevel, propsFactory: this.zLevelPropsFactory, }); } protected updateSelf(props: Props) { this.state.tick++; const activeIntent = props.gameState.intent.activeIntent; let deltaX = 0; let deltaY = 0; const unit = 5 * props.delta; // if we want to pan [the hud] west (i.e. the left key was pressed), action stage needs to move east if (activeIntent[IntentName.PAN_WEST]) deltaX += unit; if (activeIntent[IntentName.PAN_EAST]) deltaX += -unit; // if we want to pan south (i.e. the down key was pressed), action stage needs to move north to give the impression // the hud is moving south. note that north is negative y direction since top left is 0,0 if (activeIntent[IntentName.PAN_SOUTH]) deltaY += -unit; if (activeIntent[IntentName.PAN_NORTH]) deltaY += unit; this.actionStage.x += deltaX; this.actionStage.y += deltaY; if (props.gameState.intent.newIntent[IntentName.TRAVEL_IN]) { this.state.playerCurrentZ--; // scale by a factor of 9 this.actionStage.x *= ChunkGenConstants.CHUNK_DIM; this.actionStage.y *= ChunkGenConstants.CHUNK_DIM; // console.log({ currentZ: this.state.playerCurrentZ }); } if (props.gameState.intent.newIntent[IntentName.TRAVEL_OUT]) { this.state.playerCurrentZ++; this.actionStage.x /= ChunkGenConstants.CHUNK_DIM; this.actionStage.y /= ChunkGenConstants.CHUNK_DIM; // console.log({ currentZ: this.state.playerCurrentZ }); } } protected renderSelf(props: Props) { this.backdrop.width = props.appSize.x; this.backdrop.height = props.appSize.y; } protected didMount() { const { updaters } = this._staleProps; this.backdrop.addListener('pointerdown', (event) => { updaters.playerUI.selectedPointNode.enqueueUpdate((prev, whole) => { return undefined; }) }); } protected didUpdate() { const { updaters } = this._staleProps; // if we find ourselves a little idle, start pregenerating other layers if (this.state.tick > 60 && !this._staleProps.gameState.worldGen.zLevels[-1]) { updaters.worldGen.zLevels.enqueueUpdate((prev, prevGameState) => { if (!prev[-1]) { prev[-1] = new ZLevelGenFactory({}).create({ seed: prevGameState.worldGen.seed, z: -1 }); return {...prev}; } else { return prev; } }) } if (this.state.tick > 120 && !this._staleProps.gameState.worldGen.zLevels[1]) { updaters.worldGen.zLevels.enqueueUpdate((prev, prevGameState) => { if (!prev[1]) { prev[1] = new ZLevelGenFactory({}).create({ seed: prevGameState.worldGen.seed, z: 1 }); return {...prev}; } else { return prev; } }) } } } const wrapped = engageLifecycle(RootComponent2); // eslint-disable-next-line type wrapped = RootComponent2; export { wrapped as RootComponent }; export type { Props as RootComponentProps, State as RootComponentState };