sparkletown/sparkle

View on GitHub
src/components/templates/AnimateMap/game/map/entities/EntityFactory.ts

Summary

Maintainability
F
5 days
Test Coverage
import { Engine, Entity, NodeList } from "@ash.ts/ash";
import { Sprite } from "pixi.js";

import { setAnimateMapFireBarrel } from "store/actions/AnimateMap";
import {
  ReplicatedArtcar,
  ReplicatedFirebarrel,
  ReplicatedUser,
  ReplicatedVenue,
} from "store/reducers/AnimateMap";

import { GameConfig } from "../../../configs/GameConfig";
import { ImageToCanvas } from "../../commands/ImageToCanvas";
import { LoadImage } from "../../commands/LoadImage";
import { RoundAvatar } from "../../commands/RoundAvatar";
import { avatarCycles } from "../../constants/AssetConstants";
import { GameInstance } from "../../GameInstance";
import { AvatarTuningComponent } from "../components/AvatarTuningComponent";
import { BubbleComponent } from "../components/BubbleComponent";
import { CollisionComponent } from "../components/CollisionComponent";
import { DeadComponent } from "../components/DeadComponent";
import { FirebarrelComponent } from "../components/FirebarrelComponent";
import { JoystickComponent } from "../components/JoystickComponent";
import { KeyboardComponent } from "../components/KeyboardComponent";
import { MotionControlSwitchComponent } from "../components/MotionControlSwitchComponent";
import { MotionKeyboardControlComponent } from "../components/MotionKeyboardControlComponent";
import { MotionTeleportComponent } from "../components/MotionTeleportComponent";
import { MovementComponent } from "../components/MovementComponent";
import { PlayerComponent } from "../components/PlayerComponent";
import { PositionComponent } from "../components/PositionComponent";
import { SoundEmitterComponent } from "../components/SoundEmitterComponent";
import { SpriteComponent } from "../components/SpriteComponent";
import { TooltipComponent } from "../components/TooltipComponent";
import { ViewportComponent } from "../components/ViewportComponent";
import { ViewportFollowComponent } from "../components/ViewportFollowComponent";
import { WaitingArtcarEnterClickComponent } from "../components/WaitingArtcarEnterClickComponent";
import { WaitingVenueClickComponent } from "../components/WaitingVenueClickComponent";
import { FSMBase } from "../finalStateMachines/FSMBase";
import { Avatar } from "../graphics/Avatar";
import { VenueTooltipEnter } from "../graphics/VenueTooltipEnter";
import { ArtcarNode } from "../nodes/ArtcarNode";
import { AvatarTuningNode } from "../nodes/AvatarTuningNode";
import { BotNode } from "../nodes/BotNode";
import { FirebarrelNode } from "../nodes/FirebarrelNode";
import { JoystickNode } from "../nodes/JoystickNode";
import { KeyboardNode } from "../nodes/KeyboardNode";
import { MotionBotControlNode } from "../nodes/MotionBotControlNode";
import { PlayerNode } from "../nodes/PlayerNode";
import { VenueNode } from "../nodes/VenueNode";
import { ViewportNode } from "../nodes/ViewportNode";
import { WaitingVenueClickNode } from "../nodes/WaitingVenueClickNode";

import { createArtcarEntity } from "./createArtcarEntity";
import { createBotEntity } from "./createBotEntity";
import {
  createFirebarrelEntity,
  updateFirebarrelEntity,
} from "./createFirebarrelEntity";
import { createVenueEntity, updateVenueEntity } from "./createVenueEntity";

export default class EntityFactory {
  public engine: Engine;

  constructor(engine: Engine) {
    this.engine = engine;
  }

  public createWaitingArtcarClick(
    artcar: ReplicatedArtcar
  ): Entity | undefined {
    const nodes = this.engine.getNodeList(WaitingVenueClickNode);
    while (nodes.head) {
      this.engine.removeEntity(nodes.head.entity);
    }

    const venueNode = this.getArtcarNode(artcar.data.id);
    if (!venueNode) {
      return undefined;
    }

    const tooltip = new TooltipComponent("", 50);
    tooltip.view = new VenueTooltipEnter(
      venueNode.artcar.artcar.data.title,
      0x6943f5
    );

    const spriteComponent = new SpriteComponent();
    spriteComponent.view = new Sprite();

    const entity = new Entity()
      .add(new WaitingArtcarEnterClickComponent(artcar))
      .add(new DeadComponent(250))
      .add(spriteComponent)
      .add(venueNode.position)
      .add(tooltip);

    this.engine.addEntity(entity);

    // removeAllTooltip on this venue
    venueNode.entity.remove(TooltipComponent);

    return entity;
  }

  public createWaitingVenueClick(venue: ReplicatedVenue): Entity | undefined {
    const nodes = this.engine.getNodeList(WaitingVenueClickNode);
    while (nodes.head) {
      this.engine.removeEntity(nodes.head.entity);
    }

    const venueNode = this.getVenueNode(venue);
    if (!venueNode) {
      return undefined;
    }

    const tooltip = new TooltipComponent("", 50);
    tooltip.view = new VenueTooltipEnter(
      venueNode.venue.model.data.title,
      0x6943f5
    );

    const spriteComponent = new SpriteComponent();
    spriteComponent.view = new Sprite();

    const entity = new Entity()
      .add(new WaitingVenueClickComponent(venue))
      .add(new DeadComponent(250))
      .add(spriteComponent)
      .add(new PositionComponent(venueNode.position.x, venueNode.position.y))
      .add(tooltip);

    this.engine.addEntity(entity);

    // removeAllTooltip on this venue
    venueNode.entity.remove(TooltipComponent);

    return entity;
  }

  public getWaitingVenueClick(): ReplicatedVenue | undefined {
    return this.engine.getNodeList(WaitingVenueClickNode).head?.venue.venue;
  }

  public getPlayerNode(): PlayerNode | null {
    return this.engine.getNodeList(PlayerNode).head;
  }

  public getRandomBot(): ReplicatedUser | undefined {
    const bots = GameInstance.instance.getState().users;
    const botIndex = Math.floor(Math.random() * bots.size);
    if (botIndex > 0) {
      const itr = bots.values();
      let count = 0;
      for (let bot = itr.next().value; bot; bot = itr.next().value) {
        if (count === botIndex) {
          return bot;
        }
        count++;
      }
    }
    return undefined;
  }

  public getBotNode(id: string): BotNode | null {
    const bots = this.engine.getNodeList(BotNode);
    for (let bot = bots?.head; bot; bot = bot.next) {
      if (bot.bot.data.data.id === id) {
        return bot;
      }
    }
    return null;
  }

  public createViewport(com: ViewportComponent): Entity {
    const nodelist = this.engine.getNodeList(ViewportNode);
    while (nodelist.head) {
      this.removeEntity(nodelist.head.entity);
    }
    const entity: Entity = new Entity().add(com);
    this.engine.addEntity(entity);

    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    //@ts-ignore
    return nodelist.head.entity;
  }

  public updateViewport(comm?: ViewportComponent) {
    const nodelist = this.engine.getNodeList(ViewportNode);
    if (!nodelist.head) {
      return;
    }
    nodelist.head.entity.add(comm ? comm : nodelist.head.viewport);
  }

  public createJoystick(comm: JoystickComponent): Entity {
    const nodelist = this.engine.getNodeList(JoystickNode);
    while (nodelist.head) {
      this.removeEntity(nodelist.head.entity);
    }

    const entity: Entity = new Entity().add(comm);
    this.engine.addEntity(entity);
    return entity;
  }

  public updateJoystick(comm?: JoystickComponent) {
    const nodelist = this.engine.getNodeList(JoystickNode);
    if (!nodelist.head) {
      return;
    }
    nodelist.head.entity.add(comm ? comm : nodelist.head.joystick);
  }

  public createKeyboard(
    comm: KeyboardComponent,
    control: MotionKeyboardControlComponent
  ): Entity {
    const nodelist = this.engine.getNodeList(KeyboardNode);
    while (nodelist.head) {
      this.removeEntity(nodelist.head.entity);
    }

    const entity = new Entity().add(comm).add(control);
    this.engine.addEntity(entity);
    return entity;
  }

  public updateKeyboard(comm: KeyboardComponent) {
    const nodelist = this.engine.getNodeList(KeyboardNode);
    if (!nodelist.head) {
      return;
    }
    nodelist.head.entity.add(comm ? comm : nodelist.head.keyboard);
  }

  public createShout(x: number, y: number, text: string): Entity {
    console.log("CREATE SHOUT");
    // TODO refactoring
    const spriteComponent = new SpriteComponent();
    const view = new Sprite();
    view.position.set(x, y);

    const entity = new Entity()
      .add(new DeadComponent(100))
      .add(new BubbleComponent(text))
      .add(new PositionComponent(x, y))
      .add(spriteComponent);
    this.engine.addEntity(entity);
    return entity;
  }

  public createBubble(userId: string, text: string): Entity | null {
    const bot = this.getBotNode(userId);
    const player = this.getPlayerNode();
    if (bot) {
      bot.entity.add(new BubbleComponent(text, bot.bot.data.data.dotColor));
      return bot.entity;
    }
    if (player) {
      player.entity.add(new BubbleComponent(text));
    }
    return null;
  }

  public removeBubble(entity: Entity) {
    entity.remove(BubbleComponent);
  }

  public createPlayer(user: ReplicatedUser): Entity {
    // HACK
    user.data.cycle = avatarCycles[0];

    const movementComponent = new MovementComponent();
    const motionControl = new MotionControlSwitchComponent();
    const collision: CollisionComponent = new CollisionComponent(0);

    const scale = 0.36;

    const entity: Entity = new Entity();
    const fsm: FSMBase = new FSMBase(entity);
    const player = new PlayerComponent(user, fsm);

    fsm.createState(player.IMMOBILIZED);

    fsm
      .createState(player.FLYING)
      .add(CollisionComponent)
      .withInstance(collision)
      .add(MovementComponent)
      .withInstance(movementComponent)
      .add(MotionControlSwitchComponent)
      .withInstance(motionControl);

    fsm
      .createState(player.CYCLING)
      .add(CollisionComponent)
      .withInstance(collision)
      .add(MovementComponent)
      .withInstance(movementComponent)
      .add(MotionControlSwitchComponent)
      .withInstance(motionControl);

    fsm
      .createState(player.WALKING)
      .add(CollisionComponent)
      .withInstance(collision)
      .add(MovementComponent)
      .withInstance(movementComponent)
      .add(MotionControlSwitchComponent)
      .withInstance(motionControl);

    entity
      .add(movementComponent)
      .add(motionControl)
      .add(player)
      .add(new PositionComponent(user.x, user.y, 0, scale, scale))
      .add(new ViewportFollowComponent());

    fsm.changeState(player.FLYING);
    this.engine.addEntity(entity);

    const url = user.data.pictureUrl;
    const sprite: Avatar = new Avatar();

    if (GameConfig.AVATAR_TEXTURE_USE_WITHOUT_PREPROCESSING) {
      new LoadImage(url)
        .execute()
        .then((comm) => {
          if (comm.image) {
            return new ImageToCanvas(comm.image).execute();
          } else {
            return Promise.reject();
          }
        })
        .then((comm) => {
          if (comm.canvas) {
            // avatar
            sprite.avatar = Sprite.from(comm.canvas);
            sprite.avatar.anchor.set(0.5);
            sprite.addChild(sprite.avatar);
          }
        })
        .catch((error) => {})
        .finally(() => {
          const spriteComponent: SpriteComponent = new SpriteComponent();
          spriteComponent.view = sprite;
          entity.add(spriteComponent);
        });
    } else {
      new RoundAvatar(url)
        .execute()
        .then((comm: RoundAvatar) => {
          if (!comm.canvas) return Promise.reject();

          // avatar
          sprite.avatar = Sprite.from(comm.canvas);
          sprite.avatar.anchor.set(0.5);
          sprite.addChild(sprite.avatar);

          return Promise.resolve(comm);
        })
        .catch(() => {})
        .finally(() => {
          const spriteComponent: SpriteComponent = new SpriteComponent();
          spriteComponent.view = sprite;
          entity.add(spriteComponent);
        });
    }

    return entity;
  }

  public createArtcar(user: ReplicatedArtcar): Entity {
    return createArtcarEntity(user, this);
  }

  public getArtcarNode(id: number): ArtcarNode | undefined {
    const nodes = this.engine.getNodeList(ArtcarNode);
    for (let node = nodes.head; node; node = node.next) {
      if (node.artcar.artcar.data.id === id) {
        return node;
      }
    }
    return undefined;
  }

  public updatePlayerTuning(node: PlayerNode) {
    node.entity.add(new AvatarTuningComponent(node.player.data));
  }

  public removePlayerTuning(node: PlayerNode) {
    node.entity.remove(AvatarTuningComponent);
  }

  public updateBotTuning(node: BotNode) {
    node.entity.add(new AvatarTuningComponent(node.bot.data));
  }

  public removeAvatarTuning(node: AvatarTuningNode) {
    node.entity.remove(AvatarTuningComponent);
  }

  public createBot(user: ReplicatedUser, realUser = false): Entity {
    return createBotEntity(user, this, realUser);
  }

  public removeBot(entity: Entity) {
    this.engine.removeEntity(entity);
  }

  public removeBotById(id: string) {
    const list: NodeList<BotNode> = this.engine.getNodeList(BotNode);
    for (let bot: BotNode | null | undefined = list.head; bot; bot = bot.next) {
      if (bot.bot.data.data.id === id) {
        this.removeBot(bot.entity);
        return;
      }
    }
  }

  public updateBotPosition(user: ReplicatedUser, x: number, y: number) {
    const list: NodeList<BotNode> = this.engine.getNodeList(BotNode);
    for (let bot = list.head; bot; bot = bot.next) {
      if (bot.bot.data.data.id === user.data.id) {
        bot.bot.fsm.changeState("idle");
        bot.bot.fsm.changeState("moving");
        const node: MotionBotControlNode = this.engine.getNodeList(
          MotionBotControlNode
        ).tail as MotionBotControlNode;
        node.click.x = x;
        node.click.y = y;
        return;
      }
    }
  }

  public updateUserPositionById(user: ReplicatedUser) {
    let bot: BotNode | null = this.getBotNode(user.data.id);
    if (!bot) {
      // const player: PlayerModel = new PlayerModel(user, -1, "", x, y);
      // player.data.id = user;
      // player.x = x;
      // player.y = y;
      this.createBot(user, true);
      bot = this.engine.getNodeList(BotNode).head as BotNode;
      bot.bot.fsm.changeState("idle");
    } else {
      this.updateBotPosition(bot.bot.data, user.x, user.y); //TODO: update another field too?
    }
  }

  public removeUserById(id: string) {
    const node: BotNode | null = this.getBotNode(id);
    if (node) {
      this.removeEntity(node.entity);
    }
  }

  public createUser(hero: ReplicatedUser): Entity {
    const entity: Entity = new Entity().add(
      new PositionComponent(hero.x, hero.y)
    );

    this.engine.addEntity(entity);
    return entity;
  }

  public createFireBarrel(barrel: ReplicatedFirebarrel): Entity {
    const node = this.getFirebarrelNode(barrel.data.id);
    if (node) return node.entity;
    return createFirebarrelEntity(barrel, this);
  }

  public enterFirebarrel(firebarrelId: string): void {
    console.log("enterFirebarrel");

    const playerNode = this.getPlayerNode();
    const firebarrelNode = this.getFirebarrelNode(firebarrelId);
    if (
      playerNode &&
      firebarrelNode &&
      playerNode.player.fsm.currentStateName !== playerNode.player.IMMOBILIZED
    ) {
      GameInstance.instance
        .getStore()
        .dispatch(setAnimateMapFireBarrel(firebarrelId));

      playerNode.entity.add(firebarrelNode.firebarrel);
      playerNode.player.fsm.changeState(playerNode.player.IMMOBILIZED);

      const x1 = firebarrelNode.position.x;
      const y1 = firebarrelNode.position.y;
      const x2 = playerNode.position.x;
      const y2 = playerNode.position.y;
      const d = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
      const r = (firebarrelNode.collision.radius * 1.6) / d;

      const x3 =
        r * x2 + (1 - r) * x1 ||
        firebarrelNode.position.x + firebarrelNode.collision.radius * 0.8;
      const y3 =
        r * y2 + (1 - r) * y1 ||
        firebarrelNode.position.y + firebarrelNode.collision.radius * 0.8;

      playerNode.entity.add(new MotionTeleportComponent(x3, y3));

      // setTimeout(() => {
      //   this.exitFirebarrel();
      // }, 5000);
    }
  }

  public exitFirebarrel(): void {
    console.log("exitFirebarrel");
    const playerNode = this.getPlayerNode();

    if (!playerNode) {
      return;
    }
    const barrelComponent = playerNode.entity.remove(FirebarrelComponent);
    playerNode.player.fsm.changeState(playerNode.player.FLYING);
    playerNode.entity.add(playerNode.player);

    if (barrelComponent) {
      const x1 = barrelComponent.model.x;
      const y1 = barrelComponent.model.y;
      const x2 = playerNode.position.x;
      const y2 = playerNode.position.y;

      const x3 = playerNode.position.x + (x2 - x1) * 2;
      const y3 = playerNode.position.y + (y2 - y1) * 2;

      playerNode.entity.add(new MotionTeleportComponent(x3, y3));
    }
  }

  public createVenue(venue: ReplicatedVenue): Entity {
    const node = this.getVenueNode(venue);
    if (node) return node.entity;
    return createVenueEntity(venue, this);
  }

  public removeVenue(venue: ReplicatedVenue) {
    const node = this.getVenueNode(venue);
    if (node) this.engine.removeEntity(node.entity);
  }

  public updateVenue(venue: ReplicatedVenue) {
    updateVenueEntity(venue, this);
  }

  public getVenueNode(venue: ReplicatedVenue): VenueNode | undefined {
    const nodes: NodeList<VenueNode> = this.engine.getNodeList(VenueNode);
    for (let node = nodes.head; node; node = node.next) {
      if (node.venue.model.data.id === venue.data.id) {
        return node;
      }
    }
    return undefined;
  }

  public createSoundEmitter(
    x: number,
    y: number,
    radius: number,
    src: string
  ): Entity {
    const entity: Entity = new Entity()
      .add(new PositionComponent(x, y))
      .add(new SoundEmitterComponent(radius, src));
    this.engine.addEntity(entity);

    return entity;
  }

  public removeEntity(entity: Entity) {
    this.engine.removeEntity(entity);
  }

  public getRandomNumber(min: number, max: number): number {
    return Math.floor(Math.random() * (max - min) + min);
  }

  public setPlayerCameraFollow(value: boolean) {
    const playerEntity = this.getPlayerNode()?.entity;

    if (value && !playerEntity?.get(ViewportFollowComponent))
      playerEntity?.add(new ViewportFollowComponent());

    if (!value && playerEntity?.get(ViewportFollowComponent))
      playerEntity?.remove(ViewportFollowComponent);
  }

  public getFirebarrelNode(id: string): FirebarrelNode | undefined {
    const nodes: NodeList<FirebarrelNode> = this.engine.getNodeList(
      FirebarrelNode
    );
    for (let node = nodes.head; node; node = node.next) {
      if (node.firebarrel.model.data.id === id) {
        return node;
      }
    }
    return undefined;
  }

  public removeBarrel(firebarrel: ReplicatedFirebarrel) {
    const node = this.getFirebarrelNode(firebarrel.data.id);
    if (node) this.engine.removeEntity(node.entity);
  }

  public updateBarrel(firebarrel: ReplicatedFirebarrel) {
    const node = this.getFirebarrelNode(firebarrel.data.id);
    if (!node) {
      return;
    }

    updateFirebarrelEntity(firebarrel, this);
  }
}