lib/gamefic/scripting/entities.rb



# frozen_string_literal: true


require 'set'

module Gamefic
  module Scripting
    # Methods related to managing entities.

    #

    module Entities
      # extend Scriptable

      include Proxies

      # @return [Array<Gamefic::Entity>]

      def entities
        entity_set.to_a
      end

      # @return [Array<Gamefic::Actor, Gamefic::Active>]

      def players
        player_set.to_a
      end

      # Create an entity.

      #

      # @example

      #   class MyPlot < Gamefic::Plot

      #     seed { make Gamefic::Entity, name: 'thing' }

      #   end

      #

      # @param klass [Class<Gamefic::Entity>]

      # @return [Gamefic::Entity]

      def make klass, **opts
        klass.new(**unproxy(opts)).tap { |entity| entity_set.add entity }
      end

      def destroy(entity)
        entity.children.each { |child| destroy child }
        entity.parent = nil
        entity_set.delete entity
        entity
      end

      def find *args
        args.inject(entities) do |entities, arg|
          case arg
          when String
            result = Scanner.scan(entities, arg)
            result.remainder.empty? ? result.match : []
          else
            entities.that_are(arg)
          end
        end
      end

      # Pick a unique entity based on the given arguments. String arguments are

      # used to scan the entities for matching names and synonyms. Return nil

      # if an entity could not be found or there is more than one possible

      # match.

      #

      # @return [Gamefic::Entity, nil]

      def pick *args
        matches = find(*args)
        return nil unless matches.one?

        matches.first
      end

      # Same as #pick, but raise an error if a unique match could not be found.

      #

      #

      # @raise [RuntimeError] if a unique match was not found.

      #

      # @param args [Array]

      # @return [Gamefic::Entity]

      def pick! *args
        matches = find(*args)
        raise "no entity matching '#{args.inspect}'" if matches.empty?
        raise "multiple entities matching '#{args.inspect}': #{matches.join_and}" unless matches.one?

        matches.first
      end

      private

      def entity_set
        @entity_set ||= Set.new
      end

      def player_set
        @player_set ||= Set.new
      end
    end
  end
end