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 };