sparkletown/sparkle

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

Summary

Maintainability
D
1 day
Test Coverage
import { Entity } from "@ash.ts/ash";
import { Sprite } from "pixi.js";

import { DEFAULT_PORTAL_BOX } from "settings";

import { setAnimateMapRoom } from "store/actions/AnimateMap";
import { ReplicatedVenue } from "store/reducers/AnimateMap";

import { GameConfig } from "../../../configs/GameConfig";
import { CropVenue } from "../../commands/CropVenue";
import { GameInstance } from "../../GameInstance";
import { AnimationComponent } from "../components/AnimationComponent";
import { ClickableSpriteComponent } from "../components/ClickableSpriteComponent";
import { CollisionComponent } from "../components/CollisionComponent";
import { HoverableSpriteComponent } from "../components/HoverableSpriteComponent";
import { PositionComponent } from "../components/PositionComponent";
import { SpriteComponent } from "../components/SpriteComponent";
import { TooltipComponent } from "../components/TooltipComponent";
import { VenueComponent } from "../components/VenueComponent";
import { FSMBase } from "../finalStateMachines/FSMBase";
import { HoverIn } from "../graphics/HoverIn";
import { HoverOut } from "../graphics/HoverOut";
import { Venue } from "../graphics/Venue";
import { VenueHalo } from "../graphics/VenueHalo";
import { VenueHaloAnimated } from "../graphics/VenueHaloAnimated";
import { VenueHaloEmpty } from "../graphics/VenueHaloEmpty";

import EntityFactory from "./EntityFactory";

const TOOLTIP_COLOR_DEFAULT = 0x655a4d;
const TOOLTIP_COLOR_ISLIVE = 0x8e5ffe;
const TOOLTIP_TEXT_LENGTH_MAX = 18;

const addVenueTooltip = (venue: ReplicatedVenue, entity: Entity) => {
  if (entity.get(TooltipComponent)) {
    return;
  }
  const text =
    venue.data.title.length > TOOLTIP_TEXT_LENGTH_MAX
      ? venue.data.title.slice(0, 15) + "..."
      : venue.data.title;
  const tooltip = new TooltipComponent(text);
  tooltip.borderColor = venue.data.isLive
    ? TOOLTIP_COLOR_ISLIVE
    : TOOLTIP_COLOR_DEFAULT;
  tooltip.backgroundColor = tooltip.borderColor;
  entity.add(tooltip);
};

const updateVenueImage = (
  replicatedVenue: ReplicatedVenue,
  spriteComponent: SpriteComponent,
  positionComponent: PositionComponent
): Promise<void> => {
  return new CropVenue(replicatedVenue.data.image_url)
    .setUsersCount(replicatedVenue.data.countUsers)
    .setWithoutPlate(replicatedVenue.data.withoutPlate)
    .setUsersCountColor(
      replicatedVenue.data.isLive ? TOOLTIP_COLOR_ISLIVE : TOOLTIP_COLOR_DEFAULT
    )
    .execute()
    .then((comm: CropVenue) => {
      const scaleSize = replicatedVenue.data.withoutPlate ? 4 : 1;
      const size = GameConfig.VENUE_DEFAULT_SIZE * scaleSize;
      const scale = size / comm.canvas.width;
      positionComponent.scaleY = scale;
      positionComponent.scaleX = scale;

      const venueSprite = spriteComponent.view as Venue;
      if (venueSprite.main) {
        venueSprite.main.parent?.removeChild(venueSprite.main);
      }

      venueSprite.main = Sprite.from(comm.canvas);
      if (replicatedVenue.data.withoutPlate) {
        // 1 - 0.5 / ((comm.canvas.height * 2) / comm.canvas.width)
        venueSprite.main.anchor.set(0.5, 0.5);
      } else {
        venueSprite.main.anchor.set(0.5);
      }
      venueSprite.addChild(venueSprite.main);
      return Promise.resolve();
    })
    .catch((err) => {
      console.log("err", err);
    })
    .finally(() => {
      return Promise.resolve();
    });
};

const getCurrentReplicatedVenue = (
  venueComponent: VenueComponent
): ReplicatedVenue => {
  return venueComponent.model;
};

export const updateVenueEntity = (
  venue: ReplicatedVenue,
  creator: EntityFactory
) => {
  const node = creator.getVenueNode(venue);
  if (!node) {
    return;
  }

  node.venue.model = venue;
  node.entity.add(node.venue);

  const sprite = node.entity.get(SpriteComponent);
  if (!sprite) {
    return;
  }
  updateVenueImage(venue, sprite, node.position);
};

export const createVenueEntity = (
  venue: ReplicatedVenue,
  creator: EntityFactory
) => {
  const engine = creator.engine;
  const entity: Entity = new Entity();
  const fsm: FSMBase = new FSMBase(entity);
  const venueComponent = new VenueComponent(venue, fsm);
  const positionComponent = new PositionComponent(venue.x, venue.y, 0, 0, 0);
  const spriteComponent: SpriteComponent = new SpriteComponent();
  const sprite: Venue = new Venue();
  sprite.zIndex = -1;
  spriteComponent.view = sprite;

  fsm
    .createState(venueComponent.WITHOUT_HALO)
    .add(VenueHaloEmpty)
    .withMethod(
      (): VenueHaloEmpty => {
        return new VenueHaloEmpty(sprite);
      }
    );

  fsm
    .createState(venueComponent.HALO)
    .add(VenueHalo)
    .withMethod(
      (): VenueHalo => {
        return new VenueHalo(sprite);
      }
    );

  fsm
    .createState(venueComponent.HALO_ANIMATED)
    .add(AnimationComponent)
    .withMethod(
      (): AnimationComponent => {
        return new AnimationComponent(
          new VenueHaloAnimated(sprite),
          Number.MAX_VALUE
        );
      }
    );

  let hoverEffectEntity: Entity;
  const hoverEffectDuration = 100;
  const scaleSize = venue.data.withoutPlate ? 3.5 : 1;
  entity
    .add(positionComponent)
    .add(venueComponent)
    .add(spriteComponent)
    .add(
      new CollisionComponent(
        GameConfig.VENUE_DEFAULT_COLLISION_RADIUS * scaleSize
      )
    )
    .add(
      new HoverableSpriteComponent(
        () => {
          // add tooltip
          const waiting = creator.getWaitingVenueClick();
          const currentVenue = getCurrentReplicatedVenue(venueComponent);
          if (!waiting || waiting.data.id !== currentVenue.data.id) {
            addVenueTooltip(currentVenue, entity);
          }

          // add increase
          const comm = entity.get(SpriteComponent);
          if (comm) {
            if (hoverEffectEntity) {
              engine.removeEntity(hoverEffectEntity);
            }
            hoverEffectEntity = new Entity();
            hoverEffectEntity.add(
              new AnimationComponent(
                new HoverIn(comm.view as Venue, hoverEffectDuration),
                hoverEffectDuration
              )
            );
            engine.addEntity(hoverEffectEntity);
          }
        },
        () => {
          // remove tooltip
          entity.remove(TooltipComponent);
          // add decrease
          const comm: SpriteComponent | null = entity.get(SpriteComponent);
          if (comm) {
            if (hoverEffectEntity) {
              engine.removeEntity(hoverEffectEntity);
            }
            hoverEffectEntity = new Entity();
            hoverEffectEntity.add(
              new AnimationComponent(
                new HoverOut(comm.view as Venue, hoverEffectDuration),
                hoverEffectDuration
              )
            );
            engine.addEntity(hoverEffectEntity);
          }
        }
      )
    )
    .add(
      new ClickableSpriteComponent(() => {
        const currentVenue = getCurrentReplicatedVenue(venueComponent);
        GameInstance.instance.getStore().dispatch(
          setAnimateMapRoom({
            ...DEFAULT_PORTAL_BOX,
            title: currentVenue.data.title,
            subtitle: currentVenue.data.subtitle,
            url: currentVenue.data.url,
            about: currentVenue.data.about,
            isEnabled: currentVenue.data.isEnabled,
            image_url: currentVenue.data.image_url,
          })
        );
      })
    );

  engine.addEntity(entity);

  spriteComponent.view.visible = false;
  updateVenueImage(venue, spriteComponent, positionComponent).finally(() => {
    if (spriteComponent && spriteComponent.view) {
      spriteComponent.view.visible = true;
    }
  });

  return entity;
};