code:
/**
 * Coded by aimozg on 06.06.2017.
 */
package coc.view {
import classes.internals.LoggerFactory;
import classes.internals.Utils;
import flash.filters.DropShadowFilter;

import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.events.Event;
import flash.text.AntiAliasType;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.utils.Dictionary;
import flash.utils.setTimeout;

import mx.logging.ILogger;

public class Block extends Sprite {
	public static const ON_LAYOUT:String                       = 'coc$layout';
	private static const LOGGER:ILogger = LoggerFactory.getLogger(Block);
	/**
	 * Null layout. All elements' positions and sizes are NOT adjusted
	 */
	public static const LAYOUT_NONE:String = 'none';
	/**
	 * Common layout parameters:
	 * * padding{s,Horiz,Vert,Top,Right,Bottom,Left} - a distance from borders of this block to its elements
	 * * `ignoreHidden` - ignore all hidden (visible=false) elements. Default layout-dependent
	 * Common element hints:
	 * * `ignore` - ignore element
	 */
	/**
	 * Grid layout. Aligns elements in a grid of fixed cell size, fixed cell count
	 * Config:
	 * * `rows`, `cols` - number or rows and columns. Default 1
	 * * `setWidth`, `setHeight - should set width/height of elements. Default false
	 * Hints:
	 * * `row`, `col` - 2D position in grid. Defaults to "next cell"
	 * * `setWidth`, `setHeight - should set width/height of this element. Default to layout config
	 */
	public static const LAYOUT_GRID:String = 'grid';
	private function applyGridLayout():void {
		var config:Object        = _layoutConfig;
		var ignoreHidden:Boolean = 'ignoreHidden' in config ? config['ignoreHidden'] : false;
		var rows:Number          = config["rows"] || 1;
		var cols:Number          = config["cols"] || 1;
		var innerw:Number        = innerWidth;
		var innerh:Number        = innerHeight;
		var cellw:Number         = innerw / cols;
		var cellh:Number         = innerh / rows;
		var setcw:Boolean        = config['setWidth'];
		var setch:Boolean        = config['setHeight'];

		var row:int = 0;
		var col:int = 0;
		for (var ci:int = 0, cn:int = _container.numChildren; ci < cn; ci++) {
			var child:DisplayObject = _container.getChildAt(ci);
			var hint:Object         = _layoutHints[child] || {};
			if (hint['ignore'] || !child.visible && ignoreHidden) continue;
			var setw:Boolean = 'setWidth' in hint ? hint['setWidth'] : setcw;
			var seth:Boolean = 'setHeight' in hint ? hint['setHeight'] : setch;
			if ('row' in hint && 'col' in hint) {
				row = hint['row'];
				col = hint['col'];
			}
			child.x = paddingLeft + col * (paddingCenter + cellw);
			child.y = paddingTop + row * (paddingCenter + cellh);
			if (setw) child.width = cellw;
			if (seth) child.height = cellh;
			col = col + 1;
			if (col >= cols) {
				col = 0;
				row++;
			}
		}
	}
	/**
	 * Flow layout. Aligns size-providing elements in a row or column
	 * Config:
	 * `direction` - 'row'|'column'. Defaults to 'row'
	 * `gap` - Gap between neighbouring elements. Defaults to 2.
	 * `ignoreHidden` defaults to true
	 * Hints:
	 * `before`, `after` - Additional gap before/after that element. May be negative
	 */
	public static const LAYOUT_FLOW:String = 'flow';
	private function applyFlowLayout():void {
		var config:Object        = _layoutConfig;
		var ignoreHidden:Boolean = 'ignoreHidden' in config ? config['ignoreHidden'] : true;
		var dir:String           = config['direction'] || 'row';
		var gap:Number           = 'gap' in config ? config['gap'] : 2;
		var x:Number             = paddingLeft;
		var y:Number             = paddingTop;
		for (var ci:int = 0, cn:int = _container.numChildren; ci < cn; ci++) {
			var child:DisplayObject = _container.getChildAt(ci);
			var hint:Object         = _layoutHints[child] || {};
			if (hint['ignore'] || !child.visible && ignoreHidden) continue;
			var before:Number = 'before' in hint ? hint['before'] : 0;
			if (dir == 'column') {
				y += before;
			} else {
				x += before;
			}
			child.x          = x;
			child.y          = y;
			var after:Number = 'after' in hint ? hint['after'] : 0;
			if (dir == 'column') {
				y += child.height + after + gap;
			} else {
				x += child.width + after + gap;
			}
		}
	}

	private var _container:Sprite;
	private var _layoutHints:Dictionary = new Dictionary();
	private var _dirty:Boolean          = false;
	private var _layoutConfig:Object;
	private var explicitWidth:Number   = 0;
	private var explicitHeight:Number  = 0;

	public function Block(options:Object = null) {
		super();
		_layoutConfig = {type: LAYOUT_NONE};
		UIUtils.setProperties(this, options || {});
		_container = new Sprite();
		addChild(_container);
		addEventListener(Event.ADDED_TO_STAGE, addedToStage);
		invalidateLayout();
	}

	private function get xmin():Number {
		var xmin:Number = 0;
		if (_container) {
			for (var i:int = 0, n:int = numElements; i < n; i++) {
				xmin = Math.min(xmin, getElementAt(i).x);
			}
		}
		return xmin;
	}
	private function get ymin():Number {
		var ymin:Number = 0;
		if (_container) {
			for (var i:int = 0, n:int = numElements; i < n; i++) {
				ymin = Math.min(ymin, getElementAt(i).y);
			}
		}
		return ymin;
	}
	override public function get width():Number {
		if (explicitWidth) return explicitWidth;
		return super.width-xmin;
	}
	override public function get height():Number {
		if (explicitHeight) return explicitHeight;
		return super.height-ymin;
	}
	override public function set width(value:Number):void {
		if (width != value) {
			explicitWidth = value;
			resize();
		}
	}
	override public function set height(value:Number):void {
		if (height != value) {
			explicitHeight = value;
			resize();
		}
	}
	private function resize():void {
		if (width>0 || height>0) {
			graphics.clear();
			graphics.beginFill(0, 0);
			graphics.drawRect(0, 0, width, height);
			graphics.endFill();
		}
		if (width && height) {
			super.width  = width+Math.max(0,-xmin);
			super.height = height+Math.max(0,-ymin);
		}
	}
	public function get layoutConfig():Object {
		return _layoutConfig;
	}

	public function set layoutConfig(value:Object):void {
		_layoutConfig = value;
		invalidateLayout();
	}
	public function get innerWidth():Number {
		return Math.max(0, (width||explicitWidth) - paddingLeft - paddingRight);
	}
	public function get innerHeight():Number {
		return Math.max(0, (height||explicitHeight) - paddingTop - paddingBottom);
	}
	protected function addedToStage(e:Event):void {
	}

	public function addElement(e:DisplayObject, hint:Object = null):DisplayObject {
		_container.addChild(e);
		layElement(e, hint);
		return e;
	}

	public function addElementAt(e:DisplayObject, index:int, hint:Object = null):DisplayObject {
		_container.addChildAt(e, index);
		layElement(e, hint);
		return e;
	}

	public function layElement(e:DisplayObject, hint:Object):Block {
		_layoutHints[e] = hint;
		invalidateLayout();
		return this;
	}

	public function getElementIndex(e:DisplayObject):int {
		return _container.getChildIndex(e);
	}

	public function getElementByName(name:String):DisplayObject {
		return _container.getChildByName(name);
	}
	public function getElementAt(index:int):DisplayObject {
		return _container.getChildAt(index);
	}
	public function get numElements():int {
		return _container.numChildren;
	}

	public function removeElement(e:DisplayObject):void {
		_container.removeChild(e);
		delete _layoutHints[e];
		invalidateLayout();
	}

	public function invalidateLayout():void {
		if (!_dirty) {
			_dirty = true;
			setTimeout(maybeDoLayout, 0);
		}
	}

	public function get paddingTop():Number {
		var config:Object = _layoutConfig;
		if ('paddingTop' in config) return config['paddingTop'];
		if ('paddingVert' in config) return config['paddingVert'];
		if ('padding' in config) return config['padding'];
		return 0;
	}
	public function get paddingRight():Number {
		var config:Object = _layoutConfig;
		if ('paddingRight' in config) return config['paddingRight'];
		if ('paddingHoriz' in config) return config['paddingHoriz'];
		if ('padding' in config) return config['padding'];
		return 0;
	}
	public function get paddingBottom():Number {
		var config:Object = _layoutConfig;
		if ('paddingBottom' in config) return config['paddingBottom'];
		if ('paddingVert' in config) return config['paddingVert'];
		if ('padding' in config) return config['padding'];
		return 0;
	}
	public function get paddingLeft():Number {
		var config:Object = _layoutConfig;
		if ('paddingLeft' in config) return config['paddingLeft'];
		if ('paddingHoriz' in config) return config['paddingHoriz'];
		if ('padding' in config) return config['padding'];
		return 0;
	}
	public function get paddingCenter():Number {
		var config:Object = _layoutConfig;
		if ('paddingCenter' in config) return config['paddingCenter'];
		return 0;
	}
	public function doLayout():void {
		_dirty          = false;
		var type:String = _layoutConfig["type"];
		switch (type) {
			case "grid":
				applyGridLayout();
				break;
			case "flow":
				applyFlowLayout();
				break;
			case "none":
				break;
			default:
				LOGGER.warn("Unknown layout config type ", type);
				break;
		}
		dispatchEvent(new Event(ON_LAYOUT, true, true));
	}

	protected function maybeDoLayout():void {
		if (_dirty) doLayout();
	}

	public function applyShadow():void {
		if (this.filters.length > 0) {
			return;
		}

		var dropShadow:DropShadowFilter = new DropShadowFilter();
		_container.filters.push(dropShadow);
	}

	/////////////////
	// Helper methods
	/////////////////

	public function addBitmapDataSprite(options:Object, hint:Object = null):BitmapDataSprite {
		var e:BitmapDataSprite = new BitmapDataSprite(options);
		addElement(e, hint);
		return e;
	}
	public function addTextField(options:Object, hint:Object = null):TextField {
		var e:TextField = new TextField();
		e.antiAliasType = AntiAliasType.ADVANCED;
		if ('defaultTextFormat' in options) {
			e.defaultTextFormat = UIUtils.convertTextFormat(options['defaultTextFormat']);
		}
		UIUtils.setProperties(e, options, {
			defaultTextFormat: UIUtils.convertTextFormat,
			background: UIUtils.convertColor
		});
		if (!('mouseEnabled' in options) && options['type'] != 'input') e.mouseEnabled = false;
		if (!('width' in options || 'height' in options || 'autoSize' in options)) {
			e.autoSize = TextFieldAutoSize.LEFT;
		}
		addElement(e, hint);
		return e;
	}

}
}


code:
/**
 * Coded by aimozg on 02.12.2017.
 */
package coc.view {
import flash.display.MovieClip;
import flash.display.Sprite;

public class BoundClip extends MovieClip{
	public static var nextContent:Sprite;
	public function BoundClip() {
		if (nextContent != null) {
			addChild(nextContent);
		}
	}

}
}

license: unknown


code:
/**
 * Coded by aimozg on 10.07.2017.
 */
package coc.view {
import classes.internals.LoggerFactory;

import coc.view.charview.CharViewCompiler;
import coc.view.charview.CharViewContext;
import coc.view.charview.CharViewSprite;
import coc.view.charview.Palette;
import coc.view.charview.EvalPaletteProperty;
import coc.view.charview.PaletteProperty;
import coc.view.composite.CompositeImage;
import coc.view.composite.SimpleKeyColorProvider;
import coc.xlogic.Statement;
import coc.xlogic.StmtList;

import flash.display.BitmapData;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Point;
import flash.geom.Rectangle;

import mx.logging.ILogger;

public class CharView extends Sprite {
	private static const LOGGER:ILogger = LoggerFactory.getLogger(CharView);
	private var loading:Boolean;
	private var sprites:Object = {}; // spritesheet/spritemap -> CharViewSprite
	public var composite:CompositeImage;
	private var ss_total:int;
	private var ss_loaded:int;
	private var file_total:int;
	private var file_loaded:int;
	private var _originX:int;
	private var _originY:int;
	private var _width:uint;
	private var _height:uint;
	private var scale:Number;
	private var pendingRedraw:Boolean;
	private var loaderLocation:String;
	private var parts:Statement;
	private var _palette:Palette;

	public function get palette():Palette {
		return _palette;
	}
	public function CharView() {
		clearAll();
	}
	/**
	 * @param location "external" or "internal"
	 */
	public function reload(location:String = "external"):void {
		loaderLocation = location;
		if (loading) return;
		try {
			loading = true;
			clearAll();
			if (loaderLocation == "external") LOGGER.info("loading XML res/model.xml");
			CoCLoader.loadText("res/model.xml", function (success:Boolean, result:String, e:Event):void {
				if (success) {
					init(XML(result));
				} else {
					LOGGER.error("XML file not found: " + e);
					loading = false;
				}
			}, loaderLocation);
		} catch (e:Error) {
			loading = false;
			LOGGER.error("[ERROR]\n" + e.getStackTrace());
		}
	}
	private function clearAll():void {
		this.sprites       = {};
		this.composite     = null;
		this.ss_total      = 0;
		this.ss_loaded     = 0;
		this.file_total    = 0;
		this.file_loaded   = 0;
		this._width        = 180;
		this._height       = 220;
		this.scale         = 1;
		this.pendingRedraw = false;
		this.parts         = new StmtList();
		clearSprite();
	}
	private function clearSprite():void {
		var g:Graphics = graphics;
		g.clear();
		g.beginFill(0, 0);
		g.drawRect(0, 0, _width, _height);
		g.endFill();
	}
	private function init(xml:XML):void {
		_width    = xml.@width;
		_height   = xml.@height;
		_originX  = xml.@originX || 0;
		_originY  = xml.@originY || 0;
		composite = new CompositeImage(_width, _height);
		ss_loaded = 0;
		ss_total  = -1;
		/**/
		loadPalette(xml);
		var compiler:CharViewCompiler = new CharViewCompiler(this);
		this.parts = compiler.compileXMLList(xml.logic.children());
		var n:int  = 0;
		var item:XML;
		for each(item in xml.spritesheet) {
			n++;
			loadSpritesheet(xml, item);
		}
		for each(item in xml.spritemap) {
			n++;
			loadSpritemap(xml, item);
		}
		ss_total = n;
		if (n == 0) loadLayers(xml);
		var g:Graphics = graphics;
		clearSprite();
		scale       = parseFloat(xml.@scale);
		this.scaleX = scale;
		this.scaleY = scale;
		loading     = false;
		if (pendingRedraw) redraw();
	}
	private function loadPalette(xml:XML):void {
		_palette                 = new Palette();
		for each (var xpal:XML in xml.palettes.palette) {
			var lookups:Object = {};
			for each (var color:XML in xpal.color) {
				lookups[color.@name.toString()] = color.text().toString();
			}
			_palette.addLookups(xpal.@name.toString(),lookups);
		}
		for each (var prop:XML in xml.properties.property) {
			var propname:String = prop.@name.toString();

			var pp:PaletteProperty;
			var defaultt:uint = Color.convertColor(prop.@default.toString());
			var lookupNames:* = prop.@palette.toString().split(',');
			if ('@src' in prop) {
				pp = new EvalPaletteProperty(_palette,propname,defaultt,lookupNames,prop.@src.toString());
			} else {
				pp = new PaletteProperty(_palette,propname,defaultt,lookupNames);
			}
			_palette.addPaletteProperty(pp);
		}
		for each (var key:XML in xml.colorkeys.key) {
			var src:uint    = Color.convertColor(key.@src.toString());
			var base:String = key.@base.toString();
			var tf:String   = key.@transform.toString() || "";
			_palette.addKeyColor(src, base, tf);
		}
	}
	public function lookupColorValue(layername:String, propname:String, colorname:String):uint {
		return _palette.lookupColor(layername, propname, colorname);
	}
	private function loadLayers(xml:XML):void {
		file_loaded = 0;
		var item:XML;
		var n:int   = 0;
		file_total  = -1;
		for each(item in xml.layers..layer) {
			var lpfx:String = item.@name + "/";
			for (var sname:String in sprites) {
				if (sname.indexOf(lpfx) == 0) {
					var sprite:CharViewSprite = sprites[sname];
					composite.addLayer(sname, sprite.bmp,
							sprite.dx - _originX, sprite.dy - _originY, false);
				}
			}
		}
		file_total = n;
		if (pendingRedraw) redraw();
	}
	private var _character:Object = {};
	public function setCharacter(value:Object):void {
		_character = value;
	}
	public function redraw():void {
		if (file_total == 0 && ss_total == 0 && !loading) {
			reload();
		}
		pendingRedraw = true;
		if (ss_loaded != ss_total || file_loaded != file_total || (ss_total + file_total) == 0) {
			return;
		}
		pendingRedraw = false;

		// Mark visible layers
		composite.hideAll();
		parts.execute(new CharViewContext(this,_character));

		_palette.character   = _character;
		var bd:BitmapData    = composite.draw(_palette);
		var g:Graphics       = graphics;
		g.clear();
		g.beginBitmapFill(bd);
		g.drawRect(0, 0, _width, _height);
		g.endFill();
		this.scaleX = scale;
		this.scaleY = scale;
	}
	private function loadSpritemap(xml:XML, sm:XML):void {
		const filename:String = sm.@file;
		var path:String       = xml.@dir + filename;
		if (loaderLocation == "external") LOGGER.info('loading spritemap ' + path);
		CoCLoader.loadImage(path, function (success:Boolean, result:BitmapData, e:Event):void {
			if (!success) {
				LOGGER.error("Spritemap file not found: " + e);
				ss_loaded++;
				if (pendingRedraw) redraw();
				return;
			}
			for each (var cell:XML in sm.cell) {
				var rect:/*String*/Array = cell.@rect.toString().match(/^(\d+),(\d+),(\d+),(\d+)$/);
				var x:int                = rect ? int(rect[1]) : cell.@x;
				var y:int                = rect ? int(rect[2]) : cell.@y;
				var w:int                = rect ? int(rect[3]) : cell.@w;
				var h:int                = rect ? int(rect[4]) : cell.@h;
				var f:String             = cell.@name;
				var dx:int               = cell.@dx;
				var dy:int               = cell.@dy;
				var bd:BitmapData        = new BitmapData(w, h, true, 0);
				bd.copyPixels(result, new Rectangle(x, y, w, h), new Point(0, 0));
				sprites[f] = new CharViewSprite(bd, dx, dy);
			}
			ss_loaded++;
			if (ss_loaded == ss_total) loadLayers(xml);
		}, loaderLocation);
	}
	private function loadSpritesheet(xml:XML, ss:XML):void {
		const filename:String = ss.@file;
		const cellwidth:int   = ss.@cellwidth;
		const cellheight:int  = ss.@cellheight;
		var path:String       = xml.@dir + filename;
		if (loaderLocation == "external") LOGGER.info('loading spritesheet ' + path);
		CoCLoader.loadImage(path, function (success:Boolean, result:BitmapData, e:Event):void {
			if (!success) {
				LOGGER.error("Spritesheet file not found: " + e);
				ss_loaded++;
				if (pendingRedraw) redraw();
				return;
			}
			var y:int = 0;
			for each (var row:XML in ss.row) {
				var x:int                 = 0;
				var files:/*String*/Array = row.text().toString().split(",");
				for each (var f:String in files) {
					if (f) {
						var bd:BitmapData = new BitmapData(cellwidth, cellheight, true, 0);
						bd.copyPixels(result, new Rectangle(x, y, cellwidth, cellheight), new Point(0, 0));
						sprites[f] = new CharViewSprite(bd, 0, 0);
					}
					x += cellwidth;
				}
				y += cellheight;
			}
			ss_loaded++;
			if (ss_loaded == ss_total) loadLayers(xml);
		}, loaderLocation);
	}

}
}


code:
package coc.view {

	/****
		coc.view.CoCButton

		note that although this stores its current tool tip text,
		it does not display the text.  That is taken care of
		by whoever owns this.

		The mouse event handlers are public to facilitate reaction to
		keyboard events.
	****/

	import classes.ItemType;
	import classes.internals.Utils;
	import flash.events.MouseEvent;
	import flash.geom.ColorTransform;
	import flash.text.Font;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;
	import flash.system.Capabilities;
	import classes.GlobalFlags.kGAMECLASS;
	import classes.lists.Gender;

	public class CoCButton extends Block {

		[Embed(source='../../../res/ui/Shrewsbury-Titling_Bold.ttf',
				advancedAntiAliasing='true',
				fontName='ShrewsburyTitlingBold',
				embedAsCFF='false')]
		private static const BUTTON_LABEL_FONT:Class;
		public static const BUTTON_LABEL_FONT_NAME:String = (new BUTTON_LABEL_FONT() as Font).fontName;
		private static const BTN_AVG_RED:int = 152;
		private static const BTN_AVG_GREEN:int = 133;
		private static const BTN_AVG_BLUE:int = 116;

		private var _labelField:TextField,
					_backgroundGraphic:BitmapDataSprite,
					_enabled:Boolean = true,
				_callback:Function = null,
				_preCallback:Function = null;

		public var toolTipHeader:String,
				   toolTipText:String,
				   position:int;

		/**
		 * @param options  enabled, labelText, bitmapClass, callback
		 */
		public function CoCButton(options:Object = null) {
			super();
			initButton(options);
		}

		/**
		 * Extracted constructor to make use of the JIT compiler.
		 *
		 * @param	options See constructor.
		 */
		private function initButton(options:Object):void
		{
			_backgroundGraphic = addBitmapDataSprite({
				stretch: true,
				width  : MainView.BTN_W,
				height : MainView.BTN_H
			});
			_labelField        = addTextField({
				embedFonts       : true,
				//autoSize         : TextFieldAutoSize.CENTER,
				width            : MainView.BTN_W,
				height           : MainView.BTN_H - 8,
				x                : 0,
				y                : 8,
				defaultTextFormat: {
					font : BUTTON_LABEL_FONT_NAME,
					size : 18,
					align: 'center'
				}
			});

			this.mouseChildren = true;
			this.buttonMode    = true;
			this.visible       = true;
			UIUtils.setProperties(this, options);
			if (this.width < 130) { //A workaround for squashed text on narrower buttons.
				this._labelField.x = 0;
				this._labelField.width = this.width;
				this._labelField.scaleX = (MainView.BTN_W / this._labelField.width);
			}

			this.addEventListener(MouseEvent.ROLL_OVER, this.hover);
			this.addEventListener(MouseEvent.ROLL_OUT, this.dim);
			this.addEventListener(MouseEvent.CLICK, this.click);
		}

		//////// Mouse Events... ////////

		public function hover(event:MouseEvent = null):void {
			if (this._backgroundGraphic)
				this._backgroundGraphic.alpha = enabled ? 0.5 : 0.4;
		}

		public function dim(event:MouseEvent = null):void {
			if (this._backgroundGraphic)
				this._backgroundGraphic.alpha = enabled ? 1 : 0.4;
		}

		public function click(event:MouseEvent = null):void {
		if (!this.enabled) return;
		if (this._preCallback != null)
			this._preCallback(this);
			if (this._callback != null)
				this._callback();
		}

		//////// Getters and Setters ////////

		public function get enabled():Boolean {
			return _enabled;
		}

		public function set enabled(value:Boolean):void {
			_enabled                      = value;
			this._labelField.alpha        = value ? 1 : 0.4;
			this._backgroundGraphic.alpha = value ? 1 : 0.4;
		}

		public function get labelText():String {
			return this._labelField.text;
		}

		public function set labelText(value:String):void {
			this._labelField.text = value;
		}

		public function set bitmapClass(value:Class):void {
			_backgroundGraphic.bitmapClass = value;
		}

		public function get bitmapClass():Class {
			return null;
		}

		public function get callback():Function {
			return this._callback;
		}

		public function set callback(value:Function):void {
			this._callback = value;
		}

		public function get preCallback():Function {
			return _preCallback;
		}

		public function set preCallback(value:Function):void {
			_preCallback = value;
		}
		//////////// Builder functions
		/**
		 * Setup (text, callback, tooltip) and show enabled button. Removes all previously set options
		 * @return this
		 */
		public function show(text:String, callback:Function, toolTipText:String = "", toolTipHeader:String = ""):CoCButton {
			this.resetColor();
			this.labelText     = text;
			this.callback      = callback;
			hint(toolTipText,toolTipHeader);
			this.visible       = true;
			this.enabled       = true;
			this.alpha         = 1;
			return this;
		}
		/**
		 * Setup (text, tooltip) and show disabled button. Removes all previously set options
		 * @return this
		 */
		public function showDisabled(text:String,toolTipText:String="",toolTipHeader:String=""):CoCButton {
			this.resetColor();
			this.labelText     = text;
			this.callback      = null;
			hint(toolTipText,toolTipHeader);
			this.visible       = true;
			this.enabled       = false;
			this.alpha         = 1;
			return this;
		}
		/**
		 * Set text and tooltip. Don't change callback, enabled, visibility
		 * @return this
		 */
		public function text(text:String,toolTipText:String = "",toolTipHeader:String=""):CoCButton {
			this.labelText = text;
			hint(toolTipText,toolTipHeader);
			return this;
		}
		/**
		 * Set tooltip only. Don't change text, callback, enabled, visibility
		 * @return this
		 */
		public function hint(toolTipText:String = "",toolTipHeader:String=""):CoCButton {
			this.toolTipText   = toolTipText   || getToolTipText(this.labelText);
			this.toolTipHeader = toolTipHeader || getToolTipHeader(this.labelText);
			return this;
		}
		/**
		 * Disable if condition is true, optionally change tooltip. Does not un-hide button.
		 * @return this
		 */
		public function disableIf(condition:Boolean, toolTipText:String=null):CoCButton {
			if (condition) {
				enabled = false;
				if (toolTipText !== null) this.toolTipText = toolTipText;
			}
			return this;
		}
		/**
		 * Disable, optionally change tooltip. Does not un-hide button.
		 * @return this
		 */
		public function disable(toolTipText:String=null):CoCButton {
			enabled = false;
			if (toolTipText!==null) this.toolTipText = toolTipText;
			return this;
		}
		/**
		 * Enable, optionally change tooltip. Does not un-hide button.
		 * @return this
		 */
		public function enable(toolTipText:String=null):CoCButton {
			enabled = true;
			if (toolTipText!==null) this.toolTipText = toolTipText;
			return this;
		}
		/**
		 * Disable if condition is true, otherwise enable, and optionally change tooltip. Does not un-hide button.
		 * @return this
		 */
		public function disableEnable(condition:Boolean, toolTipText:String=null):CoCButton {
			enabled = !condition;
			if (toolTipText !== null) this.toolTipText = condition?toolTipText:this.toolTipText;
			return this;
		}
		/**
		 * Takes a required gender and disables the button if the player doesn't have the proper equipment or sufficient lust.
		 */
		public function sexButton(gender:int = 0, lust:Boolean = true):CoCButton {
			if (lust) {
				this.enabled = kGAMECLASS.player.lust >= 33;
				if (!this.enabled) this.toolTipText = "You are not aroused enough to have sex.";
			}
			if (gender > 0) {
				this.enabled &&= (gender & kGAMECLASS.player.gender) == gender;
				if (!this.enabled) this.toolTipText = "This scene requires "+["ERROR","a cock","a vagina","being a herm"][gender]+" and sufficient lust.";
			}
			else if (gender < 0) {
				this.enabled &&= kGAMECLASS.player.gender != Gender.NONE;
				if (!this.enabled) this.toolTipText = "This scene requires genitals and sufficient lust.";
			}
			return this;
		}
		/**
		 * Set callback to fn(...args)
		 * @return this
		 */
		public function call(fn:Function,...args:Array):CoCButton {
			this.callback = Utils.curry.apply(fn,args);
			return this;
		}
		/**
		 * Hide the button
		 * @return this
		 */
		public function hide():CoCButton {
			visible = false;
			return this;
		}

		public static function getToolTipHeader(buttonText:String):String
		{
			var toolTipHeader:String;

			if (buttonText.indexOf(" x") !== -1)
			{
				buttonText = buttonText.split(" x")[0];
			}

			//Set tooltip header to button.
			if (toolTipHeader === null) {
				toolTipHeader = buttonText;
			}

			return toolTipHeader;
		}

		// Returns a string or undefined.
		public static function getToolTipText(buttonText:String):String
		{
			var toolTipText :String;

			buttonText = buttonText || '';

			// TODO: Remove those code eventually.
			if (buttonText.indexOf(" x") !== -1)
			{
				buttonText = buttonText.split(" x")[0];
			}

			return toolTipText;
		}

		public function getColorString():String {
			var str:String = "";
			str += Math.round(_backgroundGraphic.transform.colorTransform.redMultiplier*100)/100;
			str += ", "
			str += Math.round(_backgroundGraphic.transform.colorTransform.greenMultiplier*100)/100;
			str += ", "
			str += Math.round(_backgroundGraphic.transform.colorTransform.blueMultiplier*100)/100;
			return str;
		}

		public function resetColor():CoCButton {
			var newColor:ColorTransform = new ColorTransform();
			//Preserve alpha
			var alpha:Number = _backgroundGraphic.alpha;
			newColor.alphaMultiplier = alpha;
			_backgroundGraphic.transform.colorTransform = newColor;
			return this;
		}

		/**
		 * Recolor the button
		 * multipliers: An array of three numbers, for red/green/blue values.
		 * If absolute == true, multipliers is interpreted as absolute RGB values, and multipliers are calculated to bring the button to approximately those values
		 * if absolute == false, multipliers is interpreted as multipliers to the base RGB values.
		 */
		public function colorize(multipliers:Array, absolute:Boolean = false):CoCButton {
			var alpha:Number = _backgroundGraphic.alpha;
			var red:Number = parseFloat(multipliers[0]);
			var green:Number = parseFloat(multipliers[1]);
			var blue:Number = parseFloat(multipliers[2]);

			var newColor:ColorTransform = new ColorTransform();
			if (absolute) {
				//
				newColor.redMultiplier = red/BTN_AVG_RED;
				newColor.greenMultiplier = green/BTN_AVG_GREEN;
				newColor.blueMultiplier = blue/BTN_AVG_BLUE;
			}
			else {
				newColor.redMultiplier = red;
				newColor.greenMultiplier = green;
				newColor.blueMultiplier = blue;
			}
			newColor.alphaMultiplier = alpha;
			_backgroundGraphic.transform.colorTransform = newColor;
			return this;
		}
	}
}


code:
/**
 * Coded by aimozg on 23.07.2017.
 */
package coc.view {
import classes.internals.LoggerFactory;

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.utils.describeType;
import flash.utils.setTimeout;

import mx.logging.ILogger;

/**
 *
 */
public class CoCLoader {
	private static const LOGGER:ILogger = LoggerFactory.getLogger(CoCLoader);
	public function CoCLoader() {
	}
	// [path:String]=>String
	private static var TEXT_BUNDLE:Object  = {};

	[Embed(source="../../../res/model.xml", mimeType="application/octet-stream")]
	public static const BUNDLE_RES_MODEL_XML:Class;

	public static function bundleText(key:String,c:Class):void {
		if (c) TEXT_BUNDLE[key] = new c();
	}

	// [path:String]=>BitmapData
	private static var IMAGE_BUNDLE:Object = {};

//	[Embed(source="../../../res/char1.png", mimeType="image/png")]
//	public static const BUNDLE_RES_CHAR1_PNG:Class;

	[Embed(source="../../../res/charview/body.png", mimeType="image/png")]
	public static const BUNDLE_RES_CHARVIEW_BODY_PNG:Class;

	[Embed(source="../../../res/charview/extra.png", mimeType="image/png")]
	public static const BUNDLE_RES_CHARVIEW_EXTRA_PNG:Class;

	[Embed(source="../../../res/charview/hair.png", mimeType="image/png")]
	public static const BUNDLE_RES_CHARVIEW_HAIR_PNG:Class;

	[Embed(source="../../../res/charview/head.png", mimeType="image/png")]
	public static const BUNDLE_RES_CHARVIEW_HEAD_PNG:Class;

	[Embed(source="../../../res/charview/lewd.png", mimeType="image/png")]
	public static const BUNDLE_RES_CHARVIEW_LEWD_PNG:Class;

	[Embed(source="../../../res/charview/tails.png", mimeType="image/png")]
	public static const BUNDLE_RES_CHARVIEW_TAILS_PNG:Class;

	[Embed(source="../../../res/charview/wings.png", mimeType="image/png")]
	public static const BUNDLE_RES_CHARVIEW_WINGS_PNG:Class;

	public static function bundleImage(key:String, c:Class):void {
		var o:BitmapData = c ? ((new c() as Bitmap).bitmapData) : null;
		if (o) IMAGE_BUNDLE[key] = o;
	}
	/**
	 * @param path
	 * @param callback Function (success:Boollean, result:*,event:Event):*
	 * where result is String or Error
	 * @param location "external", "internal"
	 */
	public static function loadText(path:String, callback:Function, location:String = "external"):void {
		function orLocal(e:Event):void {
			if (path in TEXT_BUNDLE) {
				setTimeout(callback, 0,true, TEXT_BUNDLE[path], new Event("complete"));
			} else {
				setTimeout(callback, 0,false, null, e);
			}
		}
		if (path.indexOf("./") == 0) path = path.slice(2);
		switch (location) {
			case "internal":
				orLocal(new ErrorEvent("error", false, false,
						"Internal resource " + path + "not found"));
				break;
			case "external":
				var loader:URLLoader = new URLLoader();
				loader.addEventListener(Event.COMPLETE, function (e:Event):void {
					try {
						LOGGER.info("Loaded external "+path);
						TEXT_BUNDLE[path] = loader.data;
					} catch (e:Error) {
						LOGGER.warn(e.name+" loading external "+path+": "+e.message);
						orLocal(new ErrorEvent("error",false,false,e.message));
						return;
					}
					callback(true, loader.data, e);
				});
				var req:URLRequest = new URLRequest(path);
				loader.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
					LOGGER.warn(e.type+" loading external "+path+": "+e.toString());
					orLocal(e);
				});
				try {
					loader.load(req);
				} catch (e:Error) {
					LOGGER.warn(e.name+" loading external "+path+": "+e.message);
					orLocal(new ErrorEvent("error",false,false,e.message));
				}
				break;
			default:
				throw new Error("Incorrect location " + location);
		}

	}
	/**
	 * @param path
	 * @param callback Function (success:Boollean, result:BitmapData, e:Event):*
	 * @param location "external", "internal"
	 */
	public static function loadImage(path:String, callback:Function, location:String = "external"):void {
		function orLocal(e:Event):void {
			if (path in IMAGE_BUNDLE) {
				setTimeout(callback, 0,true, IMAGE_BUNDLE[path], new Event("complete"));
			} else {
				setTimeout(callback, 0,false, null, e);
			}
		}
		if (path.indexOf("./") == 0) path = path.slice(2);
		switch (location) {
			case "internal":
				orLocal(new ErrorEvent("error", false, false,
						"Internal resource " + path + "not found"));
				break;
			case "external":
				var loader:Loader = new Loader();
				var cli:LoaderInfo = loader.contentLoaderInfo;
				cli.addEventListener(Event.COMPLETE, function (e:Event):void {
					var bmp:Bitmap = null;
					try {
						bmp = cli.content as Bitmap;
					} catch (e:Error) {
						LOGGER.warn(e.name+" loading external "+path+": "+e.message);
						orLocal(new ErrorEvent("error",false,false,e.message));
						return;
					}
					if (bmp) {
						LOGGER.info("Loaded external "+path);
						IMAGE_BUNDLE[path] = bmp.bitmapData;
						callback(true, bmp.bitmapData, e);
					} else {
						LOGGER.warn("Not found external "+path);
						callback(false, null, e);
					}
				});
				cli.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
					LOGGER.warn(e.type+" loading external "+path+": "+e.toString());
					orLocal(e);
				});
				try {
					loader.load(new URLRequest(path));
				} catch (e:Error) {
					LOGGER.warn(e.name+" loading external "+path+": "+e.message);
					orLocal(new ErrorEvent("error",false,false,e.message));
				}
				break;
			default:
				throw new Error("Incorrect location " + location);
		}

	}

	private static function bundleResources(CoCLoader:Class):void {
		var dt:XML           = describeType(CoCLoader);
		// <type name="coc.view::CoCLoader" ...
		var classname:String         = String(dt.@name);
		var extraPath:String   = "classes/";
		if (classname.indexOf('::')>=0) {
			var packageName:String = classname.split('::')[0];
			if (packageName) {
				extraPath += packageName.replace(/\./g, '/') + "/";
				// => extraPath = "classes/coc/view/"
			}
		}
		for each (var decl:XML in dt.variable.(@type == "Class")) {
			/*
			<variable name="BUNDLE_RES_MODEL_XML" type="Class">
				<metadata name="Embed">
					<arg key="source" value="../../../res/model.xml"/>
					<arg key="mimeType" value="application/octet-stream"/>
				</metadata>
			</variable>
			 */
			var name:String = decl.@name;
			var meta:XMLList = decl.metadata.(@name == "Embed");
			var src:String = meta.arg.(@key == "source").@value;
			var mime:String = meta.arg.(@key == "mimeType").@value;
			if (name && src) {
				var value:Class = CoCLoader[name];
				src = extraPath + src;
				// src = "classes/coc/view/../../../res/model.xml"
				while(true) {
					var src2:String = src.replace(/[^\/]+\/\.\.\//,'');
					// src  = "classes/coc/view/../../../res/model.xml"
					// src2 = "classes/coc/"+     "../../res/model.xml"
					if (src == src2) break;
					src = src2;
				}
				if (mime.indexOf('image/') == 0) {
					bundleImage(src, value);
				} else {
					bundleText(src, value);
				}
			}
		}
	}
	bundleResources(CoCLoader);
}
}


code:
package coc.view {

import classes.internals.Utils;

import com.bit101.components.*;

import flash.display.DisplayObjectContainer;
import flash.events.MouseEvent;

//TODO @Oxdeception check some scroll issues, more customisation
public class CoCScrollPane extends ScrollPane {
	public function CoCScrollPane(parent:DisplayObjectContainer = null, xpos:Number = 0, ypos:Number = 0) {
		super(parent, xpos, ypos);
		_background.alpha = 0;
		addEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler)
	}

	private function mouseWheelHandler(e:MouseEvent):void {
		if (content.height < this.height) {return;}
		content.y         = Utils.boundFloat(-_vScrollbar.maximum, content.y + (10 * e.delta), _vScrollbar.minimum);
		_vScrollbar.value = -content.y;
	}
}
}


code:
/**
 * Coded by aimozg on 11.07.2017.
 * Contains code from:
 * * TinyColor v1.4.1 by Brian Grinstead, MIT License
 */
package coc.view {
import classes.internals.Utils;

public class Color {
	public static const INVALID_COLOR:uint = 0x1ee00ee;
	public function Color() {
	}
	public static function convertColor(input:Object):uint {
		return convertColorEx(input,false);
	}
	public static function convertColor32(input:Object):uint {
		return convertColorEx(input,true);
	}
	public static function convertColorEx(input:Object,alpha:Boolean):uint {
		var s:String = input as String;
		if (s) return parseColorString(s);
		if (input is Number) return uint(input);
		if (!input) return INVALID_COLOR;
		if ('r' in input && 'g' in input && 'b' in input) {
			var r:Number = input.r;
			var g:Number = input.g;
			var b:Number = input.b;
			var a:Number = ('a' in input) ? input.a : 1.0;
			if (r<=1 && g<=1 && b<=1 && a<=1) return alpha?fromArgbFloat(a,r,g,b):fromRgbFloat(r,g,b);
			return alpha?fromArgb(a,r,g,b):fromRgb(r,g,b);
		}
		return INVALID_COLOR;
	}
	public static function fromArgb(a:uint, r:uint, g:uint, b:uint):uint {
		return ((a & 0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff);
	}
	public static function fromRgb(r:uint, g:uint, b:uint):uint {
		return ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff);
	}
	public static function fromArgbFloat(a:Number,r:Number, g:Number, b:Number):uint {
		return fromArgb(a*255,r*255,g*255,b*255);
	}
	public static function fromRgbFloat(r:Number, g:Number, b:Number):uint {
		return fromRgb(r*255,g*255,b*255);
	}
	public static function parseColorString(s:String, alpha:Boolean = false):uint {
		var a:Array;
		if ((a = s.match(/^(?:0x|\$|#)([a-fA-F0-9]{8})$/))) {
			x = parseInt(a[1],16);
			return alpha ? x : (x & 0x00ffffff);
		}
		var x:uint;
		if ((a = s.match(/^(?:0x|\$|#)([a-fA-F0-9]{6})$/))) {
			x = parseInt(a[1], 16);
		} else if ((a = s.match(/^(?:0x|\$|#)([a-fA-F0-9]{3})$/))) {
			var rgb12:uint = parseInt(a[1], 16);
			x              = ((rgb12 & 0xf00) << (5 - 2) |
							  (rgb12 & 0xf00) << (4 - 2) |
							  (rgb12 & 0x0f0) << (3 - 1) |
							  (rgb12 & 0x0f0) << (2 - 1) |
							  (rgb12 & 0x00f) << (1 - 0) |
							  (rgb12 & 0x00f) << (0 - 0));
		} else if ((a = s.match(/^hsl\((\d+),(\d+),(\d+)\)$/))) {
			x = fromHsl({h:a[1],s:a[2],l:a[3]});
		} else {
			return INVALID_COLOR;
		}
		return alpha ? 0xff000000|x : x;
	}
	public static function toHsl(color:uint):Object {
		var a:Number = ((color >> 24) & 0xff)/255.0;
		var r:Number = ((color >> 16) & 0xff)/255.0;
		var g:Number = ((color >> 8) & 0xff)/255.0;
		var b:Number = ((color >> 0) & 0xff)/255.0;

		var max:Number = Math.max(r, g, b), min:Number = Math.min(r, g, b);
		var h:Number, s:Number, l:Number = (max + min) / 2;

		if(max == min) {
			h = s = 0; // achromatic
		}
		else {
			var d:Number = max - min;
			s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
			switch(max) {
				case r: h = (g - b) / d + (g < b ? 6 : 0); break;
				case g: h = (b - r) / d + 2; break;
				case b: h = (r - g) / d + 4; break;
				default: //It's there to shut up that Sonarcloud code smell.
			}

			h /= 6;
		}

		return { h: h*360, s: s*100, l: l*100 , a: a/255.0};
	}
	public static function fromHsl(hsl:Object):uint {
		var r:Number, g:Number, b:Number;

		var h:Number = Utils.boundFloat(0, hsl.h/360, 1);
		var s:Number = Utils.boundFloat(0, hsl.s/100, 1);
		var l:Number = Utils.boundFloat(0, hsl.l/100, 1);

		function hue2rgb(p:Number, q:Number, t:Number):Number {
			if(t < 0) t += 1;
			if(t > 1) t -= 1;
			if(t < 1/6) return p + (q - p) * 6 * t;
			if(t < 1/2) return q;
			if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
			return p;
		}

		if(s === 0) {
			r = g = b = l; // achromatic
		}
		else {
			var q:Number = l < 0.5 ? l * (1 + s) : l + s - l * s;
			var p:Number = 2 * l - q;
			r = hue2rgb(p, q, h + 1/3);
			g = hue2rgb(p, q, h);
			b = hue2rgb(p, q, h - 1/3);
		}

		if ('a' in hsl) return fromArgbFloat(hsl.a,r,g,b);
		return fromRgbFloat(r,g,b);
	}
	public static function darken(color:uint, amount:Number = 10):uint {
		var hsl:Object = toHsl(color);
		hsl.l = Utils.boundFloat(0,hsl.l-amount,100);
		return fromHsl(hsl);
	}
	public static function lighten(color:uint, amount:Number = 10):uint {
		var hsl:Object = toHsl(color);
		hsl.l = Utils.boundFloat(0,hsl.l+amount,100);
		return fromHsl(hsl);
	}
}
}


code:
/**
 * Coded by aimozg on 24.11.2017.
 */
package coc.view {
import classes.CoC;
import classes.Monster;
import classes.PerkLib;
import classes.Player;
import classes.internals.Utils;
import classes.GlobalFlags.kFLAGS;
import classes.GlobalFlags.kGAMECLASS;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.utils.Timer;
import flash.events.TimerEvent;
import com.bit101.components.TextFieldVScroll;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.display.Sprite;

public class MinimapView extends Block {

	private var sideBarBG:BitmapDataSprite;
	public var scrollBar:TextFieldVScroll;
	public var mapView:TextField;
    public var scrollBarMap:TextFieldVScroll;

	public function MinimapView(mainView:MainView) {
		super({
            x                : 0,
            y                : 650,
            width            : MainView.STATBAR_W,
            height           : 175,
			layoutConfig: {
				padding: MainView.GAP,
				type: 'flow',
				direction: 'column',
				ignoreHidden: true,
				gap: 1
			}
		});
		sideBarBG = addBitmapDataSprite({
            width            : MainView.STATBAR_W,
            height           : 175,
			stretch: true
		}, {ignore: true});
        mapView       = addTextField({
            multiline        : true,
            wordWrap         : true,
            x                : 100,
            y                : 625,
            width            : MainView.STATBAR_W,
            height           : 150,
            mouseEnabled     : true,
            defaultTextFormat: {
                size: 15
            }
        });
        scrollBarMap = new TextFieldVScroll(mapView);
        UIUtils.setProperties(scrollBarMap,{
            name: "scrollBarMap",
            direction: "vertical",
            x: mapView.x + mapView.width,
            y: mapView.y,
            height: mapView.height,
            width: 5
        });
        addElement(scrollBarMap);
	}

	public function show():void {
		this.visible = true;
		this.alpha    = 1;
	}

	public function hide():void {
		this.visible = false;
	}
	public function setBackground(bitmapClass:Class):void {
		sideBarBG.bitmapClass = bitmapClass;
	}
    public function setTheme(textColor:uint):void {
        var dtf:TextFormat;
        this.mapView.textColor = textColor;

    }

}
}


code:
/**
 * Coded by aimozg on 24.11.2017.
 */
package coc.view {
import classes.CoC;
import classes.Monster;
import classes.PerkLib;
import classes.Player;
import classes.internals.Utils;
import classes.GlobalFlags.kFLAGS;
import classes.GlobalFlags.kGAMECLASS;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.utils.Timer;
import flash.events.TimerEvent;
import com.bit101.components.TextFieldVScroll;

public class MonsterStatsView extends Block {

	private var sideBarBG:BitmapDataSprite;
	private var nameText:TextField;
	private var levelBar:StatBar;
	private var hpBar:StatBar;
	private var lustBar:StatBar;
	private var fatigueBar:StatBar;
	private var corBar:StatBar;
	public var sprite:BitmapDataSprite;
	public var monsterViews:Vector.<OneMonsterView> = new Vector.<OneMonsterView>;
	public var moved:Boolean = false;
	public function MonsterStatsView(mainView:MainView) {
		super({
			x           : MainView.MONSTER_X,
			y           : MainView.MONSTER_Y,
			width       : MainView.MONSTER_W,
			height      : MainView.MONSTER_H,
			layoutConfig: {
				//padding: MainView.GAP,
				type: 'flow',
				direction: 'column',
				ignoreHidden: true
				//gap: 1
			}
		});
		StatBar.setDefaultOptions({
			barColor: '#600000',
			width   : innerWidth
		});
		//addElement(monster1 = new OneMonsterView());
		for (var j:int = 0; j < 4; j++){
			var monsterView:OneMonsterView = new OneMonsterView(mainView);
			monsterView.hide();
			this.addElement(monsterView);
			monsterViews.push(monsterView);

		}
	}

	public function show():void {
		this.visible = true;
	}

	public function hide():void {
		for (var i:int = 0; i < 4; i++){
			monsterViews[i].hide();
		}

		this.visible = false;
	}

	public function resetStats(game:CoC):void{
		for each(var view:OneMonsterView in monsterViews){
			view.resetStats();
		}
	}

	public function refreshStats(game:CoC):void {
        var i:int = 0
		for (i; i < game.monsterArray.length; i++){
			if (game.monsterArray[i] != null){
				monsterViews[i].refreshStats(game, i);
				monsterViews[i].show(game.monsterArray[i].generateTooltip(),"Details");
			}else{
				monsterViews[i].hide();
			}
		}
		if(i < monsterViews.length){
			for(i; i<monsterViews.length;i++){
                monsterViews[i].hide();
			}
		}
	invalidateLayout();
	}

	public function setBackground(bitmapClass:Class):void {
		for (var i:int = 0; i < 4; i++){
			monsterViews[i].setBackground(bitmapClass);
		}
	}
	public function setTheme(font:String,
							 textColor:uint,
							 barAlpha:Number):void {
		for (var i:int = 0; i < 4; i++){
			monsterViews[i].setTheme(font, textColor, barAlpha);
		}
	}

	public function animateBarChange(bar:StatBar, newValue:Number):void {
		if (kGAMECLASS.flags[kFLAGS.ANIMATE_STATS_BARS] == 0) {
			bar.value = newValue;
			return;
		}
		var oldValue:Number = bar.value;
		//Now animate the bar.
		var tmr:Timer = new Timer(32, 30);
		tmr.addEventListener(TimerEvent.TIMER, kGAMECLASS.createCallBackFunction(stepBarChange, bar, [oldValue, newValue, tmr]));
		tmr.start();
	}
	private function stepBarChange(bar:StatBar, args:Array):void {
		var originalValue:Number = args[0];
		var targetValue:Number = args[1];
		var timer:Timer = args[2];
		bar.value = originalValue + (((targetValue - originalValue) / timer.repeatCount) * timer.currentCount);
		if (timer.currentCount >= timer.repeatCount) bar.value = targetValue;
		//if (bar == hpBar) bar.bar.fillColor = Color.fromRgbFloat((1 - (bar.value / bar.maxValue)) * 0.8, (bar.value / bar.maxValue) * 0.8, 0);
	}

}
}


code:
/**
 * Coded by aimozg on 24.11.2017.
 */
package coc.view {
import classes.CoC;
import classes.Monster;
import classes.PerkLib;
import classes.Player;
import classes.internals.Utils;
import classes.GlobalFlags.kFLAGS;
import classes.GlobalFlags.kGAMECLASS;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.utils.Timer;
import flash.events.TimerEvent;
import com.bit101.components.TextFieldVScroll;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.display.Sprite;

public class OneMonsterView extends Block {

	private var sideBarBG:BitmapDataSprite;
	private var nameText:TextField;
	private var levelBar:StatBar;
	private var hpBar:StatBar;
	private var lustBar:StatBar;
	private var fatigueBar:StatBar;
	private var corBar:StatBar;
	public var sprite:BitmapDataSprite;
	public var scrollBar:TextFieldVScroll;
	public var topRow:Block;
	public var monsterIndex:Number;
	public var toolTipHeader:String;
	public var toolTipText:String = "sfssd";
	public var index:int = -1;

	public function OneMonsterView(mainView:MainView) {
		super({
			x           : MainView.MONSTER_X,
			y           : MainView.MONSTER_Y - 30,
			width       : MainView.MONSTER_W,
			height      : MainView.MONSTER_H,
			layoutConfig: {
				padding: MainView.GAP,
				type: 'flow',
				direction: 'column',
				ignoreHidden: true,
				gap: 1
			}
		});
		const LABEL_FORMAT:Object = {
			font:'Palatino Linotype',
			bold:true,
			size:12
		};
		StatBar.setDefaultOptions({
			barColor: '#600000',
			width   : MainView.MONSTER_W
		});
		sideBarBG = addBitmapDataSprite({
			width  : MainView.MONSTER_W,
			height : MainView.MONSTER_H,
			stretch: true
		}, {ignore: true});
		nameText  = addTextField({
			defaultTextFormat: LABEL_FORMAT
		});
		addElement(levelBar = new StatBar({
			statName: "Level:",
			hasBar  : false,
			height     : 23
		}));
		addElement(hpBar = new StatBar({
			statName: "HP:",
			//barColor: '#6a9a6a',
			showMax : true,
			hasMinBar : true,
			minBarColor: '#a86e52',
			barColor: '#b17d5e',
			height     : 23

		}));
		addElement(lustBar = new StatBar({
			statName   : "Lust:",
			minBarColor: '#880101',
			hasMinBar  : true,
			showMax    : true,
			height     : 23
		}));
		addElement(fatigueBar = new StatBar({
			statName: "Fatigue:",
			showMax : true,
			height     : 23
		}));
			this.addEventListener(MouseEvent.ROLL_OVER, this.hover);
			this.addEventListener(MouseEvent.ROLL_OUT, this.dim);
			this.addEventListener(MouseEvent.CLICK, this.selectMonster);
	}

	public function cheat(event:MouseEvent = null):void{
		kGAMECLASS.mainView.nameBox.visible = true;
	}

	protected function selectMonster(event:MouseEvent):void{
		var target:int = (event.target as OneMonsterView).index;
		if (kGAMECLASS.monsterArray.length > 1 && kGAMECLASS.combat.canTarget(target) && kGAMECLASS.combat.playerTurn){
			kGAMECLASS.combat.multiAttack(target);
			kGAMECLASS.mainView.monsterStatsView.refreshStats(kGAMECLASS);
		}
	}

	public function show(toolTipText:String="",toolTipHeader:String=""):void {
		this.visible = true;
		this.alpha    = 1;
		hint(toolTipText, toolTipHeader);
	}

	public function hide():void {
		this.visible = false;
	}

	public function hover(event:MouseEvent = null):void {
			if (this.alpha)
				this.alpha = 0.5;
		}

		public function dim(event:MouseEvent = null):void {
			if (this.alpha)
				this.alpha = 1;
	}

	public function resetStats():void{
		hpBar.value = 0;
		hpBar.value = 0;
		lustBar.value = 0;
		lustBar.value = 0;
		fatigueBar.value = 0;
	}

	public function refreshStats(game:CoC, index:Number = -1):void {
		this.index = index;
		if (index != -1 && game.monsterArray[index] == null) return;
		var monster:Monster   = index != -1 ? game.monsterArray[index] : game.monster;
		if (game.monsterArray.length > 1 && game.combat.currTarget == index) nameText.text = "[" + Utils.capitalizeFirstLetter(monster.short) + "]";
		else nameText.text         = Utils.capitalizeFirstLetter(monster.short);
		levelBar.value        = monster.level;
		hpBar.maxValue        = monster.maxHP();
		hpBar.minValue        = monster.HP;
		animateBarChange(hpBar, monster.HP);
		lustBar.maxValue      = monster.maxLust();
		lustBar.minValue      = monster.minLust();
		//lustBar.value         = monster.lust;
		animateBarChange(lustBar, monster.lust);
		fatigueBar.value      = monster.fatigue;
		fatigueBar.maxValue   = monster.maxFatigue();
		animateBarChange(fatigueBar, monster.fatigue);
		toolTipHeader = "Details";
		toolTipText = monster.generateTooltip();
		animateBarChange(fatigueBar, monster.fatigue);

		invalidateLayout();
	}

	public function setBackground(bitmapClass:Class):void {
		sideBarBG.bitmapClass = bitmapClass;
	}
	public function setTheme(font:String,
							 textColor:uint,
							 barAlpha:Number):void {
		var dtf:TextFormat;
		for (var ci:int = 0, cn:int = this.numElements; ci < cn; ci++) {
			var e:StatBar = this.getElementAt(ci) as StatBar;
			if (!e) continue;
			dtf = e.valueLabel.defaultTextFormat;
			dtf.color = textColor;
			dtf.font = font;
			e.valueLabel.defaultTextFormat = dtf;
			e.valueLabel.setTextFormat(dtf);
			if (e.bar) e.bar.alpha    = barAlpha;
			if (e.minBar) e.minBar.alpha = (1 - (1 - barAlpha) / 2); // 2 times less transparent than bar
		}

		for each(var tf:TextField in [nameText]) {
			dtf = tf.defaultTextFormat;
			dtf.color = textColor;
			tf.defaultTextFormat = dtf;
			tf.setTextFormat(dtf);
		}
	}

	public function animateBarChange(bar:StatBar, newValue:Number):void {
		if (kGAMECLASS.flags[kFLAGS.ANIMATE_STATS_BARS] == 0) {
			bar.value = newValue;
			return;
		}
		var oldValue:Number = bar.value;
		//Now animate the bar.
		var tmr:Timer = new Timer(32, 30);
		tmr.addEventListener(TimerEvent.TIMER, kGAMECLASS.createCallBackFunction(stepBarChange, bar, [oldValue, newValue, tmr]));
		tmr.start();
	}
	private function stepBarChange(bar:StatBar, args:Array):void {
		var originalValue:Number = args[0];
		var targetValue:Number = args[1];
		var timer:Timer = args[2];
		bar.value = originalValue + (((targetValue - originalValue) / timer.repeatCount) * timer.currentCount);
		if (timer.currentCount >= timer.repeatCount) bar.value = targetValue;
		//if (bar == hpBar) bar.bar.fillColor = Color.fromRgbFloat((1 - (bar.value / bar.maxValue)) * 0.8, (bar.value / bar.maxValue) * 0.8, 0);
	}

	public function hint(toolTipText:String = "",toolTipHeader:String=""):void {
			this.toolTipText   = toolTipText;
			this.toolTipHeader = toolTipHeader;
		}

}
}


code:
/**
 * Coded by aimozg on 06.06.2017.
 */
package coc.view {
import classes.internals.Utils;

import flash.text.TextField;

public class StatBar extends Block {
	[Embed(source="../../../res/ui/StatsBarBottom.png")]
	public static const StatsBarBottom:Class;
	[Embed(source = "../../../res/ui/arrow-up.png")]
	public static const ArrowUp:Class;
	[Embed(source = "../../../res/ui/arrow-down.png")]
	public static const ArrowDown:Class;

	private static function factoryReset():Object {
		return {
			width      : 200,
			height     : 28,
			minValue   : 0,
			maxValue   : 100,
			value      : 0,
			statName   : "",
			showMax    : false,
			isUp       : false,
			isDown     : false,
			hasGauge   : true,
			hasBar     : true,
			hasMinBar  : false,
			hasShadow  : true,
			barAlpha   : 0.4,
			barHeight  : 1.0, // relative to height
			barColor   : '#0000ff',
			minBarColor: '#8080ff',
			bgColor    : null
		};
	}
	private static var DEFAULT_OPTIONS:Object     = factoryReset();
	public static function setDefaultOptions(options:Object):void {
		Utils.extend(DEFAULT_OPTIONS,options);
	}
	public static function resetDefaultOptions():void {
		DEFAULT_OPTIONS = factoryReset();
	}

	private var _bar:BitmapDataSprite;
	private var _minBar:BitmapDataSprite;
	private var _bgBar:BitmapDataSprite;
	private var _arrowUp:BitmapDataSprite;
	private var _arrowDown:BitmapDataSprite;
	private var _nameLabel:TextField;
	private var _valueLabel:TextField;
	private var _minValue:Number;
	private var _maxValue:Number;
	private var _value:Number;
	private var _showMax:Boolean;
	private function get arrowSz():Number {
		return this.height-2;
	}

	public function StatBar(options:Object) {
		super();
		options          = Utils.extend({},DEFAULT_OPTIONS, options);
		var myWidth:Number = options.width;
		var myHeight:Number = options.height;
		var arrowSz:Number  = myHeight - 2;
		var barWidth:Number = myWidth - arrowSz - 2;
		if (options.hasBar) {
			var barX:Number = 1;
			var barHeight:Number = myHeight*options.barHeight;
			var barY:Number = myHeight - barHeight;
			if (options.bgColor != null) {
				_bgBar = addBitmapDataSprite({
					x:barX,y:barY,
					alpha:options.barAlpha,
					fillColor:options.bgColor,
					width:barWidth,
					height:barHeight
				})
			}
			_bar = addBitmapDataSprite({
				x        : barX,
				y        : barY,
				alpha    : options.barAlpha,
				fillColor: options.barColor,
				width    : 0,
				height   : barHeight
			});
			if (options.hasMinBar) {
				_minBar = addBitmapDataSprite({
					x        : barX,
					y        : barY,
					alpha    : options.barAlpha,
					fillColor: options.minBarColor,
					width    : 0,
					height   : barHeight
				});
			}
			if (options.hasGauge) {
				/*gauge=*/
				addBitmapDataSprite({
					x          : 0,
					y          : myHeight - 10,
					width      : barWidth+2,
					height     : 10,
					stretch    : true,
					bitmapClass: StatsBarBottom
				});
			}
			if (options.hasShadow) {
				this.applyShadow();
			}
		}
		_nameLabel  = addTextField({
			x                : 6, y: 4,
			width: barWidth,
			height           : myHeight - 4 * options.height / 28,
			defaultTextFormat: {
				font: 'Palatino Linotype',
				size: 15 * options.height / 28
			}
		});
		_valueLabel = addTextField({
			x                : 0, y: myHeight-(30 * options.height / 28),
			width: barWidth,
			height           : 30 * options.height / 28,
			defaultTextFormat: {
				font : 'Palatino Linotype',
				size : 22 * options.height / 28,
				align: 'right'
			}
		});
		_arrowUp = addBitmapDataSprite({
			bitmapClass: ArrowUp,
			width: arrowSz,
			height: arrowSz,
			stretch: true,
			x          : myWidth - arrowSz - 1,
			y: 1,
			visible: false
		});
		_arrowDown = addBitmapDataSprite({
			bitmapClass: ArrowDown,
			width: arrowSz,
			height: arrowSz,
			stretch: true,
			x          : myWidth - arrowSz - 1,
			y: 1,
			visible: false
		});
		UIUtils.setProperties(this,options);
		update();
	}

	public function get minValue():Number {
		return _minValue;
	}
	public function set minValue(value:Number):void {
		_minValue = value;
		update();
	}
	public function get maxValue():Number {
		return _maxValue;
	}
	public function set maxValue(value:Number):void {
		_maxValue = value;
		if (showMax) renderValue();
		update();
	}
	private function renderValue():void {
		valueText = '' + Math.floor(value) + (showMax ? '/' + Math.floor(maxValue) : '');
	}
	public function get value():Number {
		return _value;
	}
	public function set value(value:Number):void {
		_value    = value;
		renderValue();
		update();
	}
	public function get valueText():String {
		return _valueLabel ? _valueLabel.text : value + '';
	}
	public function set valueText(value:String):void {
		if (_valueLabel) _valueLabel.text = value;
	}
	public function update():void {
		if (_bar) {
			_bar.width = maxValue > 0 ?
					Utils.boundFloat(0, value, maxValue) * (width - arrowSz-2) / maxValue : 0;
		}
		if (_minBar) {
			_minBar.width = maxValue > 0 ?
					Utils.boundFloat(0, minValue, maxValue) * (width - arrowSz-2) / maxValue : 0;
		}
	}
	public function get showMax():Boolean {
		return _showMax;
	}
	public function set showMax(value:Boolean):void {
		_showMax = value;
		renderValue();
	}
	public function get isUp():Boolean {
		return _arrowUp.visible;
	}
	public function set isUp(value:Boolean):void {
		_arrowUp.visible = value;
		if (value) _arrowDown.visible = false;
	}
	public function get isDown():Boolean {
		return _arrowDown.visible;
	}
	public function set isDown(value:Boolean):void {
		_arrowDown.visible = value;
		if (value) _arrowUp.visible = false;
	}
	public function get statName():String {
		return _nameLabel.text;
	}
	public function set statName(value:String):void {
		_nameLabel.text = value;
	}

	public function get bar():BitmapDataSprite {
		return _bar;
	}
	public function get minBar():BitmapDataSprite {
		return _minBar;
	}
	public function get nameLabel():TextField {
		return _nameLabel;
	}
	public function get valueLabel():TextField {
		return _valueLabel;
	}
	public function get arrowUp():BitmapDataSprite {
		return _arrowUp;
	}
	public function get arrowDown():BitmapDataSprite {
		return _arrowDown;
	}
}
}


code:
package coc.view {
import classes.CoC;
import classes.GlobalFlags.kFLAGS;
import classes.GlobalFlags.kGAMECLASS;
import classes.Player;
import classes.internals.LoggerFactory;
import classes.internals.Utils;
import flash.events.TimerEvent;
import flash.filters.DropShadowFilter;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.utils.Timer;
import mx.logging.ILogger;

public class StatsView extends Block {
	private static const LOGGER:ILogger = LoggerFactory.getLogger(StatsView);
	[Embed(source = "../../../res/ui/sidebar1.png")]
	public static const SidebarBg1:Class;
	[Embed(source = "../../../res/ui/sidebar2.png")]
	public static const SidebarBg2:Class;
	[Embed(source = "../../../res/ui/sidebar3.png")]
	public static const SidebarBg3:Class;
	[Embed(source = "../../../res/ui/sidebar4.png")]
	public static const SidebarBg4:Class;
	[Embed(source = "../../../res/ui/sidebar5.png")]
	public static const SidebarBg5:Class;
	//[Embed(source = "../../../res/ui/sidebar6.png")]
	//public static const SidebarBg6:Class;
	[Embed(source = "../../../res/ui/sidebarKaizo.png")]
	public static const SidebarBgKaizo:Class;
	public static const SidebarBackgrounds:Array = [SidebarBg1, SidebarBg2, SidebarBg3, SidebarBg4, SidebarBg5, null, null, null, SidebarBgKaizo, null];
	public static const ValueFontOld:String    = 'Lucida Sans Typewriter';
	public static const ValueFont:String       = 'Palatino Linotype';

	private var sideBarBG:BitmapDataSprite;
	private var nameText:TextField;
	private var coreStatsText:TextField;
	private var combatStatsText:TextField;
	private var advancementText:TextField;
	private var timeText:TextField;
	private var strBar:StatBar;
	private var touBar:StatBar;
	private var speBar:StatBar;
	private var intBar:StatBar;
	/* [INTERMOD: xianxia]
	private var wisBar:StatBar;
	 */
	private var libBar:StatBar;
	private var senBar:StatBar;
	private var corBar:StatBar;
	private var hpBar:StatBar;
	/* [INTERMOD: xianxia]
	private var wrathBar:StatBar;
	*/
	private var lustBar:StatBar;
	private var fatigueBar:StatBar;
	/* [INTERMOD: xianxia]
	private var manaBar:StatBar;
	private var soulforceBar:StatBar;
	*/
	private var hungerBar:StatBar;
	private var esteemBar:StatBar;
	private var willBar:StatBar;
	private var obeyBar:StatBar;
	private var levelBar:StatBar;
	private var xpBar:StatBar;
	private var gemsBar:StatBar;
	/* [INTERMOD: xianxia]
	private var spiritstonesBar:StatBar;
	*/

	private var allStats:Array;

	public function StatsView(mainView:MainView) {
		super({
			width: MainView.STATBAR_W,
			height: MainView.STATBAR_H,
			layoutConfig: {
				padding: MainView.GAP,
				type: 'flow',
				direction: 'column',
				ignoreHidden: true,
				gap: 1
			}
		});
		const LABEL_FORMAT:Object = {
			font:'Palatino Linotype',
			bold:true,
			size:22
		};
		const TIME_FORMAT:Object = {
			font:'Lucida Sans Typewriter',
			size:18
		};
		StatBar.setDefaultOptions({
			barColor: '#600000',
			width: innerWidth
		});
		sideBarBG     = addBitmapDataSprite({
			width: MainView.STATBAR_W,
			height: MainView.STATBAR_H,
			stretch: true
		},{ ignore:true });
		nameText      = addTextField({
			defaultTextFormat: LABEL_FORMAT
		});
		coreStatsText = addTextField({
			text: 'Core stats:',
			defaultTextFormat: LABEL_FORMAT
		},{before:1});
		addElement(strBar = new StatBar({statName: "Strength:"}));
		addElement(touBar = new StatBar({statName: "Toughness:"}));
		addElement(speBar = new StatBar({statName: "Speed:"}));
		addElement(intBar = new StatBar({statName: "Intelligence:"}));
		/* [INTERMOD: xianxia]
		addElement(wisBar = new StatBar({statName: "Wisdom:"}));
		 */
		addElement(libBar = new StatBar({statName: "Libido:", maxValue: 100}));
		addElement(senBar = new StatBar({statName: "Sensitivity:", maxValue: 100}));
		addElement(corBar = new StatBar({statName: "Corruption:", maxValue: 100}));
		combatStatsText = addTextField({
			text: 'Combat stats',
			defaultTextFormat: LABEL_FORMAT
		},{before:1});
		addElement(hpBar = new StatBar({
			statName: "HP:",
			//barColor: '#6a9a6a',
			showMax : true,
			hasMinBar : true,
			minBarColor: '#a86e52',
			barColor: '#b17d5e'
		}));
		addElement(lustBar = new StatBar({
			statName   : "Lust:",
			minBarColor: '#880101',
			hasMinBar  : true,
			showMax    : true
		}));
		/* [INTERMOD: xianxia]
		addElement(wrathBar = new StatBar({
			statName: "Wrath:",
			showMax : true
		}));
		*/
		addElement(fatigueBar = new StatBar({
			statName: "Fatigue:",
			showMax: true
		}));
		/* [INTERMOD: xianxia]
		addElement(manaBar = new StatBar({
			statName: "Mana:",
		//	barColor: '#0000ff',
			showMax : true
		}));
		addElement(soulforceBar = new StatBar({
			statName: "Soulforce:",
		//	barColor: '#ffd700',
			showMax : true
		}));
		*/
		addElement(hungerBar = new StatBar({
			statName: "Satiety:",
			showMax : true
		}));
		addElement(esteemBar = new StatBar({
			statName: "Self Esteem:",
			showMax : true
		}));
		addElement(willBar = new StatBar({
			statName: "Willpower:",
			showMax : true
		}));
		addElement(obeyBar = new StatBar({
			statName: "Obedience:",
			showMax : true
		}));
		advancementText = addTextField({
			text:'Advancement',
			defaultTextFormat: LABEL_FORMAT
		},{before:1});
		addElement(levelBar = new StatBar({
			statName: "Level:",
			hasBar  : false
		}));
		addElement(xpBar = new StatBar({
			statName: "XP:",
			showMax: true
		}));
		addElement(gemsBar = new StatBar({
			statName: "Gems:",
			hasBar: false
		}));
		/* [INTERMOD: xianxia]
		addElement(spiritstonesBar = new StatBar({
			statName: "Spirit Stones:",
			hasBar: false
		}));
		*/
		timeText = addTextField({
			htmlText: 'Day#: 0\nTime: 00:00',
			defaultTextFormat: TIME_FORMAT
		},{before:1});
		///////////////////////////
		allStats = [];
		for (var ci:int = 0, cn:int = this.numElements; ci < cn; ci++) {
			var e:StatBar = this.getElementAt(ci) as StatBar;
			if (e) allStats.push(e);
		}
	}

	public function show():void {
		this.visible = true;
	}

	public function hide():void {
		this.visible = false;
	}

	// <- hideUpDown
	public function hideUpDown():void {
		var ci:int, cc:int = this.allStats.length;
		for (ci = 0; ci < cc; ++ci) {
			var c:StatBar = this.allStats[ci];
			c.isUp        = false;
			c.isDown      = false;
		}
	}

	public function showLevelUp():void {
		this.levelBar.isUp = true;
	}

	public function hideLevelUp():void {
		this.levelBar.isUp = false;
	}

	public function statByName(statName:String):StatBar {
		switch (statName.toLowerCase()) {
			case 'str':
				return strBar;
			case 'tou':
				return touBar;
			case 'spe':
				return speBar;
			case 'inte':
			case 'int':
				return intBar;
			/* [INTERMOD: xianxia]
			case 'wis':
				return wisBar;
			*/
			case 'lib':
				return libBar;
			case 'sens':
			case 'sen':
				return senBar;
			case 'cor':
				return corBar;
			case 'hp':
				return hpBar;
			/* [INTERMOD: xianxia]
			case 'wrath':
				return wrathBar;
			*/
			case 'lust':
				return lustBar;
			case 'fatigue':
				return fatigueBar;
			/* [INTERMOD: xianxia]
			case 'mana':
				return manaBar;
			case 'soulforce':
				return soulforceBar;
			*/
			case 'hunger':
				return hungerBar;
			case 'level':
				return levelBar;
			case 'xp':
				return xpBar;
			case 'gems':
				return gemsBar;
			case 'will':
				return willBar;
			case 'esteem':
				return esteemBar;
			case 'obey':
				return obeyBar;
			/* [INTERMOD: xianxia]
			case 'spiritstones':
				return spiritstonesBar;
			*/
			default:
				return null;
		}
	}
	public function showStatUp(statName:String):void {
		var stat:StatBar = statByName(statName);
		if (stat) stat.isUp        = true;
		else LOGGER.error("Cannot showStatUp "+statName);
	}

	public function showStatDown(statName:String):void {
		var stat:StatBar = statByName(statName);
		if (stat) stat.isDown      = true;
		else LOGGER.error("[ERROR] Cannot showStatDown "+statName);
	}
	public function toggleHungerBar(show:Boolean):void {
		hungerBar.visible = show;
		invalidateLayout();
	}
	public function refreshStats(game:CoC):void {
		var player:Player            = game.player;
		var maxes:Object      = player.getAllMaxStats();
		nameText.htmlText     = "Name: " + player.short + "";
		strBar.maxValue       = maxes.str;
		strBar.value          = player.str;
		touBar.maxValue       = maxes.tou;
		touBar.value          = player.tou;
		speBar.maxValue       = maxes.spe;
		speBar.value          = player.spe;
		intBar.maxValue       = maxes.inte;
		intBar.value          = player.inte;
		/* [INTERMOD: xianxia]
		wisBar.maxValue       = maxes.wis;
		wisBar.value          = player.wis;
		libBar.maxValue       = maxes.lib;
		*/
		libBar.value          = player.lib;
		senBar.value          = player.sens;
		corBar.value          = player.cor;
		hpBar.maxValue        = player.maxHP();
		hpBar.minValue 	   	= player.HP;
		animateBarChange(hpBar, player.HP);
		//hpBar.value           = player.HP;
		/* [INTERMOD: xianxia]
		wrathBar.maxValue 	  = player.maxWrath();
		wrathBar.value    	  = player.wrath;
		*/
		lustBar.maxValue      = player.maxLust();
		lustBar.minValue      = player.minLust();
		animateBarChange(lustBar, player.lust);
		fatigueBar.maxValue   = player.maxFatigue();
		animateBarChange(fatigueBar, player.fatigue);
		/* [INTERMOD: xianxia]
		manaBar.maxValue 	  = player.maxMana();
		manaBar.value    	  = player.mana;
		soulforceBar.maxValue = player.maxSoulforce();
		soulforceBar.value    = player.soulforce;
	//	soulforceBar.valueText= (player.soulforce/player.maxSoulforce()).toFixed(2)+'%';
		*/
		hungerBar.maxValue    = player.maxHunger();
		hungerBar.value       = player.hunger;
		var inPrison:Boolean          = game.prison.inPrison;
		esteemBar.visible     		  = inPrison;
		willBar.visible      		  = inPrison;
		obeyBar.visible       		  = inPrison;
		levelBar.visible      		  = !inPrison;
		xpBar.visible         		  = !inPrison;
		gemsBar.visible       		  = !inPrison;
		/* [INTERMOD: xianxia]
		spiritstonesBar.visible       = !inPrison;
		*/
		if (inPrison) {
			advancementText.htmlText = "Prison Stats";
			esteemBar.maxValue       = 100;
			esteemBar.value          = player.esteem;
			willBar.maxValue         = 100;
			willBar.value            = player.will;
			obeyBar.maxValue         = 100;
			obeyBar.value            = player.obey;
		} else {
			advancementText.htmlText = "Advancement";
			levelBar.value           = player.level;
			if (player.level < kGAMECLASS.levelCap) {
				xpBar.maxValue  = player.requiredXP();
				animateBarChange(xpBar, player.XP);
			} else {
				xpBar.maxValue  = player.XP;
				xpBar.value     = player.XP;
				xpBar.valueText = 'MAX';
			}
			gemsBar.valueText = Utils.addComma(Math.floor(player.gems));
			/* [INTERMOD: xianxia]
			spiritstonesBar.valueText = game.flags[kFLAGS.SPIRIT_STONES];
			*/
		}

		var minutesDisplay:String = "" + game.time.minutes;
		if (minutesDisplay.length == 1) minutesDisplay = "0" + minutesDisplay;

		var hours:Number = game.time.hours;
		var hrs:String, ampm:String;
		if (game.flags[kFLAGS.USE_12_HOURS] == 0) {
			hrs  = "" + hours;
			ampm = "";
		} else {
			hrs  = (hours % 12 == 0) ? "12" : "" + (hours % 12);
			ampm = hours < 12 ? "am" : "pm";
		}
		timeText.htmlText = "Day#: " + game.time.days + ""+
						"\nTime: " + hrs + ":" + minutesDisplay + ampm;

		invalidateLayout();
	}

	public function setBackground(bitmapClass:Class):void {
		sideBarBG.bitmapClass = bitmapClass;
	}

	public function setTheme(font:String, textColor:uint, barAlpha:Number):void {
		var dtf:TextFormat;
		var shadowFilter:DropShadowFilter = new DropShadowFilter();

		for each(var e:StatBar in allStats) {
			dtf = e.valueLabel.defaultTextFormat;
			dtf.color = textColor;
			dtf.font = font;
			e.valueLabel.defaultTextFormat = dtf;
			e.valueLabel.setTextFormat(dtf);
			dtf = e.nameLabel.defaultTextFormat;
			dtf.color = textColor;
			e.nameLabel.defaultTextFormat = dtf;
			e.nameLabel.setTextFormat(dtf);

			if (e.bar) {
				e.bar.alpha = barAlpha;

				if (e.bar.filters.length < 1) {
					e.bar.filters = [shadowFilter];
				}
			}

			if (e.minBar) {
				e.minBar.alpha = (1 - (1 - barAlpha) / 2); // 2 times less transparent than bar
			}
		}

		for each(var tf:TextField in [nameText,coreStatsText,combatStatsText,advancementText,timeText]) {
			dtf = tf.defaultTextFormat;
			dtf.color = textColor;
			tf.defaultTextFormat = dtf;
			tf.setTextFormat(dtf);
		}

	}

	public function animateBarChange(bar:StatBar, newValue:Number):void {
		if (kGAMECLASS.flags[kFLAGS.ANIMATE_STATS_BARS] == 0) {
			bar.value = newValue;
			return;
		}
		var oldValue:Number = bar.value;
		//Now animate the bar.
		var tmr:Timer = new Timer(32, 30);
		tmr.addEventListener(TimerEvent.TIMER, kGAMECLASS.createCallBackFunction(stepBarChange, bar, [oldValue, newValue, tmr]));
		tmr.start();
	}
	private function stepBarChange(bar:StatBar, args:Array):void {
		var originalValue:Number = args[0];
		var targetValue:Number = args[1];
		var timer:Timer = args[2];
		bar.value = originalValue + (((targetValue - originalValue) / timer.repeatCount) * timer.currentCount);
		if (timer.currentCount >= timer.repeatCount) bar.value = targetValue;
		//if (bar == hpBar) bar.bar.fillColor = Color.fromRgbFloat((1 - (bar.value / bar.maxValue)) * 0.8, (bar.value / bar.maxValue) * 0.8, 0);
	}
}
}


code:
package coc.view {
	import flash.display.Bitmap;
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.display.DisplayObject;
	import flash.geom.Point;
	import flash.text.TextField;
	import flash.text.TextFieldType;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFieldType;

	public class ToolTipView extends Block {
		[Embed(source='../../../res/ui/tooltip.png')]
		public static const tooltipBg:Class;
		public var
			bg:Sprite,
			ln:Sprite,
			hd:TextField,
			tf:TextField;

		private var mainView:MainView;
		private static const MIN_HEIGHT:Number = 239;
		private static const WIDTH:Number = 350;

		public function ToolTipView(mainView:MainView) {
			super();
			this.mainView = mainView;

			this.bg = addBitmapDataSprite({
				x:0, y:0,
				width:350,height:239,stretch: true,
				bitmapClass: tooltipBg
			});
			this.ln = addBitmapDataSprite({
				x:15,y:40,
				width:320,height:1,fillColor:'#000000'
			});
			this.hd = addTextField({
				x:15,y:15,
				width:316,height:25.35,
				multiline:true,
				wordWrap:false,
				embedFonts:true,
				defaultTextFormat:{
					size: 18,
					font: CoCButton.BUTTON_LABEL_FONT_NAME
				}
			});
			this.tf = addTextField({
				x:15,y:40,
				width:316,
				multiline:true,wordWrap:true,
				defaultTextFormat:{
					size:15
				}
			});
			this.tf.autoSize = TextFieldAutoSize.LEFT;
		}

		/**
		 * Display tooltip near rectangle with specified coordinates
		 */
		public function show(bx:Number, by:Number, bw:Number, bh:Number):void {
			this.x = bx;
			if (this.x < 0) {
				this.x = 0; // left border
			} else if (this.x + this.width > mainView.width) {
				this.x = mainView.width - this.width; // right border
			}
			bg.height = Math.max(tf.height + 63, MIN_HEIGHT);
			if (by+bh < mainView.height/2) {
				// put to the bottom
				this.y = by + bh;
			} else {
				// put on top
				this.y = by - this.height;
			}
			this.visible = true;
		}

		public function showForElement(e:DisplayObject, xOffset:int = 0, yOffset:int = 0):void {
			var lpt:Point = e.getRect(this.parent).topLeft;
			show(lpt.x + xOffset, lpt.y + yOffset, e.width, e.height);
		}

		public function showForMonster(button:DisplayObject):void {
			var bx:Number = button.x,
				by:Number = button.y;
			this.x = bx + 450;
			this.y = by + 50;
			this.visible = true;
		}

		public function hide():void {
			this.visible = false;

		}

		public function set header(newText:String):void {
			this.hd.htmlText = newText || '';
		}

		public function get header():String {
			return this.hd.htmlText;
		}

		public function set text(newText:String):void {
			this.tf.htmlText = newText || '';

				bg.height = 239;
				tf.height = 176;

		}

		public function get text():String {
			return this.tf.htmlText;
		}
	}
}


code:
/**
 * Coded by aimozg on 06.06.2017.
 */
package coc.view {
import flash.text.TextFormat;

public class UIUtils {
	public function UIUtils() {
	}
	public static function convertColor(input:Object):uint {
		return Color.convertColor(input);
	}
	public static function convertTextFormat(input:Object):TextFormat {
		if (input is TextFormat) return input as TextFormat;
		var tf:TextFormat = new TextFormat();
		setProperties(tf,input,{
			'color':Color.convertColor
		});
		return tf;
	}
	public static function convertSize(value:Object,ref100pc:Number):Number {
		if (value is Number) return Number(value);
		value = ''+value;
		if (value.indexOf('%') == value.length-1) {
			return Number(value.substr(0,value.length-1))*ref100pc/100;
		}
		return Number(value);
	}
	public static function setProperties(e:Object,options:Object,special:Object=null):Object {
		if (options) for (var key:String in options) {
			if (options.hasOwnProperty(key) && key in e) {
				var spc:Function = special ? special[key] as Function : null;
				var value:*     = options[key];
				e[key] = spc != null ? spc(value) : value;
			}
		}
		return e;
	}

}
}


code:
/**
 * Coded by aimozg on 27.08.2017.
 */
package coc.view.charview {
import coc.view.*;
import coc.view.charview.LayerPart;
import coc.xlogic.Compiler;
import coc.xlogic.Statement;

public class CharViewCompiler extends Compiler{
	private var charview:CharView;
	public function CharViewCompiler(charview:CharView) {
		this.charview = charview;
	}

	override protected function unknownTag(tag:String, x:XML):Statement {
		switch (tag){
			case 'show':
				return new LayerPart(charview.composite, x.@part, true);
			case 'hide':
				return new LayerPart(charview.composite, x.@part, false);
		}
		return super.unknownTag(tag, x);
	}
}
}


code:
/**
 * Coded by aimozg on 27.08.2017.
 */
package coc.view.charview {
import classes.BodyParts.Antennae;
import classes.BodyParts.Arms;
import classes.BodyParts.Beard;
import classes.BodyParts.Butt;
import classes.BodyParts.Claws;
import classes.BodyParts.Ears;
import classes.BodyParts.Eyes;
import classes.BodyParts.Face;
import classes.BodyParts.Gills;
import classes.BodyParts.Hair;
import classes.BodyParts.Hips;
import classes.BodyParts.Horns;
import classes.BodyParts.LowerBody;
import classes.BodyParts.Neck;
import classes.BodyParts.Piercing;
import classes.BodyParts.RearBody;
import classes.BodyParts.Skin;
import classes.BodyParts.Tail;
import classes.BodyParts.Tongue;
import classes.BodyParts.UnderBody;
import classes.BodyParts.Wings;
import classes.Creature;
import classes.lists.BreastCup;
import classes.lists.Gender;

import coc.view.*;
import coc.xlogic.ExecContext;

/**
 * Evaluation context
 */
public class CharViewContext extends ExecContext {
	private var charview:CharView;
	public function CharViewContext(charview:CharView, character:*) {
		this.charview = charview;
		pushScope(character);
		// Globals used in model.xml
		pushScope({
			Antennae : Antennae,
			Arms     : Arms,
			Beard    : Beard,
			BreastCup: BreastCup,
			Butt     : Butt,
			Claws    : Claws,
			Ears     : Ears,
			Eyes     : Eyes,
			Face     : Face,
			Gender   : Gender,
			Gills    : Gills,
			Hair     : Hair,
			Hips     : Hips,
			Horns    : Horns,
			LowerBody: LowerBody,
			Neck     : Neck,
			Pattern  : Pattern,
			Piercing : Piercing,
			RearBody : RearBody,
			Skin     : Skin,
			Tail     : Tail,
			Tongue   : Tongue,
			UnderBody: UnderBody,
			Wings    : Wings
		});
		// Intermod compatibility layer
		// - objects not present in character but required in model.xml scripts should be listed here
		var creature:Creature = character as Creature;
		if (creature) {
			pushScope({
				hairLength: creature.hair.length,
				hairType: creature.hair.type,
				faceType: creature.face.type,
				lowerBody: creature.lowerBody.type,
				legCount: creature.lowerBody.legCount,
				tailType: creature.tail.type,
				tailCount: creature.tail.venom,
				skin: {
					type: creature.skin.type,
					base: {
						type: creature.skin.type,
						pattern: 0
					},
					coat: {
						type: creature.skin.type,
						pattern: 0
					}
				},
				hasPartialCoat: function():Boolean {
					return false;
				},
				hasPartialCoatOfType: function(...types):Boolean {
					return false;
				},
				hasFullCoatOfType: function(...types):Boolean {
					return types.indexOf(creature.skin.type) >= 0;
				}
			});
		}
	}
}
}

// EJ compatibility layer
class Pattern {
	public static const NONE:int                    = 0;
	public static const MAGICAL_TATTOO:int          = 1;
	public static const ORCA_UNDERBODY:int          = 2;
	public static const BEE_STRIPES:int             = 3;
	public static const TIGER_STRIPES:int           = 4;
	public static const BATTLE_TATTOO:int           = 5;
	public static const SPOTTED:int                 = 6;
	public static const LIGHTNING_SHAPED_TATTOO:int = 7;
}


code:
/**
 * Coded by aimozg on 06.08.2017.
 */
package coc.view.charview {
import flash.display.BitmapData;

public class CharViewSprite {
	public var bmp:BitmapData;
	public var dx:int;
	public var dy:int;
	public function CharViewSprite(
			bmp:BitmapData,
			dx:int,
			dy:int
	) {
		this.bmp = bmp;
		this.dx = dx;
		this.dy = dy;
	}
}
}


code:
/**
 * Coded by aimozg on 28.07.2017.
 */
package coc.view.charview {
import coc.script.Eval;

public class EvalPaletteProperty extends PaletteProperty{
	private var srcfn:Eval;

	public function EvalPaletteProperty(palette:Palette, name:String, defaultt:uint, lookupNames:/*String*/Array, expr:String) {
		super(palette,name,defaultt,lookupNames);
		this.srcfn = Eval.compile(expr);
	}

	public override function colorName(layerName:String, src:Object):* {
		return srcfn.call(src);
	}
}
}


code:
/**
 * Coded by aimozg on 06.01.2019.
 */
package coc.view.charview {
public interface IColorNameProvider {
	function getKeyColor(layerName:String,keyColorName:String):String;
}
}


code:
/**
 * Coded by aimozg on 28.07.2017.
 */
package coc.view.charview {
import classes.internals.Utils;

import coc.view.Color;

public class KeyColor {
	private var _src:uint;
	private var _base:String;
	private var tfs:/*String*/Array;
	public function KeyColor(src:uint, base:String, tf:String) {
		this._src  = src;
		this._base = base;
		if (tf) this.tfs = tf.split(";");
		else this.tfs = [];
	}
	public function transform(baseValue:uint):uint {
		var hsl:* = Color.toHsl(baseValue);
		for each (var tf:String in tfs) {
			var fn:Array               = tf.match(/^(\w+)\(([\d.,-]+)\)/);
			var fname:String           = fn ? fn[1] : undefined;
			var values:/*Number*/Array = fn ? fn[2].split(",") : [];
			switch (fname) {
				case "shift":
				case "h":
					hsl.h = hsl.h + values[0];
					break;
				case "shiftTo":
				case "hto":
					if (hsl.h > values[0]) {
						hsl.h -= values[1];
					} else {
						hsl.h += values[2];
					}
					break;
				case "saturate":
				case "s":
					hsl.s += values[0];
					break;
				case "desaturate":
					hsl.s -= values[0];
					break;
				case "darken":
					hsl.l -= values[0];
					break;
				case "lighten":
				case "l":
					hsl.l += values[0];
					break;
				default:
					trace("Error: invalid color transform '" + tf + "'");
					break;
			}
			hsl.h = hsl.h % 360;
			while (hsl.h < 0) hsl.h += 360;
			hsl.s = Utils.boundFloat(0, hsl.s, 100);
			hsl.l = Utils.boundFloat(0, hsl.l, 100);

		}
		return Color.fromHsl(hsl);
	}
	public function get src():uint {
		return _src;
	}
	public function get base():String {
		return _base;
	}
}
}


code:
/**
 * Coded by aimozg on 27.07.2017.
 */
package coc.view.charview {
import coc.view.composite.CompositeImage;
import coc.xlogic.ExecContext;
import coc.xlogic.Statement;

public class LayerPart extends Statement {
	private var name:String;
	private var image:CompositeImage;
	private var visible:Boolean;
	private var _prefix:Boolean;

	public function LayerPart(image:CompositeImage, name:String, visible:Boolean) {
		this.image = image;
		this.visible = visible;
		this.name = name;
		var pp:Array = name.match(/([^\/]+\/)\*/);
		if (pp) {
			this._prefix = true;
			this.name = pp[1];
		}
	}

	override public function execute(context:ExecContext):void {
		if (_prefix) {
			image.setMultiVisibility(name,visible);
		} else {
			image.setVisibility(name, visible);
		}
	}
}
}


code:
/**
 * Coded by aimozg on 06.01.2019.
 */
package coc.view.charview {
import coc.view.composite.IKeyColorProvider;

public class Palette implements IKeyColorProvider {
	private var _keyColorsList:/*KeyColor*/Array = [];
	private var _lookupObjects:Object            = {}; // { dict_name => { color_name => color_string } }
	private var _paletteProps:/*PaletteProperty*/Array = [];
	private var _character:Object                      = null;
	private var _cachedKeyColors:Object                = {};
	public function addKeyColor(src:uint,base:String,tf:String):void {
		this._keyColorsList.push(new KeyColor(src,base,tf));
	}
	public function addLookups(name:String,lookup:Object):void {
		_lookupObjects[name] = lookup;
	}
	public function addPaletteProperty(pp:PaletteProperty):void {
		this._paletteProps.push(pp);
	}
	/**
	 * @param propToColor { prop_name -> color_value }
	 * @return { key_color -> actual_color }
	 */
	protected function keyColorsFromProperties(propToColor:Object):Object {
		var keyColorsMap:Object = {};
		for each (var color:KeyColor in _keyColorsList) {
			keyColorsMap[color.src] = color.transform(propToColor[color.base]);
		}
		return keyColorsMap;
	}
	public function set character(value:Object):void {
		_character       = value;
		_cachedKeyColors = {};
	}
	public function get keyColorsList():Array {
		return _keyColorsList;
	}
	public function get lookupObjects():Object {
		return _lookupObjects;
	}
	// { key_color -> actual_color }
	public function allKeyColorsFor(layerName:String):Object {
		if (!_cachedKeyColors[layerName]) {
			var props:Object = {};
			for each (var property:PaletteProperty in _paletteProps) {
				props[property.name] = property.colorValue(layerName, _character);
			}
			_cachedKeyColors[layerName] = keyColorsFromProperties(props);
		}
		return _cachedKeyColors[layerName];
	}
	public function lookupColor(layername:String, propname:String, colorname:String):uint {
		for each (var property:PaletteProperty in _paletteProps) {
			if (property.name == propname) return property.lookup(layername, colorname);
		}
		return 0xfff000e0;
	}
}
}


code:
/**
 * Coded by aimozg on 06.01.2019.
 */
package coc.view.charview {
import coc.view.Color;
import coc.view.composite.IKeyColorProvider;

public class PaletteProperty {
	public var name:String;
	private var palette:Palette;
	private var defaultt:uint;
	private var lookupNames:/*String*/Array;
	public function PaletteProperty(palette:Palette, name:String, defaultt:uint, lookupNames:/*String*/Array) {
		this.palette = palette;
		this.name = name;
		this.defaultt = defaultt;
		this.lookupNames = lookupNames.slice(0);
	}
	public function colorName(layerName:String, src:Object):* {
		if (src is IColorNameProvider) return (src as IColorNameProvider).getKeyColor(layerName,name);
		if (name in src) return src[name];
		return undefined;
	}
	public function lookup(layerName:String,colorName:String):uint {
		if (colorName.charAt(0) == '$') return Color.convertColor(colorName.substr(1));
		for each (var ln:String in lookupNames) {
			var lookup:Object = palette.lookupObjects[ln];
			if (lookup && colorName in lookup) return Color.convertColor(lookup[colorName]);
		}
		return defaultt;
	}
	public function colorValue(layerName:String,src:Object):uint {
		var sv:String = String(colorName(layerName,src));
		return lookup(layerName,sv);
	}
}
}


code:
# Character Viewer

## Overview

TODO

#### Accessing in game

It is rendered under \[Appearance\]. Depending on the mod it's enabled:
* (Xianxia) In \[Main Menu\] > \[Settings\]
* (Revamp) In debug builds only

In debug mode setting on, external resources are reloaded every time you click \[Appearance\] ()so you can preview changes without restarting).

Also available in Body Part Editor (`D` `E` `B` `U` `G` > \[Hack Stuff\] > \[Body Part Editor\]).

## Contributing

#### Sprite editor (spred)

Located in `devTools/spred/spred.html` (open file in browser).

Used to test adjust sprite positions, body part combinations, colors.

On each character buttons are: zoom in/out, show layers, show colors

### Editing existing parts

To edit existing body part images, just edit corresponding spritemap image file and preview in game or **spred**.

To re-position body parts, open them in **spred**, select layer in the list, and drag around with mouse.
Save by clicking **"Save spritemaps"**, then **"Copy"** button (near **"Close"**). Select all `<spritemap>` tags in `model.xml` and Ctrl+V (replace with clipboard).

If the existing bounding box is too small, you have to reposition it somewhere and put new values in spritemap (see **Adding new parts**).

### Adding/editing colors

Textual colors, like "**fair** skin" are mapped to RGB colors through palette\[s\].
There can be many of them to have different colors for "**red** *hair*", and "**red** (*everything else*)".

Then these colors are used in sprites, replacing *key colors* (see `basic.png`, bottom right corner).

1. If adding new color, add a `<color name="newname">#RRGGBB</color>` line in the `<common>` or specific palette.
2. Open **spred**. Add that color to some preview characters.
3. Select it in the list to the right and adjust HSL values.
4. Click **Save palettes**, then **Copy** button. Select whole `<palette>` tag in `model.xml` and Ctrl+V.

### Adding new parts

1. Draw it on existing or new spritemap file. If you add a new image file, you have to add a `<spritemap file="path">` tag to it.
    1) if you're doing a close variation of existing body part, copy it and re-draw.
    2) if it doesn't fit bounding box of existing body part or is very different, just draw it anywhere and add a box.
2. Write down the bounding box coordinates. In Paint.NET this can be done by selecting its contents (with magic wand on transparent space or rectangular select tool) and using the coordinates in bottom status bar.
3. Add a `<cell rect="dx,dy,width,height" name="layer/variation" dx="0" dy="0"/>` line in `model.xml`. If copying existing bounding box, use its **width**, **height**, **dx**, **dy**.

    Also if you add a new layer, you need to add a `<layer name="layer"/>` tag under `<layers>` to set between which layers it is rendered.
4. Reload the **spred**. Add that body part to some chars, and adjust its position. This might be optional for existing bounding box if the place of attachment is the same.
5. When it looks good (and you did any changes), click **"Save spritemaps"**, then **"Copy"**. Select all `<spritemap>` tags in `model.xml` and Ctrl+V.

### Editing layer selection logic

TODO

### Editing key color logic

TODO

## Implementation details

### Initialization:

1. Body parts are cut from files according to `<spritemap>` definitions.
2. Expressions in `<palette>/<property src="...">` and `<logic>/...` are compiled.

### Rendering

1. `<logic>` is evaluated with the `game.player` as a context. The `<show>` and `<hide>` statements that are executed determine set of visible parts.
2. For every `<palette>/<property>`:
    1. Its `src` is evaluated with `game.player` as a context.
    2. The resulting value is mapped to RGB color through its own palette (`<palette>/<property>/<color>`), if not found, through the `<palette>/<common>` palette.
    3. The resulting RGB is saved under its `name`.
3. For every `<colorkeys>/<key>`:
    1. A corresponding (by `key.base = property.name`) palette property is found; its RGB value will be used.
    2. If `transform` is present, it is applied to the color value.
    3. The mapping from `key.src` RGB color to transformed RGB color is saved.
4. Each visible layer undergoes color replacement (key colors replaced with actual colors).
5. Resulting layers are blended according to their order in `<layers>`

### Accessing in code

TODO


code:
/**
 * Coded by aimozg on 10.07.2017.
 */
package coc.view.composite {
import coc.view.*;

import flash.display.BitmapData;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;

public class CompositeImage {
	private var list:/*CompositeLayer*/Array;
	private var visibility:Object;
	private var bmp:BitmapData;
	public function get width():int {
		return bmp.width;
	}
	public function get height():int {
		return bmp.height;
	}

	public function CompositeImage(width:uint, height:uint) {
		list       = [];
		visibility = {};
		bmp        = new BitmapData(width, height,true,0);
	}
	public function addLayer(name:String, src:BitmapData, dx:int,dy:int,visible:Boolean = true):void {
		if (name in visibility) removeLayer(name);
		list.unshift(new CompositeLayer(name, src,dx,dy));
		visibility[name] = visible;
	}
	public function removeLayer(name:String):void {
		for (var i:int = 0; i < list.length; i++) {
			if (list[i].name == name) {
				list.splice(i, 1);
				delete visibility[name];
				break;
			}
		}
	}
	public function replaceLayer(name:String, src:BitmapData, dx: int, dy: int):void {
		for (var i:int = 0; i < list.length; i++) {
			if (list[i].name == name) {
				list[i] = new CompositeLayer(name, src, dx, dy);
			}
		}
	}
	public function setMultiVisibility(prefix:String, visible:Boolean):void {
		for (var key:String in visibility) {
			if (key.indexOf(prefix)==0) visibility[key] = visible;
		}
	}
	public function setVisibility(name:String, visible:Boolean):void {
		visibility[name] = visible;
	}
	public function hideAll():void {
		for each (var layer:CompositeLayer in list) {
			visibility[layer.name] = false;
		}
	}
	public function draw(keyColorProvider:IKeyColorProvider):BitmapData {
		bmp.fillRect(bmp.rect, 0);
		for each (var layer:CompositeLayer in list) {
			if (visibility[layer.name]) {
				layer.setKeyColors(keyColorProvider.allKeyColorsFor(layer.name));
				var sx:int = 0,sy:int = 0;
				var sw:int = layer.width;
				var sh:int = layer.height;
				var dx:int = layer.dx;
				var dy:int = layer.dy;
				if (dx<0) {
					sx = -dx;
					dx = 0;
				}
				if (dy<0) {
					sy = -dy;
					dy = 0;
				}
				if (dx + sw > width) sw = width - dx;
				if (dy + sh > height) sh = height - dy;
				bmp.copyPixels(layer.draw(),
						new Rectangle(sx,sy,sw,sh),
						new Point(dx,dy),null,null,true);
			}
		}
		return bmp;
	}
}
}


code:
/**
 * Coded by aimozg on 11.07.2017.
 */
package coc.view.composite {
import flash.display.BitmapData;

internal class CompositeLayer {
	private var _name:String;
	private var src:BitmapData;
	private var dst:BitmapData;
	private var keyColors:Object;// uint color24 -> uint color24
	private var dirty:Boolean = true;
	public var dx:int;
	public var dy:int;

	public function get width():int {
		return src.width;
	}
	public function get height():int {
		return src.height;
	}

	public function CompositeLayer(name:String, src:BitmapData, dx:int, dy:int) {
		this._name     = name;
		this.src       = src;
		this.dx = dx;
		this.dy = dy;
		this.dst       = new BitmapData(src.width, src.height,true,0);
		this.keyColors = {};
		this.dst.draw(src);
	}

	public function get name():String {
		return _name;
	}
	public function setKeyColors(newKeyColors:Object):void {
		for (var kc:String in keyColors) {
			if (!(kc in newKeyColors)) {
				dirty = true;
				delete keyColors[kc];
			}
		}
		for (kc in newKeyColors) {
			if (!(kc in keyColors) || keyColors[kc] != newKeyColors[kc]) {
				dirty         = true;
				keyColors[kc] = newKeyColors[kc];
			}
		}
	}

	public function draw():BitmapData {
		if (dirty) doUpdate();
		return dst;
	}

	private function doUpdate():void {
		var kc:Object = keyColors;
		for (var y:uint = 0, height:int = src.height; y < height; y++) {
			for (var x:uint = 0, width:int = src.width; x < width; x++) {
				var pix:uint = src.getPixel32(x, y);
				var alpha:uint = pix&0xff000000;
				var rgb:uint = pix&0x00ffffff;
				if (rgb in kc) rgb = kc[rgb]&0x00ffffff;
				dst.setPixel32(x, y, alpha|rgb);
			}
		}
	}
}
}


code:
/**
 * Coded by aimozg on 06.01.2019.
 */
package coc.view.composite {
public interface IKeyColorProvider {
	/**
	 * @return uint color24 -> uint color24
	 */
	function allKeyColorsFor(layerName:String):Object;
}
}


code:
/**
 * Coded by aimozg on 06.01.2019.
 */
package coc.view.composite {
public class SimpleKeyColorProvider implements IKeyColorProvider {
	private var _keyColors:Object;

	/**
	 * @param keyColors uint color24 -> uint color24
	 */
	public function SimpleKeyColorProvider(keyColors:Object) {
		_keyColors = keyColors;
	}
	public function allKeyColorsFor(layerName:String):Object {
		return _keyColors;
	}
}
}


code:
/**
 * Coded by aimozg on 06.07.2018.
 */
package coc.xlogic {
public class CallStmt extends Statement{
	private var fn:Function;
	private var passContext:Boolean;
	public function CallStmt(fn:Function, passContext:Boolean =false) {
		this.fn = fn;
		this.passContext = passContext;
	}

	override public function execute(context:ExecContext):void {
		if (passContext) fn(context);
		else fn();
	}
}
}