<commit_before>require 'set'
require 'fiber'


require_relative "../abilities"
require_relative "world_object"
require_relative "floating_text"

require_relative "../mixins/line_of_sight"
require_relative "../mixins/pathfinding"
require_relative "../mixins/has_contents"

module SmashAndGrab
module Objects
class Entity < WorldObject
  include Mixins::LineOfSight
  include Mixins::Pathfinding
  include Mixins::HasContents

  CLASS = :entity

  SPRITE_WIDTH, SPRITE_HEIGHT = 66, 66
  PORTRAIT_WIDTH, PORTRAIT_HEIGHT = 36, 36

  STATS_BACKGROUND_COLOR = Color::BLACK
  STATS_HP_COLOR = Color.rgb(0, 200, 0)
  STATS_MP_COLOR = Color::rgb(50, 50, 255)
  STATS_AP_COLOR = Color::YELLOW
  STATS_WIDTH = 12.0
  STATS_HALF_WIDTH = STATS_WIDTH / 2
  PIP_WIDTH, PIP_SEP_WIDTH = 2, 0.5
  STATS_USED_SATURATION = 0.5

  ACTOR_NAME_COLOR = Color.rgb(50, 200, 50)
  TARGET_NAME_COLOR = Color.rgb(50, 200, 50)
  DAMAGE_NUMBER_COLOR = Color::RED

  COLOR_ACTIVE = Color::rgb(255, 255, 255)
  COLOR_ACTIVE_NO_MOVE = STATS_AP_COLOR
  COLOR_ACTIVE_NO_ACTION = STATS_MP_COLOR
  COLOR_ACTIVE_FINISHED = Color.rgb(70, 70, 70)
  COLOR_INACTIVE = Color::BLACK

  class << self
    def config; @config ||= YAML.load_file(File.expand_path("config/map/entities.yml", EXTRACT_PATH)); end
    def types; config.keys; end
    def sprites; @sprites ||= SpriteSheet["entities.png", SPRITE_WIDTH, SPRITE_HEIGHT, 8]; end
    def portraits; @portraits ||= SpriteSheet["entity_portraits.png", PORTRAIT_WIDTH, PORTRAIT_HEIGHT, 8]; end
  end

  event :ended_turn
  event :started_turn

  attr_reader :faction, :movement_points, :action_points, :health_points, :type, :portrait,
              :max_movement_points, :max_action_points, :max_health_points, :default_faction_type

  alias_method :hp, :health_points
  alias_method :max_hp, :max_health_points

  def minimap_color; @faction.minimap_color; end
  def active?; @faction.active?; end
  def inactive?; @faction.inactive?; end

  def movement_points=(movement_points)
    @movement_points = movement_points
    publish :changed
    @movement_points
  end

  def action_points=(action_points)
    @action_points = action_points
    publish :changed
    @action_points
  end

  alias_method :max_mp, :max_movement_points
  alias_method :max_ap, :max_action_points

  alias_method :mp, :movement_points
  alias_method :ap, :action_points

  alias_method :mp=, :movement_points=
  alias_method :ap=, :action_points=

  def to_s; "<#{self.class.name}/#{@type}##{id} #{tile ? grid_position : "[off-map]"}>"; end
  def alive?; @health_points > 0 and @tile; end
  def title; t.title; end
  def colorized_name; faction.class::TEXT_COLOR.colorize name; end

  def bystander?; faction.is_a? Factions::Bystanders; end
  def goody?; faction.is_a? Factions::Goodies; end
  def baddy?; faction.is_a? Factions::Baddies; end

  def initialize(map, data)
    @type = data[:type]
    config = self.class.config[data[:type]]

    @default_faction_type = config[:faction]
    @faction = nil

    options = {
        image: self.class.sprites[*config[:spritesheet_position]],
        factor_x: data[:facing].to_sym == :right ? 1 : -1,
    }

    @portrait = self.class.portraits[*config[:spritesheet_position]]

    super(map, data, options)

    raise @type unless image

    @max_movement_points = config[:movement_points]
    @movement_points = data[:movement_points] || @max_movement_points

    @max_action_points = config[:action_points]
    @action_points = data[:action_points] || @max_action_points

    @max_health_points = config[:health_points]
    @health_points = data[:health_points] || @max_health_points

    # Load other abilities of the entity from config.
    @abilities = {}

    # Everyone who has movement_points has the ability to move, without it needing to be explicit.
    @abilities[:move] = Abilities.ability(self, type: :move) if max_movement_points > 0

    if config[:abilities]
      config[:abilities].each do |ability_data|
        @abilities[ability_data[:type]] = Abilities.ability(self, ability_data)
      end
    end

    if max_ap > 0
      @abilities[:pick_up] = Abilities.ability(self, type: :pick_up)
      @abilities[:drop] = Abilities.ability(self, type: :drop)
    end

    @tmp_contents_id = data[:contents_id] # Need to wait until all objects are loaded before picking it up.

    @queued_activities = []

    @stat_bars_record = nil
    subscribe :changed do
      @stat_bars_record = nil
    end
  end

  def faction=(faction)
    @faction.remove self if @faction
    @faction = faction
    @faction << self
  end

  def has_ability?(type); @abilities.has_key? type; end
  def ability(type); @abilities[type]; end

  def health_points=(value)
    original_health = @health_points
    @health_points = [value, 0].max

    # Show damage/healing as a floating number.
    if original_health != @health_points
      text, color = if @health_points > original_health
                      ["+#{@health_points - original_health}", Color::GREEN]
                    else
                      [(@health_points - original_health).to_s, Color::RED]
                    end

      FloatingText.new(text, color: color, x: x, y: y - height / 3, zorder: y - 0.01)
      publish :changed
    end

    if @health_points == 0 and @tile
      parent.publish :game_info, "#{colorized_name} was vanquished!"
      self.tile = nil
      @queued_activities.empty?<commit_msg>Drop carried object on death.<commit_after>require 'set'
require 'fiber'


require_relative "../abilities"
require_relative "world_object"
require_relative "floating_text"

require_relative "../mixins/line_of_sight"
require_relative "../mixins/pathfinding"
require_relative "../mixins/has_contents"

module SmashAndGrab
module Objects
class Entity < WorldObject
  include Mixins::LineOfSight
  include Mixins::Pathfinding
  include Mixins::HasContents

  CLASS = :entity

  SPRITE_WIDTH, SPRITE_HEIGHT = 66, 66
  PORTRAIT_WIDTH, PORTRAIT_HEIGHT = 36, 36

  STATS_BACKGROUND_COLOR = Color::BLACK
  STATS_HP_COLOR = Color.rgb(0, 200, 0)
  STATS_MP_COLOR = Color::rgb(50, 50, 255)
  STATS_AP_COLOR = Color::YELLOW
  STATS_WIDTH = 12.0
  STATS_HALF_WIDTH = STATS_WIDTH / 2
  PIP_WIDTH, PIP_SEP_WIDTH = 2, 0.5
  STATS_USED_SATURATION = 0.5

  ACTOR_NAME_COLOR = Color.rgb(50, 200, 50)
  TARGET_NAME_COLOR = Color.rgb(50, 200, 50)
  DAMAGE_NUMBER_COLOR = Color::RED

  COLOR_ACTIVE = Color::rgb(255, 255, 255)
  COLOR_ACTIVE_NO_MOVE = STATS_AP_COLOR
  COLOR_ACTIVE_NO_ACTION = STATS_MP_COLOR
  COLOR_ACTIVE_FINISHED = Color.rgb(70, 70, 70)
  COLOR_INACTIVE = Color::BLACK

  class << self
    def config; @config ||= YAML.load_file(File.expand_path("config/map/entities.yml", EXTRACT_PATH)); end
    def types; config.keys; end
    def sprites; @sprites ||= SpriteSheet["entities.png", SPRITE_WIDTH, SPRITE_HEIGHT, 8]; end
    def portraits; @portraits ||= SpriteSheet["entity_portraits.png", PORTRAIT_WIDTH, PORTRAIT_HEIGHT, 8]; end
  end

  event :ended_turn
  event :started_turn

  attr_reader :faction, :movement_points, :action_points, :health_points, :type, :portrait,
              :max_movement_points, :max_action_points, :max_health_points, :default_faction_type

  alias_method :hp, :health_points
  alias_method :max_hp, :max_health_points

  def minimap_color; @faction.minimap_color; end
  def active?; @faction.active?; end
  def inactive?; @faction.inactive?; end

  def movement_points=(movement_points)
    @movement_points = movement_points
    publish :changed
    @movement_points
  end

  def action_points=(action_points)
    @action_points = action_points
    publish :changed
    @action_points
  end

  alias_method :max_mp, :max_movement_points
  alias_method :max_ap, :max_action_points

  alias_method :mp, :movement_points
  alias_method :ap, :action_points

  alias_method :mp=, :movement_points=
  alias_method :ap=, :action_points=

  def to_s; "<#{self.class.name}/#{@type}##{id} #{tile ? grid_position : "[off-map]"}>"; end
  def alive?; @health_points > 0 and @tile; end
  def title; t.title; end
  def colorized_name; faction.class::TEXT_COLOR.colorize name; end

  def bystander?; faction.is_a? Factions::Bystanders; end
  def goody?; faction.is_a? Factions::Goodies; end
  def baddy?; faction.is_a? Factions::Baddies; end

  def initialize(map, data)
    @type = data[:type]
    config = self.class.config[data[:type]]

    @default_faction_type = config[:faction]
    @faction = nil

    options = {
        image: self.class.sprites[*config[:spritesheet_position]],
        factor_x: data[:facing].to_sym == :right ? 1 : -1,
    }

    @portrait = self.class.portraits[*config[:spritesheet_position]]

    super(map, data, options)

    raise @type unless image

    @max_movement_points = config[:movement_points]
    @movement_points = data[:movement_points] || @max_movement_points

    @max_action_points = config[:action_points]
    @action_points = data[:action_points] || @max_action_points

    @max_health_points = config[:health_points]
    @health_points = data[:health_points] || @max_health_points

    # Load other abilities of the entity from config.
    @abilities = {}

    # Everyone who has movement_points has the ability to move, without it needing to be explicit.
    @abilities[:move] = Abilities.ability(self, type: :move) if max_movement_points > 0

    if config[:abilities]
      config[:abilities].each do |ability_data|
        @abilities[ability_data[:type]] = Abilities.ability(self, ability_data)
      end
    end

    if max_ap > 0
      @abilities[:pick_up] = Abilities.ability(self, type: :pick_up)
      @abilities[:drop] = Abilities.ability(self, type: :drop)
    end

    @tmp_contents_id = data[:contents_id] # Need to wait until all objects are loaded before picking it up.

    @queued_activities = []

    @stat_bars_record = nil
    subscribe :changed do
      @stat_bars_record = nil
    end
  end

  def faction=(faction)
    @faction.remove self if @faction
    @faction = faction
    @faction << self
  end

  def has_ability?(type); @abilities.has_key? type; end
  def ability(type); @abilities[type]; end

  def health_points=(value)
    original_health = @health_points
    @health_points = [value, 0].max

    # Show damage/healing as a floating number.
    if original_health != @health_points
      text, color = if @health_points > original_health
                      ["+#{@health_points - original_health}", Color::GREEN]
                    else
                      [(@health_points - original_health).to_s, Color::RED]
                    end

      FloatingText.new(text, color: color, x: x, y: y - height / 3, zorder: y - 0.01)
      publish :changed
    end

    if @health_points == 0 and @tile
      parent.publish :game_info, "#{colorized_name} was vanquished!"

      # Leave the tile, then drop anything we are carrying into it.
      # TODO: this is not undo/redoable!
      old_tile = tile
      self.tile = nil
      drop old_tile if contents

      @queued_activities.empty?
{'fraction_non_alphanumeric': 0.10876490397742787, 'fraction_numerical': 0.012924365158824065, 'mean_word_length': 3.846934274371416, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6629159', 'n_tokens_mistral': 3897, 'n_tokens_neox': 3645, 'n_words': 859}