Files
pixel.horse/src/ts/client/ponyDraw.ts
T

821 lines
30 KiB
TypeScript

import {
PonyEye, PonyState, PalettePonyInfo, PaletteSpriteSet, Palette, HeadAnimationFrame,
Eye, Iris, ColorExtraSets, ExpressionExtra, BodyAnimationFrame, DrawPonyOptions, Muzzle, BodyShadow, DrawOptions,
NoDraw, PaletteSpriteBatch, defaultDrawOptions, PonyStateFlags, PaletteManager, Matrix2D, HeadAnimationProperties,
getEyeOpenness, Expression, getMuzzleOpenness,
} from '../common/interfaces';
import { WHITE, SHINES_COLOR, FAR_COLOR, TRANSPARENT, fillToOutlineColor } from '../common/colors';
import { toInt, hasFlag, repeat, flatten, point } from '../common/utils';
import * as sprites from '../generated/sprites';
import * as offsets from '../common/offsets';
import { defaultHeadAnimation, defaultBodyFrame, defaultHeadFrame } from './ponyAnimations';
import { toWorldX, toWorldY } from '../common/positionUtils';
import {
frontHooves, PONY_WIDTH, PONY_HEIGHT, wings, chestBehind, tails, chest, neckAccessories, waistAccessories,
SLEEVED_ACCESSORIES, blinkFrames, flipIris, claws, Sets, backAccessories, SLEEVED_BACK_ACCESSORIES,
CHEST_ACCESSORIES_IN_FRONT, flipFaceAccessoryType, flipFaceAccessoryPattern, backLegSleeves,
NO_MANE_HEAD_ACCESSORIES, backHoovesInFront, frontHoovesInFront
} from './ponyUtils';
import { HEAD_ACCESSORY_OFFSETS, EAR_ACCESSORY_OFFSETS, EXTRA_ACCESSORY_OFFSETS } from '../common/offsets';
import { createMat2D, identityMat2D, translateMat2D, copyMat2D, rotateMat2D, scaleMat2D } from '../common/mat2d';
import { darkenForOutline } from '../common/ponyInfo';
type Batch = PaletteSpriteBatch;
type Info = Readonly<PalettePonyInfo>;
type State = Readonly<PonyState>;
type Options = Readonly<DrawPonyOptions>;
const holdingDrawOptions: DrawOptions = {
...defaultDrawOptions,
shadowColor: TRANSPARENT,
};
function checker(parts: number[]) {
const set = new Set(parts);
return (part: number) => set.has(part);
}
function partChecker(parts: number[]) {
const check = checker(parts);
return (set: PaletteSpriteSet | undefined) => set !== undefined && !!set.type && check(set.type);
}
const SHADOW_OX = 20;
const SHADOW_OY = 64;
const FAR_OX = -3;
const FAR_OY = -1;
const FAR_WING_OX = -4;
const FAR_WING_OY = 0;
const hasNoMane = checker(NO_MANE_HEAD_ACCESSORIES);
const behindBackAccessory = partChecker([1]);
const sleevedAccessory = partChecker(SLEEVED_ACCESSORIES);
const sleevedBackAccessory = partChecker(SLEEVED_BACK_ACCESSORIES);
const chestAccessoryInFront = partChecker(CHEST_ACCESSORIES_IN_FRONT);
const pointZero = point(0, 0);
const headFlipOffsetX = 35.5;
const headFlipOffsetY = 42;
const headTransform = createMat2D();
function clamp(value: number, min: number, max: number): number {
return value > min ? (value < max ? value : max) : min;
}
function at<T>(items: T[], index: any): T | undefined {
// if (DEVELOPMENT) {
// if (items.length === 0) {
// console.warn(`Empty array at: ${getStackLocation(2)} / ${getStackLocation(3)}`);
// } else if (index < 0 || index >= items.length) {
// console.warn(`Index out of range ${index} of 0..${items.length} at: ${getStackLocation(2)} / ${getStackLocation(3)}`);
// }
// }
return items[clamp(index | 0, 0, items.length - 1)];
}
function att<T>(items: T[] | undefined, index: any): T | undefined {
return items && items[clamp(index | 0, 0, items.length - 1)];
}
function atDef<T>(items: T[] | undefined, index: number, def: T): T {
return (items && items.length > 0 && index >= 0 && index < items.length) ? items[index | 0] : def;
}
export function getPonyAnimationFrame<T>({ frames }: { frames: T[] }, frame: number, defaultFrame: T): T {
return frames.length > 0 ? frames[Math.max(0, frame) % frames.length] : defaultFrame;
}
function getHeadXY(x: number, y: number, turned: boolean, frame: BodyAnimationFrame, headFrame: HeadAnimationFrame) {
const headOffset = at(offsets.headOffsets, frame.body)!;
const headX = x + frame.headX + (headFrame.headX * (turned ? -1 : 1)) + headOffset.x;
const headY = y + frame.headY + headFrame.headY + headOffset.y;
return { headX, headY };
}
export function getPonyHeadPosition(state: State, ponyX: number, ponyY: number) {
const frame = getPonyAnimationFrame(state.animation, state.animationFrame, defaultBodyFrame);
const headFrame = getPonyAnimationFrame(state.headAnimation || defaultHeadAnimation, state.headAnimationFrame, defaultHeadFrame);
const baseX = ponyX - PONY_WIDTH / 2;
const baseY = ponyY - PONY_HEIGHT;
const x = baseX + frame.bodyX;
const y = baseY + frame.bodyY;
const { headX, headY } = getHeadXY(x, y, state.headTurned, frame, headFrame);
return { x: headX, y: headY };
}
export function createHeadTransform(
originalTransform: Matrix2D | undefined, headX: number, headY: number, { headTilt, headTurned }: State
) {
if (originalTransform !== undefined) {
copyMat2D(headTransform, originalTransform);
} else {
identityMat2D(headTransform);
}
translateMat2D(headTransform, headTransform, headX + headFlipOffsetX, headY + headFlipOffsetY);
if (headTilt) {
rotateMat2D(headTransform, headTransform, headTilt * 0.1);
}
scaleMat2D(headTransform, headTransform, headTurned ? -1 : 1, 1);
translateMat2D(headTransform, headTransform, -headFlipOffsetX, -headFlipOffsetY);
return headTransform;
}
export function getHeadY(frame: BodyAnimationFrame, headFrame: HeadAnimationFrame): number {
const headOffset = offsets.headOffsets[frame.body];
return frame.bodyY + frame.headY + headFrame.headY + headOffset.y;
}
const defaultShadow: BodyShadow = { frame: 0, offset: 0 };
const hairOffsets = [
0, 0, 0, 0,
-1, -1, 0, 0,
0, 0, 0, -1,
-1, 0, 0, 0,
0, 0, 0, 0,
...repeat(100, 0),
];
function draw(options: Options, flag: NoDraw) {
if (TOOLS) {
return !hasFlag(options.no, flag);
} else {
return true;
}
}
const headOffsetsX = [0, 1, 1, 1, 1, 1, 0];
const headOffsetsY = [0, 0, 1, 1, 0, 0, 0];
let toys: (PaletteSpriteSet | undefined)[] = [];
export function initializeToys(paletteManager: PaletteManager) {
function set(type: number, pattern: number, colors: number[]): PaletteSpriteSet {
const palette = [
TRANSPARENT,
...flatten(colors.map(color => [color, darkenForOutline(fillToOutlineColor(color))]))
];
return { type, pattern, palette: paletteManager.add(palette) };
}
toys = [
undefined,
// hat
set(2, 0, [0xffffffff, 0xff2525ff]),
set(2, 0, [0xffffffff, 0x22ac22ff]),
set(2, 0, [0xffffffff, 0x2e58f4ff]),
set(2, 0, [0xffffffff, 0xff71ffff]),
// snowpony
set(3, 0, [0xffffffff, 0x000000ff, 0xff9100ff, 0xff0000ff]),
set(4, 0, [0xffffffff, 0x000000ff, 0xff9100ff, 0xff0000ff]),
set(4, 0, [0x404040ff, 0xff0000ff, 0x000000ff, 0xff0000ff]),
// gift
set(6, 0, [0xcf1717ff, 0xecd132ff]),
set(6, 0, [0xdfc588ff, 0xe7559bff]),
set(6, 0, [0x7fc484ff, 0x4a79daff]),
set(6, 0, [0xd56a69ff, 0x62ab64ff]),
set(6, 0, [0xe586dfff, 0x9553c1ff]),
// hanging thing
set(5, 0, [0x91622fff, 0xc02455ff, 0x429a51ff, 0xb9c0d8ff, 0xffd94fff, 0xeca242ff]), // bell
set(7, 0, [0x91622fff, 0xc02455ff, 0x429a51ff, 0xb9c0d8ff, 0xc0ccc4ff]), // mistletoe
set(17, 0, [0x91622fff, 0xc02455ff, 0x429a51ff, 0x000000ff, 0xe7b86fff, 0x3f1d0fff]), // cookie
set(10, 0, [0x91622fff, 0xc02455ff, 0x429a51ff, 0x000000ff]), // spider
// teddy
set(8, 0, [0xa86230ff, 0xdfbe8bff, TRANSPARENT, TRANSPARENT]), // brown
set(8, 0, [0xa86230ff, 0xdfbe8bff, 0xffa500ff, 0xffffffff]), // brown angel
set(8, 0, [0x474444ff, 0x96623eff, TRANSPARENT, TRANSPARENT]), // black
set(8, 0, [0x474444ff, 0x96623eff, 0xffa500ff, 0xffffffff]), // black angel
set(9, 1, [0xa86230ff, 0xdfbe8bff, 0xff0000ff, TRANSPARENT, 0xf5f5f5ff]), // brown clothes
set(9, 1, [0x474444ff, 0x96623eff, 0xff0000ff, TRANSPARENT, 0xf5f5f5ff]), // black clothes
set(9, 0, [0xdce5edff, 0xffffffff, 0xff0000ff, 0xdfbe8bff, 0x645137ff]), // white santa
// xmas tree
set(11, 0, [0x1a9b2fff, 0x56c7ffff, 0xde4d68ff, 0xf1d224ff]),
set(11, 1, [0x1a9b2fff, 0xf1d224ff, 0x1a9b2fff, 0xde4d68ff]),
// deer
set(12, 0, [0x7b4b24ff, 0xcf0e0eff, 0xbfaa8cff]),
set(16, 0, [0x7b4b24ff, 0xcf0e0eff, 0xbfaa8cff, 0xffffffff, 0x56c7ffff, 0xf1d224ff]),
// candy horns
set(13, 0, [0xffffffff, 0xff1b1bff, TRANSPARENT, TRANSPARENT]), // one
set(13, 0, [0xffffffff, 0xff1b1bff, 0xffffffff, 0xff1b1bff]), // two
set(13, 0, [0x58df6aff, 0xffffffff, 0x3387e9ff, 0xffffffff]), // two (alt)
// star
set(14, 0, [0xffd94fff, 0xeca242ff]),
// halo
set(15, 0, [0xffd94fff, 0xeca242ff]),
];
if (DEVELOPMENT && toys.length > 33) {
console.error('too many toys', toys.length);
}
}
// const zeroPoint = point(0, 0);
const wakes = [
{ ox: 21, oy: 60, behind: sprites.pony_wake_4, front: sprites.pony_wake_3 },
{ ox: 24, oy: 60, behind: sprites.pony_wake_6, front: sprites.pony_wake_5 },
{ ox: 18, oy: 51, behind: sprites.pony_wake_2, front: sprites.pony_wake_1 },
];
// swimming effects of different size; add value per tail
const wakeIndices = [0, 2, 1, 0, 2, 2, 2, 0, 2, 2, 2, 1, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 1];
function getWakeIndex(info: Info) {
const tail = info.tail && info.tail.type || 0;
return wakeIndices[tail];
}
export function drawPony(batch: Batch, info: Info, state: State, ponyX: number, ponyY: number, options: Options) {
const frame = getPonyAnimationFrame(state.animation, state.animationFrame, defaultBodyFrame);
const headFrame = getPonyAnimationFrame(state.headAnimation || defaultHeadAnimation, state.headAnimationFrame, defaultHeadFrame);
const headAnimationProperties = (state.headAnimation || defaultHeadAnimation).properties;
const baseX = ponyX - PONY_WIDTH / 2;
const baseY = ponyY - PONY_HEIGHT;
const x = baseX + frame.bodyX;
const y = baseY + frame.bodyY;
const body = frame.body;
const { headX, headY } = getHeadXY(x, y, state.headTurned, frame, headFrame);
const frontLegOffset = at(offsets.frontLegOffsets, body)!;
const backLegOffset = at(offsets.backLegOffsets, body)!;
const wingOffset = at(offsets.wingOffsets, body)!;
const chestOffset = at(offsets.chestAccessoryOffsets, body)!;
const chestX = x + chestOffset.x;
const chestY = y + chestOffset.y;
const shadow = atDef(state.animation.shadow, state.animationFrame, defaultShadow);
const backOffset = at(offsets.backAccessoryOffsets, body)!;
const wing = at(wings, frame.wing);
const flipped = options.flipped;
const headOffset = clamp(state.headTurn, 0, headOffsetsX.length - 1);
const headOffsetX = headOffsetsX[headOffset];
const headOffsetY = headOffsetsY[headOffset];
const headTotalY = headY + headOffsetY;
const headTransform = createHeadTransform(undefined, headX + headOffsetX, headTotalY, state);
const headCropY = 42 - ((headTotalY + headFlipOffsetY) - baseY);
const cropW = 80;
const cropH = 65;
const shadowX = baseX + shadow.offset + SHADOW_OX;
const shadowY = baseY + SHADOW_OY;
const wake = wakes[getWakeIndex(info)];
const wakeX = baseX + wake.ox;
const wakeY = baseY + wake.oy;
const wakeFrame = Math.floor(options.gameTime * 7 / 1000) % wake.behind.frames.length;
const swimming = options.swimming;
if (swimming) {
batch.drawSprite(wake.behind.frames[wakeFrame], WHITE, info.waterPalette, wakeX, wakeY);
batch.crop(-40, -70, cropW, cropH);
}
// selection
if (options.selected) {
const sprite = at(sprites.ponySelections, shadow.frame);
sprite && batch.drawSprite(sprite, WHITE, info.defaultPalette, shadowX, shadowY);
}
// shadow
if (options.shadow) {
const sprite = at(sprites.ponyShadows, shadow.frame);
sprite && batch.drawSprite(sprite, options.shadowColor, info.defaultPalette, shadowX, shadowY);
}
// head accessory
const bounce = BETA && options.bounce;
const maneOffsetY = bounce ? hairOffsets[state.animationFrame] : 0;
const maneBehindOffsetY = bounce ? hairOffsets[state.animationFrame] : 0;
const hatOffsetY = maneBehindOffsetY;
let hatOffset = at(HEAD_ACCESSORY_OFFSETS, info.mane ? info.mane.type : 0)!;
const noMane = !info.mane || hasNoMane(info.mane.type);
// temp front layer hat test
// if (info.headAccessory !== undefined && info.headAccessory.type === 20) {
// hatOffset = zeroPoint;
//}
if (draw(options, NoDraw.Behind)) {
// far wing
drawSet(batch, wing, info.wings, x + FAR_WING_OX + wingOffset.x, y + FAR_WING_OY + wingOffset.y, FAR_COLOR);
batch.save();
batch.multiplyTransform(headTransform);
if (swimming) {
// batch.drawRect(0xffff0066, 0, headCropY, cropW, cropH);
batch.crop(0, headCropY, cropW, cropH);
}
if (noMane) {
drawSet(batch, sprites.headAccessoriesBehind, info.headAccessory, hatOffset.x, hatOffset.y + hatOffsetY, WHITE);
}
if (draw(options, NoDraw.FarEar)) {
drawSet(batch, sprites.earAccessoriesBehind, info.earAccessory, 0, 0, WHITE);
}
}
if (draw(options, NoDraw.Body) && draw(options, NoDraw.FarEar)) {
drawSet(batch, sprites.earsFar, info.ears, 0, 0, draw(options, NoDraw.FarEarShade) ? FAR_COLOR : WHITE);
}
if (draw(options, NoDraw.Behind)) {
drawSet(batch, sprites.hornsBehind, info.horn, 0, 0, WHITE);
if (!noMane) {
drawSet(batch, sprites.headAccessoriesBehind, info.headAccessory, hatOffset.x, hatOffset.y + hatOffsetY, WHITE);
}
drawSet(batch, sprites.backBehindManes, info.backMane, 0, maneBehindOffsetY, WHITE);
if (!state.headTurned) {
drawSet(batch, sprites.behindManes, info.mane, 0, maneBehindOffsetY, WHITE);
}
batch.restore();
// chest accessory behind
drawSet(batch, chestBehind[body], info.chestAccessory, chestX, chestY, WHITE);
}
// legs
const behindX = x + FAR_OX;
const behindY = y + FAR_OY;
const hasTailAccessory = behindBackAccessory(info.backAccessory);
const hasSleeves = sleevedAccessory(info.chestAccessory);
const hasBackSleeves = sleevedBackAccessory(info.backAccessory);
const frontBehindX = behindX + frontLegOffset.x + frame.frontFarLegX;
const frontBehindY = behindY + frontLegOffset.y + frame.frontFarLegY;
const backBehindX = behindX + backLegOffset.x + frame.backFarLegX;
const backBehindY = behindY + backLegOffset.y + frame.backFarLegY;
// far leg back
if (draw(options, NoDraw.BackFarLeg)) {
drawLeg(batch, backBehindX, backBehindY, frame.backFarLeg, sprites.backLegs,
sprites.backLegHooves, sprites.backLegAccessories, info.backLegs,
flipped ? info.backLegAccessory : info.backLegAccessoryRight, info.backHooves, backHoovesInFront, FAR_COLOR,
undefined, false, 0, 0);
}
// far leg front
if (draw(options, NoDraw.FrontFarLeg)) {
drawLeg(batch, frontBehindX, frontBehindY, frame.frontFarLeg, sprites.frontLegs,
frontHooves, sprites.frontLegAccessories, info.frontLegs,
flipped ? info.frontLegAccessory : info.frontLegAccessoryRight, info.frontHooves, frontHoovesInFront, FAR_COLOR,
undefined, false, 0, 0);
}
// far leg back sleeve
if (draw(options, NoDraw.FarSleeves) && hasBackSleeves) {
drawSet(batch, at(sprites.backLegSleeves, frame.backFarLeg), info.backAccessory, backBehindX, backBehindY, FAR_COLOR);
}
// far leg front sleeve
if (draw(options, NoDraw.FarSleeves) && hasSleeves) {
drawSet(batch, at(sprites.frontLegSleeves, frame.frontFarLeg), info.sleeveAccessory, frontBehindX, frontBehindY, FAR_COLOR);
}
// tail
const tailOffset = at(offsets.tailOffsets, body)!;
const tailX = x + tailOffset.x;
const tailY = y + tailOffset.y;
const failFrame = hasFlag(state.flags, PonyStateFlags.CurlTail) ? 1 : frame.tail;
drawSet(batch, at(tails, failFrame), info.tail, tailX, tailY, WHITE);
// tail accessory
if (draw(options, NoDraw.BackAccessory) && hasTailAccessory) {
drawSet(batch, backAccessories[body], info.backAccessory, x + backOffset.x, y + backOffset.y, WHITE);
}
// body
if (draw(options, NoDraw.Body) && draw(options, NoDraw.BodyOnly)) {
drawSet(batch, sprites.body[body], info.body, x, y, WHITE);
}
// neck accessory
const frontNeckAccessory = true; // hasPart(info.neckAccessory, FRONT_NECK_ACCESSORIES);
// if (!frontNeckAccessory) {
// drawSpriteSet(context, neckAccessories[frame.body], info.neckAccessory, x, y);
// }
const frontX = x + frontLegOffset.x + frame.frontLegX;
const frontY = y + frontLegOffset.y + frame.frontLegY;
const backX = x + backLegOffset.x + frame.backLegX;
const backY = y + backLegOffset.y + frame.backLegY;
const cmOffset = at(offsets.cmOffsets, body)!;
const hooves = (TOOLS && options.useAllHooves) ? sprites.frontLegHooves : frontHooves;
// close legs back
if (draw(options, NoDraw.BackLeg)) {
drawLeg(
batch, backX, backY, frame.backLeg, sprites.backLegs, sprites.backLegHooves, sprites.backLegAccessories,
info.backLegs, flipped ? info.backLegAccessoryRight : info.backLegAccessory, info.backHooves, backHoovesInFront, WHITE,
info.cmPalette, flipped && !!info.cmFlip, x + cmOffset.x, y + cmOffset.y);
}
// back accessory
if (draw(options, NoDraw.BackAccessory) && !hasTailAccessory) {
drawSet(batch, backAccessories[body], info.backAccessory, x + backOffset.x, y + backOffset.y, WHITE);
}
// close leg back sleeves
if (draw(options, NoDraw.CloseSleeves) && hasBackSleeves) {
drawSet(batch, at(backLegSleeves, frame.backLeg), info.backAccessory, backX, backY, WHITE);
}
const isChestAccessoryInFront = chestAccessoryInFront(info.chestAccessory);
// chest accessory
if (!isChestAccessoryInFront && draw(options, NoDraw.Front)) {
drawSet(batch, chest[body], info.chestAccessory, chestX, chestY, WHITE);
}
// close legs front
if (draw(options, NoDraw.FrontLeg)) {
drawLeg(
batch, frontX, frontY, frame.frontLeg, sprites.frontLegs, hooves, sprites.frontLegAccessories,
info.frontLegs, flipped ? info.frontLegAccessoryRight : info.frontLegAccessory, info.frontHooves, frontHoovesInFront, WHITE,
undefined, false, 0, 0);
}
// close legs front sleeves
if (draw(options, NoDraw.CloseSleeves) && hasSleeves) {
drawSet(batch, at(sprites.frontLegSleeves, frame.frontLeg), info.sleeveAccessory, frontX, frontY, WHITE);
}
// chest accessory
if (isChestAccessoryInFront && draw(options, NoDraw.Front)) {
drawSet(batch, chest[body], info.chestAccessory, chestX, chestY, WHITE);
}
// close legs back (2)
if (draw(options, NoDraw.BackLeg)) {
drawLeg(
batch, backX, backY, frame.backLeg, sprites.backLegs2, sprites.backLegHooves2, sprites.backLegAccessories2,
info.backLegs, flipped ? info.backLegAccessoryRight : info.backLegAccessory, info.backHooves, backHoovesInFront, WHITE,
undefined, false, 0, 0);
}
// close legs back sleeves (2)
if (draw(options, NoDraw.CloseSleeves) && hasBackSleeves) {
drawSet(batch, at(sprites.backLegSleeves2, frame.backLeg), info.backAccessory, backX, backY, WHITE);
}
// neck accessory
if (frontNeckAccessory) {
const neckOffset = at(offsets.neckAccessoryOffsets, body)!;
drawSet(batch, neckAccessories[body], info.neckAccessory, x + neckOffset.x, y + neckOffset.y, WHITE);
}
// waist accessory
const waistFrame = frame.wing > 2 ? 16 : body;
const waistOffset = at(offsets.waistAccessoryOffsets, waistFrame)!;
drawSet(batch, waistAccessories[waistFrame], info.waistAccessory, x + waistOffset.x, y + waistOffset.y, WHITE);
// wings
drawSet(batch, wing, info.wings, x + wingOffset.x, y + wingOffset.y, WHITE);
// head
const headTurned = state.headTurned;
const headFlip = headTurned ? !flipped : flipped;
const headSprite = headFlip ? sprites.head0[frame.head] : sprites.head1[frame.head];
if (swimming) {
batch.clearCrop();
}
batch.save();
batch.multiplyTransform(headTransform);
if (swimming) {
batch.crop(0, headCropY, cropW, cropH);
}
if (headTurned) {
drawSet(batch, sprites.behindManes, info.mane, 0, maneBehindOffsetY, WHITE);
}
drawHead(batch, info, 0, 0, headSprite, headFrame, headAnimationProperties, state, options, headFlip, maneOffsetY);
drawSet(batch, sprites.headAccessories, info.headAccessory, hatOffset.x, hatOffset.y + hatOffsetY, WHITE);
batch.restore();
if (swimming) {
batch.drawSprite(wake.front.frames[wakeFrame], WHITE, info.waterPalette, wakeX, wakeY);
}
}
export function drawHead(
batch: Batch, info: Info, x: number, y: number, headSprites: ColorExtraSets, headFrame: HeadAnimationFrame,
animationProperties: HeadAnimationProperties, { blinkFrame, expression, holding, blushColor, drawFaceExtra }: State,
options: Options, flip: boolean, maneOffsetY: number,
) {
const extraOffset = at(EXTRA_ACCESSORY_OFFSETS, info.mane && info.mane.type) || pointZero;
const extraX = x + extraOffset.x;
const extraY = y + extraOffset.y;
const toy = att(toys, options.toy);
if (toy !== undefined) {
drawSet(batch, sprites.extraAccessoriesBehind, toy, extraX, extraY, WHITE);
} else if (options.extra && draw(options, NoDraw.Behind)) {
drawSet(batch, sprites.extraAccessoriesBehind, info.extraAccessory, extraX, extraY, WHITE);
}
if (draw(options, NoDraw.Body)) {
if (draw(options, NoDraw.Head)) {
drawSet(batch, headSprites, info.head, x, y, WHITE);
}
let eyeLeftBase = -1;
let eyeRightBase = -1;
let irisLeft = Iris.Forward;
let irisRight = Iris.Forward;
if (expression !== undefined) {
if (hasFlag(expression.extra, ExpressionExtra.Blush)) {
batch.drawSprite(sprites.blush, blushColor, info.defaultPalette, x, y);
}
eyeLeftBase = expression.left;
eyeRightBase = expression.right;
irisLeft = expression.leftIris;
irisRight = expression.rightIris;
// make sure eyes are closed if sleeping
if (hasFlag(expression.extra, ExpressionExtra.Zzz)) {
if (getEyeOpenness(eyeLeftBase) !== 0) {
eyeLeftBase = Eye.Closed;
}
if (getEyeOpenness(eyeRightBase) !== 0) {
eyeRightBase = Eye.Closed;
}
}
}
const eyeRight = getEyeFrame(info.eyeOpennessRight || 1, eyeRightBase, headFrame.right, animationProperties, blinkFrame);
const eyeLeft = getEyeFrame(info.eyeOpennessLeft || 1, eyeLeftBase, headFrame.left, animationProperties, blinkFrame);
const eyeFrameLeft = flip ? eyeRight : eyeLeft;
const eyeFrameRight = flip ? eyeLeft : eyeRight;
const eyeColorLeft = flip ? info.eyeColorRight : info.eyeColorLeft;
const eyeColorRight = flip ? info.eyeColorLeft : info.eyeColorRight;
const eyePaletteLeft = flip ? info.eyePalette : info.eyePaletteLeft;
const eyePaletteRight = flip ? info.eyePaletteLeft : info.eyePalette;
const eyeIrisLeft = flip ? flipIris(irisRight) : irisLeft;
const eyeIrisRight = flip ? flipIris(irisLeft) : irisRight;
const eyeLeftSprites = sprites.eyeLeft;
const eyeRightSprites = sprites.eyeRight;
if (draw(options, NoDraw.Eyes)) {
drawEye(
batch, att(at(eyeLeftSprites, eyeFrameLeft), info.eyelashes),
eyeIrisLeft, info, eyeColorLeft, eyePaletteLeft, x, y);
drawEye(
batch, att(at(eyeRightSprites, eyeFrameRight), info.eyelashes),
eyeIrisRight, info, eyeColorRight, eyePaletteRight, x, y);
}
}
if (draw(options, NoDraw.Front)) {
drawSet(batch, sprites.facialHairBehind, info.facialHair, x, y, WHITE);
}
if (drawFaceExtra !== undefined) {
drawFaceExtra(batch);
}
const faceAccessory = info.faceAccessory;
let faceAccessoryType = 0;
let faceAccessoryPattern = 0;
if (faceAccessory !== undefined) {
faceAccessoryType = flip ? flipFaceAccessoryType(faceAccessory.type) : faceAccessory.type;
faceAccessoryPattern = flip ? flipFaceAccessoryPattern(faceAccessoryType, faceAccessory.pattern) : faceAccessory.pattern;
if (draw(options, NoDraw.FaceAccessory1)) {
drawTypePattern(
batch, sprites.faceAccessories, faceAccessoryType, faceAccessoryPattern,
faceAccessory.palette, faceAccessory.extraPalette, x, y, WHITE);
// if (info.faceAccessoryExtraPalette) {
// drawTypePattern(
// batch, sprites.faceAccessoriesExtra, faceAccessoryType, faceAccessoryPattern,
// info.faceAccessoryExtraPalette, x, y, WHITE);
// }
}
}
if (draw(options, NoDraw.Body) && draw(options, NoDraw.Nose)) {
const muzzle = getMouthFrame(!!holding, expression, headFrame.mouth, info.muzzle, animationProperties);
const noses = at(sprites.noses, muzzle);
const nose = att(noses, info.nose && info.nose.type)![0];
nose.mouth && batch.drawSprite(nose.mouth, WHITE, info.defaultPalette, x, y);
if (holding !== undefined && holding.draw !== undefined) {
holding.x = toWorldX(x + toInt(holding.pickableX));
holding.y = toWorldY(y + toInt(holding.pickableY));
holding.draw(batch, holdingDrawOptions);
}
drawSet(batch, noses, info.nose, x, y, WHITE);
if (info.fangs && nose.fangs) {
batch.drawSprite(nose.fangs, WHITE, info.defaultPalette, x, y);
}
}
if (draw(options, NoDraw.Front2)) {
drawSet(batch, sprites.facialHair, info.facialHair, x, y, WHITE);
}
// temp front layer hat test
// const skipTopAndFrontMane = info.headAccessory !== undefined && info.headAccessory.type === 20;
if (draw(options, NoDraw.FrontMane)) {
drawSet(batch, sprites.backFrontManes, info.backMane, x, y + maneOffsetY, WHITE);
}
if (draw(options, NoDraw.TopMane) /* && !skipTopAndFrontMane*/ ) {
drawSet(batch, sprites.topManes, info.mane, x, y, WHITE);
}
if (toy !== undefined) {
drawSet(batch, sprites.extraAccessories, toy, extraX, extraY, WHITE);
} else if (options.extra && draw(options, NoDraw.Front)) {
drawSet(batch, sprites.extraAccessories, info.extraAccessory, extraX, extraY, WHITE);
}
if (draw(options, NoDraw.Front)) {
drawSet(batch, sprites.horns, info.horn, x, y, WHITE);
}
if (draw(options, NoDraw.Body) && draw(options, NoDraw.CloseEar) && !options.noEars) {
drawSet(batch, sprites.ears, info.ears, x, y, WHITE);
}
if (faceAccessory !== undefined && draw(options, NoDraw.FaceAccessory2)) {
drawTypePattern(
batch, sprites.faceAccessories2, faceAccessoryType, faceAccessoryPattern,
faceAccessory.palette, faceAccessory.extraPalette, x, y, WHITE);
// if (info.faceAccessoryExtraPalette) {
// drawTypePattern(
// batch, sprites.faceAccessories2Extra, faceAccessoryType, faceAccessoryPattern,
// info.faceAccessoryExtraPalette, x, y, WHITE);
// }
}
const earAccessoryOffset = at(EAR_ACCESSORY_OFFSETS, info.ears && info.ears.type)!;
const frontEarAccessory = false; // info.earAccessory !== undefined && info.earAccessory.type === 13;
if (!frontEarAccessory && draw(options, NoDraw.Front) && draw(options, NoDraw.CloseEar)) {
drawSet(batch, sprites.earAccessories, info.earAccessory, x + earAccessoryOffset.x, y + earAccessoryOffset.y, WHITE);
}
if (draw(options, NoDraw.FrontMane) /* && !skipTopAndFrontMane */) {
drawSet(batch, sprites.frontManes, info.mane, x, y + maneOffsetY, WHITE);
}
if (frontEarAccessory && draw(options, NoDraw.Front) && draw(options, NoDraw.CloseEar)) {
drawSet(batch, sprites.earAccessories, info.earAccessory, x + earAccessoryOffset.x, y + earAccessoryOffset.y, WHITE);
}
}
function drawLeg(
batch: Batch, x: number, y: number, frame: number, leg: Sets, hoof: Sets, sock: Sets,
legSet: PaletteSpriteSet | undefined, sockSet: PaletteSpriteSet | undefined, hoofSet: PaletteSpriteSet | undefined,
hoovesInFront: boolean[], color: number, cmPalette: Palette | undefined, cmFlip: boolean, cmX: number, cmY: number
) {
const hoofInFront = hoofSet !== undefined && !!hoovesInFront[hoofSet.type];
drawSet(batch, at(leg, frame), legSet, x, y, color);
if (!hoofInFront) {
drawSet(batch, at(hoof, frame), hoofSet, x, y, color);
}
// CM
if (cmPalette !== undefined) {
const cm = cmFlip ? sprites.cmsFlip : sprites.cms;
batch.drawSprite(cm, WHITE, cmPalette, cmX, cmY);
}
drawSet(batch, at(sock, frame), sockSet, x, y, color);
if (hoofInFront) {
const hasClaws = hoofSet && hoofSet.type === 3 && hoof === frontHooves;
const hasSocks = !!(sockSet && sockSet.type > 0);
const hoofSprites = (hasClaws && hasSocks) ? claws : hoof;
drawSet(batch, at(hoofSprites, frame), hoofSet, x, y, color);
}
}
function getEyeFrame(base: Eye, expression: Eye, anim: Eye, animationProperties: HeadAnimationProperties, blinkFrame: number) {
const frame = expression === -1 ? base : expression;
if (anim !== -1) {
if (hasFlag(animationProperties, HeadAnimationProperties.DontIncreaseEyeOpenness)) {
if (getEyeOpenness(anim) > getEyeOpenness(frame)) {
return frame;
}
}
return anim;
}
const blink = blinkFrames[frame];
if (blinkFrame > 1 && blink) {
const frameOffset = 6 - blinkFrame;
if (frameOffset < blink.length) {
return blink[blink.length - frameOffset - 1];
}
}
return frame;
}
function getMouthFrame(holding: boolean, expression: Expression | undefined, headFrameMuzzle: Muzzle,
currentMuzzle: Muzzle | undefined, properties: HeadAnimationProperties) {
if (holding) {
return Muzzle.Smile;
}
let applyMuzzle = currentMuzzle;
if (expression) {
applyMuzzle = expression.muzzle;
}
if (headFrameMuzzle !== -1) {
if (applyMuzzle && hasFlag(properties, HeadAnimationProperties.DontDecreaseMouthOpenness)) {
if (getMuzzleOpenness(headFrameMuzzle) < getMuzzleOpenness(applyMuzzle)) {
return applyMuzzle;
}
}
return headFrameMuzzle;
}
return applyMuzzle;
}
function drawEye(
batch: Batch, eye: PonyEye | undefined, iris: Iris, info: Info, palette: Palette | undefined, eyePalette: Palette,
x: number, y: number
) {
if (eye !== undefined) {
if (info.eyeshadow === true) {
eye.shadow && batch.drawSprite(eye.shadow, WHITE, info.eyeshadowColor, x, y);
eye.shine && batch.drawSprite(eye.shine, SHINES_COLOR, info.defaultPalette, x, y);
}
eye.base && batch.drawSprite(eye.base, WHITE, eyePalette, x, y);
const sprite = at(eye.irises, iris);
sprite && batch.drawSprite(sprite, WHITE, palette, x, y);
}
}
function drawSet(
batch: Batch, sprites: ColorExtraSets, set: PaletteSpriteSet | undefined, x: number, y: number, tint: number
) {
if (set !== undefined) {
const patterns = att(sprites, set.type);
if (patterns !== undefined) {
const patternSprite = at(patterns, set.pattern);
if (patternSprite !== undefined) {
batch.drawSprite(patternSprite.color, tint, set.palette, x, y);
}
}
}
}
function drawTypePattern(
batch: Batch, sprites: ColorExtraSets, type: number, pattern: number, palette: Palette, extraPalette: Palette | undefined,
x: number, y: number, tint: number
) {
const patterns = att(sprites, type);
if (patterns !== undefined) {
const patternSprite = at(patterns, pattern);
if (patternSprite !== undefined) {
batch.drawSprite(patternSprite.color, tint, palette, x, y);
if (patternSprite.extra !== undefined && extraPalette !== undefined) {
batch.drawSprite(patternSprite.extra, WHITE, extraPalette, x, y);
}
}
}
}