Source: sg2d-tile.js

"use strict";

import SGModel from './libs/sg-model/sg-model.js';
import SG2DApplication from './sg2d-application.js';
import SG2DConsts from './sg2d-consts.js';
import SG2DUtils from './sg2d-utils.js';
import SG2DMath from './sg2d-math.js';
import SG2DClusters from './sg2d-clusters.js';
import SG2DBounds from './sg2d-bounds.js';
import SG2DSound from './sg2d-sound.js';
import SG2DDebugging from './sg2d-debugging.js';

/**
 * Тайл со спрайтами. Базовый класс: {@link SGModel}
 * @alias SG2D.Tile
 */
class SG2DTile extends SGModel {
	
	/**
	 * Конструктор
	 * @param {object} properties
	 * @param {object} [properties.texture] - Задаётся для основного спрайта
	 * @param {object} [properties.angle=0] - Задаётся для основного спрайта
	 * @param {object} [properties.anchor=0.5] - Задаётся для основного спрайта
	 * @param {object} [properties.scale=1] - Задаётся для основного спрайта
	 * @param {object} [properties.alpha=1] - Задаётся для основного спрайта
	 * @param {object} [properties.visible=true] - Задаётся для основного спрайта
	 * @param {object} [properties.zindex=0] - Задаётся для основного спрайта
	 * @param {object} [properties.layer] - Задаётся для основного спрайта
	 * @returns {SG2D.Tile}
	 */
	constructor(properties, thisProps, options) {
		super(...arguments);
	}
	
	initialize(properties, thisProps, options) {
		
		// Костыль, поскольку в JS нет возможности выполнить код в дочернем классе во время наследования от родительского класса
		this.constructor._prepareStaticConfigSprites();
		
		options = options || SGModel.OBJECT_EMPTY;
		
		this.bounds = (this.body && this.body.bounds ? new SG2DBounds(this.body.bounds) : new SG2DBounds());
		this.boundsCXY = new SG2DBounds();
		this.clusters = new Set();
		this.centerCluster = null;
		this.sprite = void 0; // основной спрайт, может быть не задан!
		this.sprites = void 0; // список спрайтов
		this.hasAnimations = false;
		
		if (! this.constructor.sprites) throw "Error 82423704! this.constructor.sprites must be filled!";
		
		this.sprites = SGModel.clone(this.constructor.sprites);
		if (this.sprites.main) this.sprite = this.sprites.main;
		if (this.sprite) {
			this.sprite.texture = this.properties.texture !== void 0 ? this.properties.texture : this.constructor.sprites.main.texture;
			for (var p in SG2DTile._defaultSpriteValues) {
				if (properties[p] !== void 0) this.sprite[p] = properties[p];
			}
			if (this.properties.layer !== void 0) {
				this.sprite.layer = this.properties.layer;
			}
		}
		
		SG2DUtils.objectForEach(this.sprites, sprite=>{
			sprite._texture = sprite.texture;
			if (sprite.animation) {
				sprite.animation._count = sprite.animation.start || 1;
				sprite.animation._sleep = 1;
				if (sprite.animation.onComplete) sprite.animation.onComplete = sprite.animation.onComplete.bind(this);
				this.hasAnimations = true;
				this._animationIndex = 0; // плитка может быть в нескольких кластерах
			}
		});
		
		SG2DClusters.tiles[this.properties.id] = this;
		SG2DClusters.tilesset.add(this);
		
		this._onGeometric();
		this.drawUndraw();
	}
	
	/**
	 * Own setter for texture property
	 * @private
	 */
	setTexture(value = void 0, options = SGModel.OBJECT_EMPTY, flags = 0) {
		if (SG2DConsts.ONLY_LOGIC) return;
		this._setTileProperty("texture", value, options, flags);
	}
	
	/**
	 * Own setter for position property
	 * @private
	 */
	setPosition(value = void 0, options = SGModel.OBJECT_EMPTY, flags = 0) {
		if (this.set("position", value, options, flags | SGModel.FLAG_IGNORE_OWN_SETTER)) {
			this._onGeometric();
			this.drawUndraw();
		}
	}
	
	/**
	 * Own setter for angle property
	 * @private
	 */
	setAngle(value = void 0, options = SGModel.OBJECT_EMPTY, flags = 0) {
		if (value !== void 0) value = SG2DMath.normalize_a(value, SG2DUtils.ifUndefined(options.precision, 1));
		if (this._setTileProperty("angle", value, options, flags)) {
			//this._onGeometric();
		}
	}
	
	/**
	 * Own setter for angle property
	 * @private
	 */
	setAnchor(value = void 0, options = SGModel.OBJECT_EMPTY, flags = 0) {
		this._setTileProperty("anchor", value, options, flags);
	}
	
	/**
	 * Own setter for scale property
	 * @private
	 */
	setScale(value = void 0, options = SGModel.OBJECT_EMPTY, flags = 0) {
		this._setTileProperty("scale", value, options, flags);
	}
	
	/**
	 * Own setter for visible property
	 * @private
	 */
	setAlpha(value = void 0, options = SGModel.OBJECT_EMPTY, flags = 0) {
		this._setTileProperty("alpha", value, options, flags);
	}
	
	/**
	 * Own setter for visible property
	 * @private
	 */
	setVisible(value = void 0, options = SGModel.OBJECT_EMPTY, flags = 0) {
		this._setTileProperty("visible", value, options, flags);
	}
	
	/**
	 * Own setter for visible property
	 * @private
	 */
	setZindex(value = void 0, options = SGModel.OBJECT_EMPTY, flags = 0) {
		this._setTileProperty("zindex", value, options, flags);
	}
	
	/** @private */
	_setTileProperty(name, value, options = SGModel.OBJECT_EMPTY, flags = 0) {
		this._spritesFromOptions(options);
		if (SG2DTile._spritesFromOptions.size) {
			let changed = false;
			for (var sprite of SG2DTile._spritesFromOptions) {
				if (! sprite) continue;
				if (sprite[name] !== value) {
					sprite[name] = value;
					changed = true;
					this.drawUndraw(sprite);
				}
			}
			return changed;
		} else {
			if (this.set(name, value, options, flags | SGModel.FLAG_IGNORE_OWN_SETTER)) {
				SG2DUtils.objectForEach(this.sprites, sprite=>{
					if (sprite.setter_flag !== SG2DTile.FLAG_ONLY_OPTIONS_SPRITE) sprite[name] = value;
				});
				this.drawUndraw();
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Для движущихся спрайтов проверяет, требуется ли рисование при попадании в камеру.
	 * @param {object} [sprite]
	 */
	drawUndraw(sprite = void 0) {
		if (! SG2DConsts.ONLY_LOGIC && SG2DApplication._initialized) {
			if (this.isInCamera()) {
				if (sprite) {
					this._pixiSprite(sprite);
				} else {
					for (var name in this.sprites) {
						this._pixiSprite(this.sprites[name]);
					}
					this.set("drawed", true);
				}
				return true;
			} else {
				this.removeSprites();
				return false;
			}
		}
		return void 0;
	}
	
	/**
	 * Проверить находится ли тайл в камере
	 * @returns {Boolean}
	 */
	isInCamera() {
		if (! this.clusters) debugger; // TODO DEL DEBUG
		for (var cluster of this.clusters) {
			if (cluster.drawed) return true;
		}
		return false;
	}
	
	// Чтобы обработать щелчок по экземпляру тайла, задайте этот метод
	// click(target, options) {...}
	
	/** @private */
	_pixiSprite(sprite) {
		if (SG2DApplication._initialized && this.constructor.noDraw === false) {
			var pixiSprite = sprite.pixiSprite;
			if (sprite.visible) {
				if (! pixiSprite) {
					let texture = this.updateSpriteTexture(sprite, sprite.animation && sprite.animation.running ? this.getAnimationTexture(sprite) : sprite.texture);
					pixiSprite = sprite.pixiSprite = new PIXI.Sprite(texture);
					pixiSprite.tile = this;
					SG2DApplication.drawSprite(pixiSprite, sprite.layer);
				}
			} else {
				if (pixiSprite) {
					SG2DApplication.removeSprite(sprite.pixiSprite);//, {children: true}); // TODO? Bush transform=null!!!=>Error in pixi.js
					delete sprite.pixiSprite;
				}
			}
			if (sprite.pixiSprite) {
				if (! sprite.animation || sprite.animation && ! sprite.animation.running) {
					this.updateSpriteTexture(sprite, sprite.texture);
				}
				pixiSprite.position.x = ~~this.properties.position.x;
				pixiSprite.position.y = ~~this.properties.position.y;
				if (typeof sprite.anchor === "number") pixiSprite.anchor.set(sprite.anchor); else pixiSprite.anchor.set(sprite.anchor.x, sprite.anchor.y);
				if (typeof sprite.scale === "number") pixiSprite.scale.set(sprite.scale); else pixiSprite.scale.set(sprite.scale.x, sprite.scale.y);
				pixiSprite.angle = sprite.angle;
				pixiSprite.zIndex = sprite.zindex;
				pixiSprite.alpha = sprite.alpha;
				pixiSprite.visible = sprite.visible;
			}
		}
	}
	
	/**
	 * @protected
	 */
	removeSprites() { // Переопределить, если есть сложный рендеринг
		if (this.properties.drawed) {
			for (var name in this.sprites) {
				var sprite = this.sprites[name];
				if (sprite.pixiSprite) {
					SG2DApplication.removeSprite(sprite.pixiSprite);//, {children: true}); // TODO? Bush transform=null!!!=>Error in pixi.js
					delete sprite.pixiSprite;
				}
				if (sprite.animation) {
					sprite.animation._count = 1;
					sprite.animation._sleep = 1;
					if (! sprite.animation.loop) {
						sprite._texture = sprite.texture;
						sprite.animation.running = false;
					}
				}
			}
			this.set("drawed", false);
		}
	}
	
	/**
	 * Начать анимацию
	 * @param {string|SG2D.Sprite} [name_or_sprite=void 0] Если не задано, то берётся спрайт по умолчанию (основной)
	 */
	startAnimation(name_or_sprite = void 0) {
		let sprite = this._checkSpriteAnimation(name_or_sprite);
		if (sprite) {
			sprite.animation.running = true;
			sprite.visible = true;
			sprite.animation._count = 1;
			sprite.animation._sleep = 1;
			if (this.isInCamera()) {
				this._pixiSprite(sprite);
			}
			this.updateSpriteTexture(sprite, this.getAnimationTexture(sprite));
		}
	}
	
	/**
	 * Прервать анимацию
	 * @param {string|SG2D.Sprite} [name_or_sprite=void 0] Если не задано, то берётся спрайт по умолчанию (основной)
	 */
	breakAnimation(name_or_sprite = void 0) {
		let sprite = this._checkSpriteAnimation(name_or_sprite);
		if (sprite) {
			sprite.animation.running = false;
			sprite.animation.visible = false;
			sprite.animation._count = 1;
			sprite.animation._sleep = 1;
			this.updateSpriteTexture(sprite, sprite.texture || sprite._texture);
			if (this.isInCamera()) {
				this._pixiSprite(sprite);
			}
		}
	}
	
	/**
	 * Приостановить анимацию
	 * @param {string|SG2D.Sprite} [name_or_sprite=void 0] Если не задано, то берётся спрайт по умолчанию (основной)
	 * @param {object} [options=void 0]
	 * @param {boolean}	[options.visible=void 0] Если значение задано, то спрайт отображается/скрывается
	 */
	stopAnimation(name_or_sprite = void 0, options = void 0) {
		let sprite = this._checkSpriteAnimation(name_or_sprite);
		if (sprite) {
			sprite.animation.running = false;
		}
		if (options && options.visible !== void 0) this.set("visible", options.visible, { sprite: sprite });
	}
	
	/**
	 * Возобновить анимацию
	 * @param {string|SG2D.Sprite} [name_or_sprite=void 0] Если не задано, то берётся спрайт по умолчанию (основной)
	 * @param {object} [options=void 0]
	 * @param {boolean}	[options.visible=void 0] Если значение задано, то спрайт отображается/скрывается
	 */
	resumeAnimation(name_or_sprite = void 0, options = void 0) {
		let sprite = this._checkSpriteAnimation(name_or_sprite);
		if (sprite) {
			sprite.animation.running = true;
		}
		if (options && options.visible !== void 0) this.set("visible", options.visible, { sprite: sprite });
	}
	
	/**
	 * Одна итерация анимации
	 * @param {string|SG2D.Sprite} [name_or_sprite=void 0] Если не задано, то берётся спрайт по умолчанию (основной)
	 * @param {number} [count=1]
	 */
	stepAnimation(name_or_sprite = void 0, count = 1) {
		let sprite = this._checkSpriteAnimation(name_or_sprite);
		if (sprite) {
			for (var i = 0; i < count; i++) {
				this.iterateAnimations(true, sprite);
			}
		}
	}
	
	/** @private */
	_checkSpriteAnimation(name_or_sprite) {
		if (this.hasAnimations) {
			name_or_sprite = name_or_sprite || this.sprite;
			let sprite = typeof name_or_sprite === "object" ? name_or_sprite : this.sprites[name_or_sprite];
			if (! sprite) throw "Error 730002! Animation sprite \"" + name_or_sprite + "\" not found!";
			let animation = sprite.animation;
			if (! animation) throw "Error 908720! Sprite \"" + sprite.name + "\" does not contain animation description!";
			return sprite;
		}
		return false;
	}
	
	getAnimationTexture(sprite) {
		return sprite.animation.basetexture + sprite.animation._count;
	}
	
	/** @protected */
	iterateAnimations(frame_index = true, sprite = void 0) {
		if (this.hasAnimations && (this._animationIndex < frame_index || frame_index === true)) {
			if (frame_index !== true) this._animationIndex = frame_index;
			if (sprite) {
				this._iterateAnimation(sprite);
			} else {
				SG2DUtils.objectForEach(this.sprites, this._iterateAnimation, this);
			}
		}
	}
	
	/** @private */
	_iterateAnimation(sprite) {
		var animation = sprite.animation;
		if (animation && animation.running) {
			animation._sleep++;
			if (animation._sleep > animation.sleep) {
				animation._sleep = 1;
				animation._count++;
				if (animation._count > animation.count) {
					animation._count = 1;
					if (! animation.loop) {
						sprite._texture = sprite.texture || sprite._texture;
						sprite.visible = false;
						animation.running = false;
						if (animation.onComplete) animation.onComplete(sprite);
						this._pixiSprite(sprite);
					}
				} else {
					sprite._texture = this.getAnimationTexture(sprite);
				}
				this.updateSpriteTexture(sprite);
			}
		}
	}
	
	/**
	 * @return {PIXI.Texture}
	 */
	updateSpriteTexture(sprite, _texture = void 0) {
		if (_texture !== void 0) sprite._texture = _texture;
		let sTexture = this.checkTexture(sprite._texture);
		let texture = PIXI.Texture.from(sTexture, {}, SG2DConsts.PIXI_TEXTURE_STRICT);
		if (sprite.pixiSprite) {
			if (sprite.pixiSprite.texture.textureCacheIds[0] !== sTexture) sprite.pixiSprite.texture = texture;
		}
		return texture;
	}
	
	/**
	 * @param {string} sTexture
	 * @return {string}
	 */
	checkTexture(sTexture) {
		if (! SG2DConsts.PIXI_TEXTURE_STRICT && ! PIXI.utils.TextureCache[sTexture]) {
			if (SG2DTile._textures_not_founded.indexOf(sTexture) === -1) {
				SG2DTile._textures_not_founded.push(sTexture);
				console.warn("Texture with the name \"" + sTexture + "\" was not found!"); debugger;
				if (! sTexture) debugger;
			}
			sTexture = SG2DConsts.TILE_404;
		}
		return sTexture;
	}
	
	/** @private */
	_spritesFromOptions(options = SGModel.OBJECT_EMPTY) {
		SG2DTile._spritesFromOptions.clear();
		if (options.sprite) {
			SG2DTile._spritesFromOptions.add(options.sprite);
		}
		if (options.sprites) {
			SG2DUtils.objectForEach(options.sprites, sprite=>SG2DTile._spritesFromOptions.add(sprite));
		}
	}
	
	/** @private */
	_onGeometric() {
		this._calcBoundsPX();
		this._calcCXY();
		this._calcClustersBody();
	}
	
	/** @private */
	_calcBoundsPX() {
		this.bounds.set(
			this.properties.position.x - SG2DConsts.CELLSIZEPIX05 + 0.5,
			this.properties.position.y - SG2DConsts.CELLSIZEPIX05 + 0.5,
			this.properties.position.x + SG2DConsts.CELLSIZEPIX05 - 0.5,
			this.properties.position.y + SG2DConsts.CELLSIZEPIX05 - 0.5
		);
	}
	
	/** @private */
	_calcCXY() {
		
		SG2DTile._point.x = SG2DUtils.PXtoCX(this.properties.position.x);
		SG2DTile._point.y = SG2DUtils.PXtoCX(this.properties.position.y);
		this.centerCluster = SG2DClusters.getClusterCXY(SG2DTile._point); // before this.set("cxy", ...) !
		
		this.set("cxy", SG2DTile._point, void 0, SGModel.FLAG_PREV_VALUE_CLONE);
		
		this.boundsCXY.set(
			SG2DUtils.PXtoCX(this.bounds.min.x),
			SG2DUtils.PXtoCX(this.bounds.min.y),
			SG2DUtils.PXtoCX(this.bounds.max.x),
			SG2DUtils.PXtoCX(this.bounds.max.y)
		);
	}
	
	/** @private */
	_calcClustersBody() {
		var cluster;
		// TODO: учесть то что объект может быть повернут и тогда некоторые кластера (угловые) можно выкинуть из расчетов, т.к. тело объекта не будет присутствовать там!
		for (cluster of this.clusters) {
			if (cluster.x < this.boundsCXY.min.x || cluster.x > this.boundsCXY.max.x || cluster.y < this.boundsCXY.min.y || cluster.y > this.boundsCXY.max.y) {
				this.clusters.delete(cluster);
				cluster.tiles.delete(this);
				if (this.constructor.isBody) cluster.bodies.delete(this);
			}
		}
		for (var y = this.boundsCXY.min.y; y <= this.boundsCXY.max.y; y++) {
			for (var x = this.boundsCXY.min.x; x <= this.boundsCXY.max.x; x++) {
				if (cluster = SG2DClusters.getCluster(x, y)) { // MatterJS позволяет микропроваливание объектов друг в друга, т.е. на границе карты объекты могут быть!
					this.clusters.add(cluster);
					cluster.tiles.add(this);
					if (this.constructor.isBody) cluster.bodies.add(this);
				}
			}
		}
	}
	
	/**
	 * Проиграть звук к тайлу, учитывается 2D-окружение и расстояние
	 * @param {string} code
	 */
	sound(code) {
		SG2DSound.play(code, this);
	}
	
	destroy() {
		if (SG2DConsts.ONLY_LOGIC) {
			// no code
		} else {
			this.removeSprites();
			if (SG2DConsts.DRAW_BODY_LINES) SG2DDebugging.undrawSG2DBodyLines(this);
		}
		
		for (var cluster of this.clusters) {
			cluster.tiles.delete(this);
			cluster.bodies.delete(this);
		}
		this.clusters.clear();
		SG2DClusters.tiles[this.properties.id] = null;
		SG2DClusters.tilesset.delete(this);
		super.destroy();
	}
}

SG2DTile.typeProperties = { // overriden with Object.assign(...)
	texture: SGModel.TYPE_STRING,
	position: SGModel.TYPE_OBJECT_NUMBERS,
	angle: SGModel.TYPE_NUMBER,
	anchor: SGModel.TYPE_VECTOR,
	scale: SGModel.TYPE_VECTOR,
	alpha: SGModel.TYPE_NUMBER,
	visible: SGModel.TYPE_BOOLEAN,
	zindex: SGModel.TYPE_NUMBER,
	cxy: SGModel.TYPE_OBJECT_NUMBERS
};

/**
 * Слой, в котором идёт отрисовка основного спрайта. Если равно undefined, используется слой по умолчанию (PIXI.Container).
 * @type {string}
 */
SG2DTile.layer = void 0;

/**
 * Текстура основного спрайта.
 * @type {string}
 * @example
// Пример описания простейшего тайла, где будет только один спрайт (основной), задействована только одна текстура и не будет анимаций
export class BlockSteel extends SG2D.TileBody {
	static texture = "elements/block-steel";
	static layer = "bodies";
	static zindex = 20;
}

// Пример описание тайла, где будет один спрайт (основной), но текстура задаётся при инициализации
export class Tree extends SG2D.Tile {
	static texture = "elements/trees/tree_";
	static layer = "trees";
	static zindex = 20;
	initialize(...args) {
		super.initialize(...args);
		let n = Math.floor(1 + Math.random() * 41);
		this.set("texture", Tree.texture + (n<10?"0":"") + n);
	}
}
 */
SG2DTile.texture = SG2DConsts.TILE_OVERRIDE; // overriden 

/**
 * Угол поворота основного спрайта.
 * @type {number} */
SG2DTile.angle = 0;

/**
 * Смещение основного спрайта.
 * @type {number} */
SG2DTile.anchor = 0.5;

/**
 * Масштабирование основного спрайта.
 * @type {number} */
SG2DTile.scale = 1;

/**
 * Прозрачность основного спрайта.
 * @type {number} */
SG2DTile.alpha = 1;

/**
 * Видимость основного спрайта.
 * @type {boolean} */
SG2DTile.visible = true;

/**
 * Основной z-index добавляется к z-index спрайтов, кроме спрайта с именем = "main"
 * @type {number}
 */
SG2DTile.zindex = 0;

/**
 * Конфиг спрайта в тайле.
* @typedef SG2D.Tile.SpriteConfig
* @type {object}
* @property {string} texture
* @property {(number|object)} [anchor=0.5]
* @property {number} [angle=0] - В градусах
* @property {(number|object)} [scale=1]
* @property {number} [zindex=0]
* @property {boolean} [visible=true]
* @property {string} [layer] - Код слоя
* @property {SG2D.Tile.AnimationConfig} [animation]
* @property {number} [setter_flag=0] - Флаг. Если задано SG2D.Tile.FLAG_ONLY_OPTIONS_SPRITE, то нужно передавать в options код или сам объект спрайта, например: this.set("angle", a, { sprite: this.sprites.smoke_shot });
*/

/**
 * Конфиг анимации для спрайта.
* @typedef SG2D.Tile.AnimationConfig
* @type {object}
* @property {string} basetexture
* @property {number} [start=1]
* @property {number} count
* @property {number} [sleep=1]
* @property {boolean} [running=false]
* @property {boolean} [loop=false]
* @property {function} [onComplete=void 0]
*/

/**
 * Описание спрайтов и анимаций в тайле в виде дерева.
 * @alias SG2D.Tile.sprites
 * @static
 * @type {Object.<string, SG2D.Tile.SpriteConfig>}
 * @example
// Базовый класс для тайлов с lifeband-полоской
export default class ObjectBaseLifeBand extends SG2D.TileBody {
	static layer = "bodies";
	static sprites = {
		lifeband_base: {
			texture: "ui/lifeband_base",
			anchor: { x: 0.5, y: -5 },
			zindex: 10,
			angle: 90,
			layer: "animations",
			setter_flag: SG2D.Tile.FLAG_ONLY_OPTIONS_SPRITE
		},
		lifeband_value: {
			textures: { friend: "ui/lifeband_green", enemy: "ui/lifeband_red" },
			texture: "ui/lifeband_green",
			scale: { x: 1, y: 1 },
			anchor: { x: 0.5, y: -5 },
			zindex: 11,
			angle: 90,
			layer: "animations",
			setter_flag: SG2D.Tile.FLAG_ONLY_OPTIONS_SPRITE
		}
	}
	//...
}

// Базовый класс для игроков
export default class Player extends ObjectBaseLifeBand {
	static sprites = SG2D.Model.defaults({
		platform: {
			texture: "objects/player-platform",
			zindex: 0,
			sprites: {
				turret: {
					texture: "objects/player-turret",
					anchor: { x: 0.15, y: 0.5 },
					zindex: 2,
					sprites: {
						smoke_shot: {
							texture: "objects/player-smoke-shot_1",
							layer: "animations",
							anchor: { x: -3.4, y: 0.5 },
							zindex: 1,
							visible: false,
							animation: {
								count: 6,
								sleep: 3,
								running: false,
								basetexture: "objects/player-smoke-shot_"
							},
							setter_flag: SG2D.Tile.FLAG_ONLY_OPTIONS_SPRITE
						}
					}
				}
			}
		},
		accelerator: {
			texture: "animations/accelerator_1",
			layer: "animations",
			anchor: { x: 2.7, y: 0.45 },
			zindex: 0,
			visible: false,
			animation: {
				count: 3,
				sleep: 3,
				running: false,
				basetexture: "animations/accelerator_"
			}
		},
		track_left: {
			texture: "objects/player-track_1",
			anchor: { x: -4, y: 2.7 },
			zindex: 1,
			animation: {
				count: 8,
				sleep: 2,
				running: false,
				basetexture: "objects/player-track_",
				loop: true
			}
		},
		track_right: {
			texture: "objects/player-track_1",
			anchor: { x: -4, y: -1.6 },
			zindex: 1,
			animation: {
				count: 8,
				sleep: 2,
				running: false,
				basetexture: "objects/player-track_",
				loop: true
			}
		}
	}, ObjectBaseLifeBand.sprites);
	//...
 }
 */
let SG2DTile_sprites = {};

/**
 * Описание анимации для основного спрайта
 * @alias SG2D.Tile.animation
 * @static
 * @type {SG2D.Tile.AnimationConfig}
 * @example
// Пример описания анимации для основного спрайта
export class Medikit extends SG2D.Tile {
	static texture = "objects/medikit100_1";
	static layer = "bodies";
	static animation = {
		count: 8,
		sleep: 3,
		running: false,
		basetexture: "objects/medikit100_",
		onComplete: function(sprite) {
			sprite.visible = true; // После выполнения анимации оставляем её видимой
		},
		loop: false
	};
	
	initialize(...args) {
		super.initialize(...args);
		this.scaleDir = 1;
	}
	
	iterateAnimations(...args) {
		this.set("angle", this.properties.angle + 1);
		this.set("scale", this.properties.scale +0.002 * this.scaleDir);
		if (this.properties.scale <= 0.6) this.scaleDir = 1;
		if (this.properties.scale >= 0.8) this.scaleDir = -1;
		
		if (! this.sprite.animation.running && Math.random() < 0.01) {
			this.startAnimation();
		}
		
		super.iterateAnimations(...args);
	}
}
 */
let SG2DTile_animation = {};

/**
 * @type {boolean}
 * @readonly
 */
SG2DTile.isTile = true;

/** @type {boolean} */
SG2DTile.isBody = false;

/** @type {boolean} */
SG2DTile.noDraw = false;

/**
 * Флаг для игнорирования спрайтом сеттера без указания спрайта в *options.<sprite|sprites>*. Флаг актуален для тайлов состоящих из нескольких спрайтов.
 * Например, для спрайта *this.sprites.platform* сеттер *this.set("angle", 125)* не применится, а *this.set("angle", 125, { sprite: this.sprites.platform })* применится!
 */
SG2DTile.FLAG_ONLY_OPTIONS_SPRITE = true;

// Чтобы обработать щелчок по тайлу, определите этот метод в классе тайла
// SG2DTile.click(target, options) {...}

SG2DTile._initPrevPosition = {x: -1, y: -1};

SG2DTile._point = {x: void 0, y: void 0};

SG2DTile.ownSetters = { // overriden with Object.assign(...)
	position: true,
	angle: true,
	anchor: true,
	scale: true,
	alpha: true,
	visible: true,
	zindex: true,
	texture: true
};

SG2DTile.defaultProperties = {
	id: void 0,
	position: {x: 0, y: 0},
	angle: 0,
	anchor: 0.5,
	scale: 1,
	alpha: 1,
	visible: true,
	zindex: 0,
	layer: void 0,
	cxy: {x: 0, y: 0},
	drawed: false
}

SG2DTile._defaultSpriteValues = {
	angle: 0,
	anchor: 0.5,
	scale: 1,
	alpha: 1,
	visible: true,
	zindex: 0
};

SG2DTile._prepareStaticConfigSprites = function() {

	if (this.hasOwnProperty("_spritesPrepared")) {
		return;
	} else {
		this._spritesPrepared = true;
	}

	if (this.hasOwnProperty("sprites")) {
		this.sprites = this._prepareMultipleSprites(this.sprites);
	} else {
		this.sprites = { main: this._prepareSpriteProperties({ name: "main" }, this) };
	}

	if (this.sprites.main) this.sprite = this.sprites.main;
	if (this.sprite) {
		if (this.animation !== void 0) {
			this.sprite.animation = SGModel.clone(this.animation);
		}
	}

	if (this.zindex !== void 0) {
		SG2DUtils.objectForEach(this.sprites, sprite=>{
			if (sprite.name !== "main") {
				sprite.zindex += this.zindex;
			}
		});
	}
}

SG2DTile. _prepareMultipleSprites = function(sprites, parentSprite = void 0) {
	for (var name in sprites) {
		const sprite = sprites[name];
		sprite.name = name;
		if (parentSprite) {
			sprite.parent = parentSprite.name;
			sprite.setter_flag = SG2DTile.FLAG_ONLY_OPTIONS_SPRITE;
		}
		this._prepareSpriteProperties(sprite);
		if (sprite.sprites) {
			sprites = {...sprites, ...this._prepareMultipleSprites(sprite.sprites, sprite)};
		}
	}
	return sprites;
}

SG2DTile._prepareSpriteProperties = function(dest, src = void 0) {
	if (! src) src = dest;
	dest.texture = typeof src.texture !== void 0 ? src.texture : SG2DConsts.TILE_OVERRIDE;
	for (var p in SG2DTile._defaultSpriteValues) {
		dest[p] = SG2DUtils.ifUndefined(src[p], SG2DTile._defaultSpriteValues[p]);
	}
	dest.layer = src.layer || this.layer;
	return dest;
}

SG2DTile._textures_not_founded = [];

SG2DTile._spritesFromOptions = new Set();

export default SG2DTile;