"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getHitbubbleCount = exports.Animation = exports.tweenKeyframes = exports.Hitbubble = exports.DefaultColors = exports.addHitbubble = exports.clearHitbubbles = exports.hitbubbles = exports.BubbleReference = exports.Hurtbubble = exports.hurtbubbles = exports.Heartbeat = exports.statusFactory = exports.DelayHitStatus = exports.Status = exports.EventSystem = exports.GrabDirection = exports.AnimationType = exports.HitbubbleType = exports.HurtbubbleState = exports.HitbubbleFlag = void 0;
const gl_matrix_1 = require("gl-matrix");
const color_1 = require("./color");
const easing_1 = require("./easing");
const engine_1 = require("./engine");
const math_1 = require("./math");
const model_1 = require("./model");
const rendering_1 = require("./rendering");
const utils_1 = require("./utils");
var HitbubbleFlag;
(function (HitbubbleFlag) {
    HitbubbleFlag[HitbubbleFlag["skip"] = 2] = "skip";
    HitbubbleFlag[HitbubbleFlag["fixed"] = 4] = "fixed";
    HitbubbleFlag[HitbubbleFlag["ground"] = 8] = "ground";
    HitbubbleFlag[HitbubbleFlag["air"] = 16] = "air";
    HitbubbleFlag[HitbubbleFlag["meteor"] = 32] = "meteor";
    HitbubbleFlag[HitbubbleFlag["wind"] = 64] = "wind";
    HitbubbleFlag[HitbubbleFlag["no_reverse"] = 128] = "no_reverse";
    HitbubbleFlag[HitbubbleFlag["stale_di"] = 256] = "stale_di";
    HitbubbleFlag[HitbubbleFlag["no_stale"] = 512] = "no_stale";
    HitbubbleFlag[HitbubbleFlag["no_self_lag"] = 1024] = "no_self_lag";
    HitbubbleFlag[HitbubbleFlag["no_stale_add"] = 2048] = "no_stale_add";
    HitbubbleFlag[HitbubbleFlag["no_turnaround"] = 4096] = "no_turnaround";
})(HitbubbleFlag = exports.HitbubbleFlag || (exports.HitbubbleFlag = {}));
var HurtbubbleState;
(function (HurtbubbleState) {
    HurtbubbleState[HurtbubbleState["phased"] = 0] = "phased";
    HurtbubbleState[HurtbubbleState["normal"] = 1] = "normal";
    HurtbubbleState[HurtbubbleState["lightArmor"] = 2] = "lightArmor";
    HurtbubbleState[HurtbubbleState["heavyArmor"] = 3] = "heavyArmor";
    HurtbubbleState[HurtbubbleState["invincible"] = 4] = "invincible";
    HurtbubbleState[HurtbubbleState["intangible"] = 5] = "intangible";
    HurtbubbleState[HurtbubbleState["protected"] = 6] = "protected";
    HurtbubbleState[HurtbubbleState["projectileArmor"] = 7] = "projectileArmor";
    HurtbubbleState[HurtbubbleState["lightProjectileArmor"] = 8] = "lightProjectileArmor";
    HurtbubbleState[HurtbubbleState["decoration"] = 11] = "decoration";
})(HurtbubbleState = exports.HurtbubbleState || (exports.HurtbubbleState = {}));
var HitbubbleType;
(function (HitbubbleType) {
    HitbubbleType[HitbubbleType["none"] = 0] = "none";
    HitbubbleType[HitbubbleType["ground"] = 1] = "ground";
    HitbubbleType[HitbubbleType["aerial"] = 2] = "aerial";
    HitbubbleType[HitbubbleType["special"] = 3] = "special";
    HitbubbleType[HitbubbleType["object"] = 4] = "object";
    HitbubbleType[HitbubbleType["phasing"] = 5] = "phasing";
    HitbubbleType[HitbubbleType["grab"] = 6] = "grab";
    HitbubbleType[HitbubbleType["shield"] = 7] = "shield";
    HitbubbleType[HitbubbleType["wind"] = 8] = "wind";
    HitbubbleType[HitbubbleType["projectile"] = 9] = "projectile";
    HitbubbleType[HitbubbleType["counter"] = 10] = "counter";
    HitbubbleType[HitbubbleType["reflector"] = 11] = "reflector";
    HitbubbleType[HitbubbleType["throw"] = 12] = "throw";
})(HitbubbleType = exports.HitbubbleType || (exports.HitbubbleType = {}));
var AnimationType;
(function (AnimationType) {
    AnimationType[AnimationType["movement"] = 0] = "movement";
    AnimationType[AnimationType["attack"] = 1] = "attack";
    AnimationType[AnimationType["aerial"] = 2] = "aerial";
    AnimationType[AnimationType["special"] = 3] = "special";
    AnimationType[AnimationType["passive"] = 4] = "passive";
    AnimationType[AnimationType["shield"] = 5] = "shield";
    AnimationType[AnimationType["holding"] = 6] = "holding";
    AnimationType[AnimationType["tumble"] = 7] = "tumble";
    AnimationType[AnimationType["throw"] = 8] = "throw";
})(AnimationType = exports.AnimationType || (exports.AnimationType = {}));
var GrabDirection;
(function (GrabDirection) {
    GrabDirection[GrabDirection["up"] = 1] = "up";
    GrabDirection[GrabDirection["left"] = 2] = "left";
    GrabDirection[GrabDirection["down"] = 4] = "down";
    GrabDirection[GrabDirection["right"] = 8] = "right";
    GrabDirection[GrabDirection["backwards"] = 16] = "backwards";
    GrabDirection[GrabDirection["cling"] = 32] = "cling";
    GrabDirection[GrabDirection["forward"] = 64] = "forward";
    GrabDirection[GrabDirection["backward"] = 128] = "backward";
})(GrabDirection = exports.GrabDirection || (exports.GrabDirection = {}));
let constants = null;
class EventSystem {
    constructor(entity) {
        this.listeners = new Map();
        this.tests = [];
        this.entity = entity;
    }
    test() {
        for (let i = 0; i < this.tests.length; i++) {
            if (this.tests[i][0](this.entity)) {
                this.tests[i][1](this.entity);
            }
        }
    }
    listen(l) {
        if (utils_1.objHas(l, 'tests')) {
            for (let i = 0; i < l.tests.length; i++) {
                this.tests.push(l.tests[i]);
            }
        }
        if (utils_1.objHas(l, 'listeners')) {
            const keys = Object.getOwnPropertyNames(l.listeners);
            for (let i = 0; i < keys.length; i++) {
                const key = keys[i];
                if (!this.listeners.has(key)) {
                    this.listeners.set(key, []);
                }
                this.listeners.get(key).push(l.listeners[key]);
            }
        }
    }
    remove(l) {
        if (utils_1.objHas(l, 'tests')) {
            for (let i = 0; i < l.tests.length; i++) {
                const index = this.tests.indexOf(l.tests[i]);
                if (index !== -1) {
                    this.tests.splice(index, 1);
                }
            }
        }
        if (utils_1.objHas(l, 'listeners')) {
            const keys = Object.getOwnPropertyNames(l.listeners);
            for (let i = 0; i < keys.length; i++) {
                const key = keys[i];
                let arr = null;
                let index = 0;
                const val = l.listeners[key];
                if (!this.listeners.has(key)) {
                    continue;
                }
                arr = this.listeners.get(key);
                if (arr.length === 1 && arr[0] === val) {
                    this.listeners.delete(key);
                    continue;
                }
                index = arr.indexOf(val);
                if (index !== -1) {
                    arr.splice(index, 1);
                }
            }
        }
    }
    dispatch(event, ...args) {
        let val = null;
        if (this.listeners.has(event)) {
            const l = this.listeners.get(event);
            for (let i = 0; i < l.length; i++) {
                const v = l[i].call(this.entity, this.entity, ...args);
                if (typeof v !== 'undefined' && v !== null) {
                    val = v;
                }
            }
        }
        return val;
    }
}
exports.EventSystem = EventSystem;
class Status {
    constructor() {
        this.duration = 0;
        this.startDuration = 0;
        this.repeat = 0;
        this.repeatDelay = 0;
        this.defaultDuration = 1;
        this.defaultRepeat = 0;
        this.source = null;
    }
    init() {
        this.duration = this.startDuration = this.repeatDelay = this.defaultDuration;
        this.repeat = this.defaultRepeat;
        this.source = null;
    }
    expire(_entity) { }
    apply(_entity, source, duration) {
        this.duration = this.startDuration = this.repeatDelay = duration;
        this.source = source;
    }
    tick(entity) {
        this.duration--;
        if (this.duration === 0) {
            this.expire(entity);
            if (this.repeat > 0) {
                this.repeat--;
                this.duration = this.repeatDelay;
            }
            else {
                entity.status.delete(this);
            }
        }
    }
}
exports.Status = Status;
class DelayHitStatus extends Status {
    constructor() {
        super(...arguments);
        this.defaultDuration = 30;
        this.bubble = null;
    }
    expire(entity) {
        exports.addHitbubble(this.source, this.bubble, entity.x + entity.headbubble.x + this.bubble.x, entity.y + entity.headbubble.y + this.bubble.y, entity.x + entity.headbubble.x + this.bubble.x, entity.y + entity.headbubble.y + this.bubble.y, this.bubble.radius, 0);
    }
    tick(entity) {
        if (entity.activeAnimation.type === AnimationType.shield) {
            this.expire(entity);
            entity.status.delete(this);
            return;
        }
        super.tick(entity);
    }
}
exports.DelayHitStatus = DelayHitStatus;
const statusMap = utils_1.map({
    delay: DelayHitStatus,
});
const statusFactory = (name) => {
    return new (statusMap.get(name))();
};
exports.statusFactory = statusFactory;
class Heartbeat {
    constructor(width) {
        this.width = 0;
        this.index = 0;
        this.impulse = 0;
        this.lastPulse = -1000;
        this.dropoff = 0.8;
        this.lastPing = 0;
        this.width = width;
        this.buffer = new Int8Array(width);
    }
    tick() {
        this.buffer[this.index] = this.impulse;
        this.index = (this.index + 1) % this.width;
        this.impulse = this.impulse * this.dropoff;
    }
    push(n) {
        this.buffer[this.index] = n;
        this.index = (this.index + 1) % this.width;
    }
    ping(_stocks, _damage) { }
}
exports.Heartbeat = Heartbeat;
const defaultPrefab = {
    models: [
        {
            name: 'Center',
            alias: 'center',
        },
        {
            name: 'Side',
            alias: 'side1',
        },
        {
            name: 'Side',
            alias: 'side2',
        },
    ],
};
exports.hurtbubbles = [];
class Hurtbubble {
    constructor(owner, data, index) {
        this.x = 0;
        this.y = 0;
        this.x2 = 0;
        this.y2 = 0;
        this.lx = 0;
        this.ly = 0;
        this.lx2 = 0;
        this.ly2 = 0;
        this.radius = 1;
        this.state = 1;
        this.colorIndex1 = 0;
        this.colorIndex2 = 0;
        this.prefab = null;
        this.rotateModel = true;
        const prefab = model_1.Prefab.build(data.prefab || defaultPrefab);
        const material = new model_1.Material();
        material.diffuseColor = material.ambientColor;
        this.owner = owner;
        this.index = index;
        this.name = data.name || '';
        this.i1 = data.i1 | 0;
        this.i2 = data.i2 | 0;
        this.ik = utils_1.objHas(data, 'ik') ? data.ik : -1;
        this.z = data.z | 0;
        this.flip = !!data.flip;
        this.invert = !!data.invert;
        this.ungrabbable = !!data.ungrabbable;
        this.prefab = prefab;
        for (let i = 0; i < prefab.models.length; i++) {
            prefab.models[i].material = material;
        }
        if (data.rotateModel === false) {
            this.rotateModel = false;
        }
        exports.hurtbubbles.push(this);
    }
    hitbubble(index) {
        return (Math.sqrt((this.x - exports.hitbubbles[index].x) ** 2
            + (this.y - exports.hitbubbles[index].y) ** 2)
            < this.radius + exports.hitbubbles[index].radius);
    }
}
exports.Hurtbubble = Hurtbubble;
class BubbleReference {
    constructor() {
        this.x = 0;
        this.y = 0;
        this.x2 = 0;
        this.y2 = 0;
        this.radius = 0;
        this.frame = 0;
        this.entity = null;
        this.bubble = null;
        this.facing = true;
    }
    angle(distance, airborne, damage) {
        const b = this.bubble;
        let a = b.angle;
        const reversible = (b.flags & HitbubbleFlag.no_reverse) === 0;
        const reverse = reversible ? distance > 0 : !this.facing;
        if (b.sakurai) {
            if (airborne) {
                a = constants.SAKURAI_AIR;
            }
            else if (damage < constants.SAKURAI_MIN) {
                a = 0;
            }
            else if (damage >= constants.SAKURAI_MAX) {
                a = constants.SAKURAI_ANGLE;
            }
            else {
                a
                    = ((damage - constants.SAKURAI_MIN)
                        / (constants.SAKURAI_MAX - constants.SAKURAI_MIN))
                        * constants.SAKURAI_ANGLE;
            }
        }
        a = reverse ? (a > 180 ? 540 - a : 180 - a) : a;
        return a;
    }
}
exports.BubbleReference = BubbleReference;
exports.hitbubbles = [];
let hitbubbleCount = 0;
const clearHitbubbles = () => {
    hitbubbleCount = 0;
};
exports.clearHitbubbles = clearHitbubbles;
const addHitbubble = (entity, bubble, x, y, x2, y2, radius, frame) => {
    let b = null;
    if (exports.hitbubbles.length <= hitbubbleCount) {
        exports.hitbubbles.push(new BubbleReference());
    }
    b = exports.hitbubbles[hitbubbleCount];
    b.frame = frame;
    b.entity = entity;
    b.bubble = bubble;
    b.facing = entity.face === 1;
    b.x = x;
    b.y = y;
    b.x2 = x2;
    b.y2 = y2;
    b.radius = radius * Math.sqrt(entity.skeletonScale);
    hitbubbleCount++;
};
exports.addHitbubble = addHitbubble;
exports.DefaultColors = {
    default: new color_1.Color(gl_matrix_1.vec4.fromValues(0.015, 0.9, 0.55, 1)),
    highhit: new color_1.Color(gl_matrix_1.vec4.fromValues(0.075, 0.85, 0.45, 1)),
    lowhit: new color_1.Color(gl_matrix_1.vec4.fromValues(0.005, 0.9, 0.5, 1)),
    wham: new color_1.Color(gl_matrix_1.vec4.fromValues(0, 1, 0.5, 1)),
    grab: new color_1.Color(gl_matrix_1.vec4.fromValues(0.3, 1, 0.5, 1)),
    throw: new color_1.Color(gl_matrix_1.vec4.fromValues(0.5, 1, 0.5, 1)),
    electric: new color_1.Color(gl_matrix_1.vec4.fromValues(0.15, 1, 0.5, 1)),
};
const vec4zero = gl_matrix_1.vec4.create();
class Hitbubble {
    constructor(entity, follow, type, x, y, radius, flags, damage, knockback, growth, angle, x2, y2, start, end, smear) {
        this.type = HitbubbleType.none;
        this.follow = 0;
        this.x = 0;
        this.y = 0;
        this.smear = {
            follow: this.follow,
            x: this.x,
            y: this.y,
        };
        this.radius = 0;
        this.flags = 0;
        this.color = null;
        this.audio = null;
        this.damage = 0;
        this.knockback = 0;
        this.growth = 0;
        this.angle = 0;
        this.x2 = 0;
        this.y2 = 0;
        this.start = 0;
        this.end = 0;
        this.sakurai = false;
        this.if = null;
        this.data = new Map();
        this.start = start;
        this.end = end;
        if (typeof follow === 'number') {
            this.follow = follow;
        }
        else {
            this.follow = entity.namedbubbles.has(follow)
                ? entity.namedbubbles.get(follow)
                : 0;
        }
        if (typeof type === 'number') {
            this.type = type;
        }
        else {
            if (!utils_1.objHas(HitbubbleType, type)) {
                console.warn('Tried to set unknown type:', type, this);
                this.type = 0;
            }
            else {
                this.type = HitbubbleType[type];
            }
        }
        this.x = x;
        this.y = -y;
        this.smear.x = smear?.x ?? x;
        this.smear.y = -(smear?.y ?? y);
        if (smear?.follow) {
            this.smear.x = smear?.x ?? 0;
            this.smear.y = -(smear?.y ?? 0);
            if (typeof smear.follow === 'number') {
                this.smear.follow = smear.follow;
            }
            else {
                this.smear.follow = entity.namedbubbles.has(smear.follow)
                    ? entity.namedbubbles.get(smear.follow)
                    : 0;
            }
        }
        this.radius = radius;
        if (typeof flags === 'number') {
            this.flags = flags;
        }
        else if (Array.isArray(flags)) {
            for (let i = 0; i < flags.length; i++) {
                if (!utils_1.objHas(HitbubbleFlag, flags[i])) {
                    console.warn('Tried to set unknown flag:', flags[i]);
                }
                this.flags |= HitbubbleFlag[flags[i]];
            }
        }
        else if (typeof flags === 'string') {
            this.flags |= HitbubbleFlag[flags];
        }
        else {
            console.warn('Flags in wrong format', flags, this);
        }
        this.color = exports.DefaultColors.default;
        this.damage = damage;
        this.knockback = knockback;
        this.growth = growth;
        this.angle = angle;
        this.x2 = x2;
        this.y2 = y2;
    }
    setColor(color) {
        if (typeof color === 'string') {
            this.color = exports.DefaultColors[color];
        }
        else if (color === null) {
            if (this.data.has('effect')
                && utils_1.objHas(exports.DefaultColors, this.data.get('effect'))) {
                this.color = exports.DefaultColors[this.data.get('effect')];
            }
            else if (this.data.has('audio')
                && utils_1.objHas(exports.DefaultColors, this.data.get('audio'))) {
                this.color = exports.DefaultColors[this.data.get('audio')];
            }
            else if (this.type === HitbubbleType.shield) {
                this.color = new color_1.Color(vec4zero);
            }
            else if (utils_1.objHas(exports.DefaultColors, HitbubbleType[this.type])) {
                this.color = exports.DefaultColors[HitbubbleType[this.type]];
            }
            else {
                this.color = exports.DefaultColors.default;
            }
        }
        else if (color instanceof color_1.Color) {
            this.color = color;
        }
        else {
            this.color = new color_1.Color(color);
        }
    }
    static from(entity, hb) {
        const b = new Hitbubble(entity, hb.follow || 0, hb.type || 0, hb.x || 0, hb.y || 0, hb.radius || 0, hb.flags || 0, hb.damage || 0, hb.knockback || 0, hb.growth || 0, hb.angle || 0, hb.x2 || 0, hb.y2 || 0, hb.start || 0, hb.end || 0, hb.smear || null);
        if (hb.audio) {
            b.audio = typeof hb.audio === 'string'
                ? { name: hb.audio, volume: 1.0, pitch: 1.0 }
                : { name: hb.audio.name, volume: hb.audio.volume || 1.0, pitch: hb.audio.pitch || 1.0 };
        }
        else {
            b.audio = {
                name: b.type === HitbubbleType.grab
                    ? 'grab'
                    : b.type === HitbubbleType.throw
                        ? 'throw'
                        : 'hit',
                pitch: b.type === HitbubbleType.throw ? 2.0 : 1.0,
                volume: b.type === HitbubbleType.throw ? 2.0 : 1.0,
            };
        }
        if (hb.if) {
            b.if = typeof hb.if === 'string'
                ? !hb.if.startsWith('!')
                    ? entity.handlers[hb.if]
                    : ((handler) => (...args) => handler(...args))(entity.handlers[hb.if.substring(1)])
                : hb.if;
        }
        if (hb.sakurai) {
            b.sakurai = hb.sakurai;
            b.angle = 45;
        }
        for (const key of Object.getOwnPropertyNames(hb)) {
            if (!utils_1.objHas(b, key)) {
                b.data.set(key, hb[key]);
            }
        }
        b.setColor(utils_1.objHas(hb, 'color') ? hb.color : null);
        return b;
    }
}
exports.Hitbubble = Hitbubble;
const defaultAnimation = 'idle';
const defaultAirborne = 'airborne';
const tweenKeyframes = (frame1, frame2, fraction) => {
    const l = frame1.length;
    const between = [];
    for (let i = 0; i < l; i++) {
        if (i % 4 !== 3) {
            between.push((frame2[i] - frame1[i]) * fraction + frame1[i]);
        }
        else {
            between.push(frame1[i]);
        }
    }
    return between;
};
exports.tweenKeyframes = tweenKeyframes;
const precomputeAnimation = (entity, animation) => {
    const animationArray = [];
    let next = 0;
    let index = 0;
    const keyframes = animation.keyframes;
    const l = keyframes.length - 1;
    let j = 0;
    let midframe = 0;
    let keyframe = null;
    let hurtbubs = null;
    let tweenfrom = 0;
    let nextframe = null;
    let fulllength = 0;
    let keyframeLength = 0;
    let duration = 0;
    let easeFn = easing_1.Ease.linear;
    for (let i = 0; i < l; i++) {
        keyframe = keyframes[i];
        duration = duration + keyframes[i].duration;
        if (!keyframe.tween || typeof keyframe.tween !== 'string') {
            keyframe.easeFn = easing_1.Ease.linear;
        }
        else {
            keyframe.easeFn = easing_1.Ease[keyframe.tween];
        }
        if (keyframe.hurtbubbles) {
            const tween = keyframe.tween || null;
            easeFn = easing_1.Ease.linear;
            hurtbubs = keyframe.hurtbubbles;
            fulllength = keyframe.duration;
            tweenfrom = 0;
            next = i + 1;
            nextframe = keyframes[next];
            if (nextframe?.hurtbubbles === true) {
                nextframe.hurtbubbles = hurtbubs;
            }
            if (tween) {
                if (!utils_1.objHas(easing_1.Ease, tween)) {
                    console.error('Ease type not found:', tween);
                }
                else {
                    easeFn = easing_1.Ease[tween];
                }
            }
            while (!nextframe.hurtbubbles) {
                fulllength = fulllength + nextframe.duration;
                next++;
                nextframe = keyframes[next];
            }
        }
        else {
            tweenfrom = tweenfrom + keyframeLength;
        }
        if (keyframe.hitbubbles) {
            let hitbubs = keyframe.hitbubbles;
            if (hitbubs === true) {
                hitbubs = keyframes[i - 1].hitbubbles.map((hb) => ({
                    ...hb,
                    smear: {
                        follow: hb.follow,
                        x: hb.x,
                        y: hb.y,
                    },
                    start: (keyframe.duration * hb.duration) / (hb.start || 0),
                    end: (keyframe.duration * hb.duration) / (hb.end || hb.duration),
                }));
                keyframe.hitbubbles = hitbubs;
                if (i <= 0 || !keyframes[i - 1].hitbubbles) {
                    console.warn('Tried to continue hitbubbles on', animation.name, 'from', keyframes[i - 1]);
                }
            }
            else {
                const hbs = [];
                for (j = 0; j < hitbubs.length; j++) {
                    const hb = hitbubs[j];
                    if (hb.smear === true) {
                        hb.smear = {
                            follow: hb.follow,
                            x: hb.x,
                            y: hb.y,
                        };
                    }
                    if (!hb.start) {
                        hb.start = 0;
                    }
                    if (!hb.end) {
                        hb.end = keyframe.duration;
                    }
                    if (hb.next) {
                        delete hb.next;
                        const nextHitbubs = {
                            ...hb,
                            smear: {
                                follow: hb.follow,
                                x: hb.x,
                                y: hb.y,
                            },
                            start: 0,
                            end: 1,
                        };
                        if (keyframes[i + 1].hitbubbles) {
                            keyframes[i + 1].hitbubbles.push(nextHitbubs);
                        }
                        else {
                            keyframes[i + 1].hitbubbles = [nextHitbubs];
                        }
                    }
                    hbs.push(Hitbubble.from(entity, hb));
                }
                keyframe.hitbubbles = hbs;
            }
        }
        keyframeLength = keyframe.duration;
        for (midframe = 0; midframe < keyframeLength; midframe++) {
            animationArray[index] = exports.tweenKeyframes(hurtbubs, nextframe.hurtbubbles, easeFn((midframe + tweenfrom) / fulllength));
            index++;
        }
    }
    animation.duration = duration;
    animation.frames = animationArray;
    return animationArray;
};
const setBubbles = (entity, frame, keyframe) => {
    const animation = entity.activeAnimation;
    if (frame.length > 0) {
        const l = entity.hurtbubbleCount;
        for (let i = 0; i < l; i++) {
            const index = entity.hurtbubbles[i].i1 * 4;
            const index2 = entity.hurtbubbles[i].i2 * 4;
            entity.hurtbubbles[i].lx = entity.hurtbubbles[i].x + entity.x;
            entity.hurtbubbles[i].ly = entity.hurtbubbles[i].y + entity.y;
            entity.hurtbubbles[i].lx2 = entity.hurtbubbles[i].x2 + entity.x;
            entity.hurtbubbles[i].ly2 = entity.hurtbubbles[i].y2 + entity.y;
            entity.hurtbubbles[i].x
                = entity.face * frame[index] * entity.skeletonScale;
            entity.hurtbubbles[i].y = frame[index + 1] * entity.skeletonScale;
            entity.hurtbubbles[i].x2
                = entity.face * frame[index2] * entity.skeletonScale;
            entity.hurtbubbles[i].y2 = frame[index2 + 1] * entity.skeletonScale;
            entity.hurtbubbles[i].radius = frame[index + 2] * entity.bubbleScale;
            entity.hurtbubbles[i].state = frame[index + 3];
        }
    }
    if (!animation.disableIK) {
        for (let i = 0; i < entity.hurtbubbles.length; i++) {
            const hb = entity.hurtbubbles[i];
            for (let j = 0; j < 2; j++) {
                let x = entity.x;
                let y = entity.y;
                let oy = 0;
                const plat = entity.airborne ? entity.hover : entity.platform;
                if (j === 0) {
                    x = x + hb.x;
                    y = y + hb.y;
                    oy = hb.y;
                }
                else {
                    x = x + hb.x2;
                    y = y + hb.y2;
                    oy = hb.y2;
                }
                if (plat !== null && (hb.ik === j || (x >= plat.x && x <= plat.x2))) {
                    const py = plat.yAt(x) - hb.radius;
                    if (y > py || (hb.ik === j && animation.hardIK)) {
                        y = py;
                    }
                }
                if (hb.ik === 0
                    && oy >= -hb.radius
                    && !entity.airborne
                    && entity.platform !== null) {
                    if (y - plat.yAt(x) < 0.5) {
                        x = Math.max(x, plat.x);
                        x = Math.min(x, plat.x2);
                    }
                    if (oy <= hb.radius) {
                        y = plat.yAt(x) - hb.radius;
                    }
                }
                if (j === 0) {
                    hb.x = x - entity.x;
                    hb.y = y - entity.y;
                }
                else {
                    hb.x2 = x - entity.x;
                    hb.y2 = y - entity.y;
                }
            }
        }
    }
    entity.lastFrame = entity.currentFrame;
    entity.currentFrame = frame;
    if (animation.midframe === 0
        && animation.keyframe > 0
        && utils_1.objHas(animation.keyframes[animation.keyframe - 1], 'hitbubbles')) {
        if (utils_1.objHas(animation.keyframeData, 'reset')) {
            const hbs = keyframe.hitbubbles;
            const l = hbs?.length || 0;
            for (let i = 0; i < l; i++) {
                const hb = hbs[i];
                const f = hb.follow;
                let x = entity.x + entity.face * hb.x;
                let y = entity.y + hb.y;
                if (f !== 0) {
                    if (f > 0) {
                        x = x + entity.hurtbubbles[f - 1].x;
                        y = y + entity.hurtbubbles[f - 1].y;
                    }
                    else {
                        x = x + entity.hurtbubbles[-f - 1].x2;
                        y = y + entity.hurtbubbles[-f - 1].y2;
                    }
                }
                entity.lastHb[i * 2] = x;
                entity.lastHb[i * 2 + 1] = y;
            }
        }
    }
    if (utils_1.objHas(keyframe, 'hitbubbles')) {
        const hbs = keyframe.hitbubbles;
        const l = hbs.length;
        for (let i = 0; i < l; i++) {
            const hb = hbs[i];
            const f = hb.follow;
            let x = entity.x + entity.face * hb.x * entity.skeletonScale;
            let y = entity.y + hb.y * entity.skeletonScale;
            if (hb.if !== null && !hb.if(entity, entity.controller, animation)) {
                continue;
            }
            if (animation.midframe < hb.start || animation.midframe >= hb.end) {
                continue;
            }
            if (f !== 0) {
                if (f > 0) {
                    x = x + entity.hurtbubbles[f - 1].x;
                    y = y + entity.hurtbubbles[f - 1].y;
                }
                else {
                    x = x + entity.hurtbubbles[-f - 1].x2;
                    y = y + entity.hurtbubbles[-f - 1].y2;
                }
            }
            if (animation.midframe === 0 || animation.frame === 1) {
                let x2 = x
                    - hb.x * entity.face * entity.skeletonScale
                    + hb.smear.x * entity.face * entity.skeletonScale;
                let y2 = y - hb.y * entity.skeletonScale + hb.smear.y * entity.skeletonScale;
                if (hb.smear.follow !== 0) {
                    const follow = hb.smear.follow;
                    x2 = entity.x + entity.face * hb.smear.x * entity.skeletonScale;
                    y2 = entity.y + hb.smear.y * entity.skeletonScale;
                    if (follow !== 0) {
                        if (follow > 0) {
                            x2
                                = entity.face * hb.smear.x * entity.skeletonScale
                                    + entity.hurtbubbles[follow - 1].lx;
                            y2
                                = hb.smear.y * entity.skeletonScale + entity.hurtbubbles[follow - 1].ly;
                        }
                        else {
                            x2
                                = entity.face * hb.smear.x * entity.skeletonScale
                                    + entity.hurtbubbles[-follow - 1].lx2;
                            y2
                                = hb.smear.y * entity.skeletonScale
                                    + entity.hurtbubbles[-follow - 1].ly2;
                        }
                    }
                }
                entity.lastHb[i * 2] = x2;
                entity.lastHb[i * 2 + 1] = y2;
            }
            exports.addHitbubble(entity, hb, x, y, entity.lastHb[i * 2], entity.lastHb[i * 2 + 1], hb.radius, entity.hbTime);
            entity.hbTime = entity.hbTime + 1;
            entity.lastHb[i * 2] = x;
            entity.lastHb[i * 2 + 1] = y;
        }
    }
    else {
        entity.hbTime = 0;
    }
};
const defaultActions = {
    brake: (entity, _controller, _animation, _keyframe) => {
        entity.dx = entity.dx * entity.activeAnimation.data.get('brake');
        entity.slide = entity.slide * entity.activeAnimation.data.get('brake');
        entity.dy = entity.dy * entity.activeAnimation.data.get('brake');
    },
    stop: (entity, _controller, _animation, _keyframe) => {
        entity.dx = 0;
        entity.dy = 0;
        entity.slide = 0;
    },
    remove: (entity, _controller, _animation, _keyframe) => {
        entity.removed = true;
    },
    setAnimation: (entity, _controller, _animation, _keyframe) => {
        entity.setAnimation(entity.activeAnimation.data.get('setAnimation'), true);
    },
    pivot: (entity, _controller, _animation, _keyframe) => {
        entity.face = -entity.face;
    },
    airborne: (entity, _controller, _animation, _keyframe) => {
        entity.airborne = true;
    },
};
const handlerEvents = new Set([
    'handler',
    'start',
    'end',
    'collided',
    'injured',
    'grabbed',
    'clashed',
    'interrupted',
    'blocked',
    'effect',
    'canceled',
    'spawn',
]);
const v2 = gl_matrix_1.vec2.create();
const v4 = gl_matrix_1.vec4.create();
const tColor = new color_1.Color(v4);
const heldTransitionSpeed = 0.3;
const defaultHandlers = {
    interact: {
        holding: (entity, controller, animation, _keyframe) => {
            const held = entity.held;
            let degen = 0;
            let release = false;
            if (held === null || held.removed) {
                entity.schedule('release', true);
                return;
            }
            degen
                = Math.max(0.002, held.shield.energy * 0.02)
                    * Math.min((engine_1.getTicks() - entity.grabTime) ** 1.5 * 0.00033, 2)
                    + entity.pummels * 0.005 * Math.max(held.shield.energy, 0.25);
            if (entity.kb > constants.GRAB_BREAKOUT_KB || entity.held.removed) {
                entity.schedule('release', true);
            }
            entity.kbx = entity.kbx * 0.8;
            entity.kby = entity.kby * 0.8;
            entity.kb = entity.kb * 0.8;
            entity.shield.wait = Math.max(entity.shield.wait, constants.HOLDING_ENERGY_WAIT);
            if (held.lastInjury.lastFrame && held.lastInjury.entity !== entity) {
                if (held.dx ** 2 + held.dy ** 2 > animation.data.get('grabForce')) {
                    entity.schedule('release', true);
                }
                else {
                    held.schedule(animation.data.get('heldAnimation'), true);
                }
                return;
            }
            if (!held.activeAnimation.data.has('pivotx')) {
                held.schedule(animation.data.get('heldAnimation'), true);
            }
            else {
                const f = animation.frames[animation.frame];
                const fx = f[f.length - 4];
                const fy = f[f.length - 4 + 1];
                const height = fy + held.activeAnimation.data.get('pivoty');
                const ydist = held.y
                    - Math.min(entity.y + height, !entity.airborne && entity.platform
                        ? entity.platform.yAt(held.x)
                        : entity.y);
                held.slide = 0;
                held.face = -entity.face;
                if (Math.abs(ydist) < Math.abs(held.height + entity.height)) {
                    held.dx
                        = (entity.x
                            + (fx + held.activeAnimation.data.get('pivotx')) * entity.face
                            - held.x)
                            * heldTransitionSpeed;
                    held.dy = ydist;
                    if (height < 0 || entity.airborne) {
                        held.airborne = true;
                    }
                }
                else {
                    release = true;
                }
            }
            entity.shield.energy = Math.max(0, entity.shield.energy - degen);
            if (entity.shield.energy < 0.01) {
                release = true;
            }
            if (animation.data.has('releasable') && release) {
                held.dx = entity.dx;
                held.dy = entity.dy;
                held.slide = entity.slide;
                held.schedule(!held.airborne ? 'released' : 'airreleased', true);
                entity.schedule(!held.airborne ? 'release' : 'airrelease', true);
            }
            else if (controller.attackPress || controller.grabPress) {
                entity.schedule('pummel', false, true);
            }
            else if ((controller.up
                && controller.vmove <= -0.5
                && controller.vmoveLast > -0.5)
                || controller.rup) {
                entity.schedule('uthrow', false, true);
            }
            else if ((controller.down
                && controller.vmove >= 0.5
                && controller.vmoveLast < 0.5)
                || controller.rdown) {
                entity.schedule('dthrow', false, true);
            }
            else if ((controller.left
                && controller.hmove <= -0.5
                && controller.hmoveLast > -0.5)
                || controller.rleft) {
                entity.schedule(entity.face === 1 ? 'bthrow' : 'fthrow', false, true);
            }
            else if ((controller.right
                && controller.hmove >= 0.5
                && controller.hmoveLast < 0.5)
                || controller.rright) {
                entity.schedule(entity.face === 1 ? 'fthrow' : 'bthrow', false, true);
            }
        },
        throwing: (entity, _controller, animation, _keyframe) => {
            const frame = animation.frame;
            const held = entity.held;
            if (held === null || held.removed) {
                return;
            }
            if ((held.animation !== 'held'
                && held.animation !== animation.data.get('heldAnimation'))
                || held.lastInjury.lastFrame) {
                if (held.lastInjury.entity !== entity
                    && held.kbx ** 2 + held.kby ** 2 > animation.data.get('grabForce')) {
                    entity.schedule('release', true);
                }
                else {
                    held.kb = 0;
                    held.kbx = 0;
                    held.kby = 0;
                    held.schedule(animation.data.get('heldAnimation'), true);
                }
            }
            else {
                if (!held.activeAnimation.data.has('pivotx')) {
                    held.schedule(animation.data.get('heldAnimation'), true);
                }
                else {
                    const f = animation.frames[frame];
                    const fx = f[f.length - 4];
                    const fy = f[f.length - 4 + 1];
                    const height = fy + held.activeAnimation.data.get('pivoty');
                    const ydist = held.y
                        - Math.min(entity.y + height, !entity.airborne && entity.platform
                            ? entity.platform.yAt(held.x)
                            : entity.y);
                    held.slide = 0;
                    held.face
                        = animation.data.get('heldAnimation') !== 'heldpivot'
                            ? -entity.face
                            : entity.face;
                    if (Math.abs(ydist) < Math.abs(held.height + entity.height)) {
                        held.dx
                            = entity.x
                                + (fx + held.activeAnimation.data.get('pivotx')) * entity.face
                                - held.x;
                        held.dy = ydist;
                        if (height < 0 || entity.airborne) {
                            held.airborne = true;
                        }
                    }
                }
            }
        },
    },
    handler: {
        airdodge: (entity, controller, animation, _keyframe) => {
            if (animation.frame < 4
                && entity.hover
                && entity.hover.yAt(entity.x) - entity.y < 20
                && entity.kby <= 0.5) {
                entity.y = entity.hover.yAt(entity.x) - 1;
                entity.ny = Math.min(entity.ny, -5);
            }
            if (animation.frame > 4
                && (controller.grabPress
                    || (controller.shield && controller.attackPress))
                && utils_1.objHas(entity.animations, 'dodgepanic')
                && entity.setAnimation('dodgepanic')) {
                entity.kb = entity.kb * 0.25;
                entity.kbx = entity.kbx * 0.25;
                entity.kby = entity.kby * 0.25;
            }
        },
        weakstunned: (entity, _controller, _animation, _keyframe) => {
            if (entity.stun <= 0) {
                entity.setAnimation('airborne', true);
            }
        },
        stunned: (entity, _controller, _animation, _keyframe) => {
            if (entity.stun <= 0) {
                if (entity.okb > entity.softland) {
                    entity.setAnimation('tumble', true);
                }
                else {
                    entity.setAnimation('airborne', true);
                }
            }
        },
        meteor: (entity, _controller, _animation, _keyframe) => {
            if (entity.stun <= 0) {
                entity.setAnimation('tumble', true);
            }
        },
        weakstumble: (entity, _controller, _animation, _keyframe) => {
            if (entity.airborne) {
                entity.setAnimation('weakstunned', true);
                return;
            }
            if (entity.stun <= 0) {
                entity.setAnimation('idle', true);
            }
        },
        stumble: (entity, _controller, _animation, _keyframe) => {
            if (entity.airborne) {
                entity.setAnimation('stunned', true);
                return;
            }
            if (entity.stun <= 0) {
                entity.setAnimation('idle', true);
            }
        },
        tumble: (entity, _controller, _animation, _keyframe) => {
            if (constants.SHAKEOUT_OK
                && (!!entity.events.dispatch('shakeout', entity.kba)
                    || entity.controller.leftTap === 0
                    || entity.controller.rightTap === 0)) {
                entity.setAnimation('airborne');
            }
        },
        drain: (entity, controller, animation, _keyframe) => {
            entity.shield.energy = entity.shield.energy - animation.data.get('drain');
            if (entity.shield.energy <= 0) {
                entity.shield.energy = 0;
                entity.schedule(animation.data.get('drained'), true);
                return;
            }
            if (!controller.special) {
                entity.schedule(animation.data.get('drained'), true);
            }
        },
        charge: (entity, controller, animation, _keyframe) => {
            let charge = animation.frame / (animation.duration - 1);
            if (charge > 0.75 && charge < 1.0) {
                charge = 0.75;
            }
            else if (charge >= 1.0) {
                charge = 1.0;
            }
            entity.animations[animation.data.get('release')].charged = charge;
            if (!constants.CHARGE_TAP_OK
                && (animation.type === AnimationType.attack
                    || animation.type === AnimationType.aerial)) {
                entity.schedule(animation.data.get('release'), true);
                return;
            }
            if (animation.frame % 10 < 4 && animation.frame > 1) {
                entity.flash = 2;
            }
            if (!controller.attack
                && !controller.grab
                && (animation.type === AnimationType.attack
                    || animation.type === AnimationType.aerial)) {
                entity.schedule(animation.data.get('release'), true);
            }
            else if (!controller.special
                && animation.type === AnimationType.special) {
                entity.schedule(animation.data.get('release'), true);
            }
        },
        skid: (entity, controller, _animation, _keyframe) => {
            const holding = Math.sign(controller.hmove);
            if (holding !== 0 && holding !== entity.face) {
                entity.setAnimation('turnaround', true);
            }
            if (holding === entity.face
                && (controller.leftTap < 4 || controller.rightTap < 4)) {
                entity.setAnimation('run', true);
            }
        },
        teeter: (entity, controller, _animation, _keyframe) => {
            if (controller.shield) {
                return;
            }
            if ((entity.face === -1 && controller.hmove > 0.3)
                || (entity.face === 1 && controller.hmove < -0.3)) {
                if (entity.setAnimation('walkpivot')) {
                    entity.face = -entity.face;
                }
            }
            else {
                if (controller.hmove > 0.3 || controller.hmove < -0.3) {
                    const speed = entity.walkSpeed * controller.hmove;
                    if (speed === entity.dx) {
                    }
                    else if (speed > entity.dx) {
                        entity.dx = Math.min(entity.dx + entity.walkAcceleration, speed);
                    }
                    else {
                        entity.dx = Math.max(entity.dx - entity.walkAcceleration, speed);
                    }
                }
            }
        },
        walk: (entity, controller, _animation, _keyframe) => {
            if (controller.shield) {
                return;
            }
            if ((entity.face === -1 && controller.hmove > 0.3)
                || (entity.face === 1 && controller.hmove < -0.3)) {
                if (entity.setAnimation('walkpivot')) {
                    entity.face = -entity.face;
                }
            }
            else {
                if (controller.hmove > 0.3 || controller.hmove < -0.3) {
                    const speed = entity.walkSpeed * controller.hmove;
                    if (speed === entity.dx) {
                    }
                    else if (speed > entity.dx) {
                        entity.dx = Math.min(entity.dx + entity.walkAcceleration, speed);
                    }
                    else {
                        entity.dx = Math.max(entity.dx - entity.walkAcceleration, speed);
                    }
                }
                if (Math.abs(controller.hmove) > 0.6) {
                    entity.animation !== 'stride' && entity.schedule('stride');
                }
                else if (Math.abs(controller.hmove) > 0.3) {
                    entity.animation !== 'walk' && entity.schedule('walk');
                }
                else {
                    entity.animation !== 'idle' && entity.schedule('idle');
                }
            }
        },
        dash: (entity, controller, animation, _keyframe) => {
            const hmove = controller.hmove;
            const dashAccel = constants.DASHACCEL_MOD * controller.hmove
                + constants.DASHBOOST * entity.face;
            if (hmove === 0 && controller.vmove === 0 && animation.frame < 5) {
                entity.setAnimation('idle', true);
                return;
            }
            let pivot = false;
            if (!animation.data.get('passthrough')
                && !animation.data.get('moonwalking')
                && (controller.vmove * controller.vmove + hmove * hmove < 0.95
                    || Math.sign(hmove) !== entity.face)) {
                if (animation.frame === 0 && Math.abs(hmove) > 0.95) {
                    animation.data.set('passthrough', false);
                    animation.data.set('moonwalking', true);
                }
                animation.data.set('passthrough', true);
            }
            if (controller.vmove > 0.6) {
                animation.data.set('moonwalking', true);
                animation.data.set('passthrough', false);
            }
            if (animation.frame > 1) {
                if (entity.face === -1
                    && hmove > 0.2
                    && animation.data.get('passthrough')
                    && entity.setAnimation('pivot')) {
                    pivot = true;
                }
                else if (entity.face === 1
                    && hmove < -0.2
                    && animation.data.get('passthrough')
                    && entity.setAnimation('pivot')) {
                    pivot = true;
                }
            }
            if (pivot) {
                entity.storedSpeed = Math.min(Math.max(entity.dx, -animation.data.get('dashSpeed')), animation.data.get('dashSpeed'));
            }
            else if (animation.frame > 3) {
                const friction = animation.data.has('friction')
                    ? animation.data.get('friction')
                    : 1;
                const dashSpeed = (animation.data.get('dashSpeed') / friction) * entity.face;
                const moonwalkSpeed = (animation.data.get('moonwalkSpeed') / friction) * -entity.face;
                entity.dx
                    = entity.dx
                        + (animation.data.get('acceleration') / friction) * dashAccel;
                entity.dx = Math.max(Math.min(dashSpeed, moonwalkSpeed), entity.dx);
                entity.dx = Math.min(Math.max(dashSpeed, moonwalkSpeed), entity.dx);
            }
            if (animation.data.get('passthrough')
                && controller.vmove * controller.vmove + hmove * hmove >= 0.95) {
                animation.data.set('passthrough', false);
            }
            if (animation.frame < 6) {
                if ((controller.attackPress
                    && (controller.leftTap < 6 || controller.rightTap < 6))
                    || controller.rleft === 2
                    || controller.rright === 2) {
                    const toward = controller.rleft === 2
                        ? -1
                        : controller.rright === 2
                            ? 1
                            : controller.leftTap < 6
                                ? -1
                                : 1;
                    entity.face = toward;
                    entity.setAnimation('ftap', true);
                    return;
                }
            }
            if (controller.attackPress
                || (controller.rleft === 2 && entity.face === -1)
                || (controller.rright === 2 && entity.face === 1)
                || controller.rdown === 2) {
                entity.setAnimation('dashattack');
            }
        },
        run: (entity, controller, animation, _keyframe) => {
            const friction = animation.data.has('friction')
                ? animation.data.get('friction')
                : 1;
            const speed = (animation.data.get('runSpeed') / friction) * controller.hmove;
            if ((entity.face === 1 && !controller.right)
                || (entity.face === -1 && !controller.left)) {
                if ((entity.face === 1 && controller.leftTap < 4)
                    || (entity.face === -1 && controller.rightTap < 4)) {
                    entity.schedule('turnaround', true);
                    return;
                }
                else {
                    entity.setAnimation('skid');
                    return;
                }
            }
            entity.dx
                = entity.face === 1
                    ? Math.min(entity.dx, animation.data.get('runSpeed') / friction)
                    : Math.max(entity.dx, -animation.data.get('runSpeed') / friction);
            if (speed === entity.dx) {
            }
            else if (Math.abs(entity.dx) > speed) {
                entity.dx = entity.dx - (entity.dx - speed) * 0.1;
            }
            else {
                entity.dx
                    = entity.dx + animation.data.get('acceleration') * controller.hmove;
            }
        },
        turnaround: (entity, controller, animation, _keyframe) => {
            entity.dx
                = entity.dx + animation.data.get('acceleration') * controller.hmove;
        },
        crouch: (entity, controller, _animation, _keyframe) => {
            if (!controller.down) {
                entity.schedule('stand');
            }
        },
        shield: (entity, controller, animation, _keyframe) => {
            const rollable = controller.shield1 === 0 || controller.shield2 === 0;
            if (Math.abs(controller.hmove) > 0.9 && controller.vmove < 0.1) {
                animation.data.set('lasttilt', engine_1.getTicks());
            }
            if (animation.name === 'shieldup'
                && animation.frame < constants.POWERSHIELD_FRAMES) {
                if (controller.shieldHardPress
                    && !animation.data.get('powershield')
                    && constants.POWERSHIELD_OK) {
                    rendering_1.Effects.powershield(entity, 0, 0, 30, 30);
                    animation.data.set('powershield', true);
                }
            }
            else if (entity.shield.energy < 0 && entity.stun > 0) {
                entity.shield.energy = 0;
                entity.shield.wait = 0;
                entity.schedule('crumple', true);
                entity.playAudio('whizzshhh', 1, 0.5);
                rendering_1.Effects.burst(entity.x + entity.corebubble.x, entity.y + entity.corebubble.y, 3, 10, entity.palette.base);
                return;
            }
            if (entity.stun <= 0 && entity.lag <= 0) {
                if (entity.shield.stun <= 0) {
                    entity.shield.density = constants.LIGHT_SHIELD_OK
                        ? Math.max(controller.shield, 0.25)
                        : 1;
                }
                entity.shield.energy
                    = entity.shield.energy
                        - entity.shield.decay
                            * constants.SHIELD_BASE_DECAY
                            * (0.5 + entity.shield.density / 2);
            }
            if (entity.shield.stun <= 0 && entity.stun <= 0 && entity.lag <= 0) {
                if (controller.attackPress || controller.grabPress) {
                    entity.setAnimation(animation.name !== 'shieldup' ? 'shieldgrab' : 'grab', true);
                    return;
                }
                if (controller.jumpPress || controller.rup) {
                    entity.setAnimation('hop', true);
                    entity.animations['hop'].data.set('tapJumped', false);
                    return;
                }
                if (rollable && !controller.noTapJump && controller.upTap < 2) {
                    entity.setAnimation('hop', true);
                    entity.animations['hop'].data.set('tapJumped', true);
                    return;
                }
                if (controller.shield < 0.001
                    && !controller.grab
                    && !controller.attack) {
                    entity.setAnimation('shielddrop', true);
                    return;
                }
                if (entity.platform
                    && !entity.platform.solid
                    && controller.vmove > 0.5
                    && controller.special) {
                    entity.shield.wait = constants.ENERGY_CHARGE_DELAY;
                    entity.platformDrop = true;
                    return;
                }
                if (rollable) {
                    if (entity.platform
                        && !entity.platform.solid
                        && controller.vmove > 0.5
                        && Math.abs(controller.hmove) > 0.3
                        && animation.data.get('lasttilt') > engine_1.getTicks() - 4) {
                        entity.shield.wait = constants.ENERGY_CHARGE_DELAY;
                        entity.platformDrop = true;
                        return;
                    }
                    if (entity.platform
                        && !entity.platform.solid
                        && (controller.vmoveLast < 0.63
                            || controller.leftTap < 3
                            || controller.rightTap < 3)
                        && controller.vmove >= 0.63
                        && controller.vmove < 0.69) {
                        entity.shield.wait = constants.ENERGY_CHARGE_DELAY;
                        entity.platformDrop = true;
                        return;
                    }
                    if (controller.downTap < 3) {
                        if (entity.platform
                            && !entity.platform.solid
                            && !constants.SPOT_DODGE_OK) {
                            entity.shield.wait = constants.ENERGY_CHARGE_DELAY;
                            entity.platformDrop = true;
                            return;
                        }
                        if (constants.SPOT_DODGE_OK) {
                            entity.shield.wait = constants.ENERGY_CHARGE_DELAY;
                            entity.setAnimation('spotdodge');
                            return;
                        }
                    }
                }
            }
            if (entity.stun <= 0 && entity.lag <= 0) {
                if ((rollable && controller.rightTap < 3) || controller.rright) {
                    entity.setAnimation(entity.face === -1 ? 'dodgeback' : 'dodgeforth');
                    entity.shield.wait = constants.ENERGY_CHARGE_DELAY;
                    entity.playAudio('roll');
                    return;
                }
                if ((rollable && controller.leftTap < 3) || controller.rleft) {
                    entity.setAnimation(entity.face === 1 ? 'dodgeback' : 'dodgeforth');
                    entity.shield.wait = constants.ENERGY_CHARGE_DELAY;
                    entity.playAudio('roll');
                    return;
                }
                if (constants.SPOT_DODGE_OK && controller.rdown) {
                    entity.shield.wait = constants.ENERGY_CHARGE_DELAY;
                    entity.setAnimation('spotdodge');
                    return;
                }
            }
            {
                const stunFactor = entity.stun > 0 ? 0.333 : entity.shield.stun > 0 ? 0.666 : 1;
                const x = controller.hmove * entity.face * entity.shield.mobility * stunFactor;
                const y = -controller.vmove * entity.shield.mobility * stunFactor;
                const health = Math.min(1, entity.shield.energy * 1.2);
                const r = entity.shield.baseSize
                    + (1 - entity.shield.density) * entity.shield.lightshield
                    + health
                    + entity.shield.growth * health;
                const b = entity.shield.bubble;
                if (entity.lag <= 0) {
                    if (entity.stun > 0) {
                        b.color.setAlpha(entity.palette.base, 0.3 + math_1.lqrandom() * 0.5);
                        b.color.lighten((1 - b.color.hsla[2]) * math_1.lqrandom() * 0.5);
                    }
                    else if (entity.shield.stun > 0) {
                        b.color.set(entity.palette.base);
                        b.color.lighten(0.1 + 0.1 * math_1.lqrandom());
                    }
                    else {
                        b.color.set(entity.palette.darker[0]);
                        b.color.lighten(-0.05);
                        b.x = x + entity.shield.x;
                        b.y = y + entity.shield.y;
                        b.radius = r;
                        b.x2 = x + entity.shield.x2;
                        b.y2 = y + entity.shield.y2;
                    }
                }
                else {
                    b.color.setAlpha(entity.palette.lighter[0], math_1.lqrandom() * 0.5);
                }
                b.color.a(0.3
                    + b.color.hsla[3] * entity.shield.density * 0.6
                    + (math_1.lqrandom() * 2 - 1) * 0.01);
                entity.addHitbubble(b);
            }
        },
        crumple: (entity, _controller, _animation, _keyframe) => {
            entity.shield.energy = Math.min(entity.shield.energy + constants.ENERGY_CRUMPLE_REGEN, 1);
        },
        fall: (entity, controller, animation, _keyframe) => {
            if (animation.frame < 3) {
                if (entity.teched > constants.TECH_TIMER - constants.TECH_WINDOW) {
                    let option = 'tech';
                    if (controller.left) {
                        if (entity.face === -1) {
                            option = 'techforth';
                        }
                        else {
                            option = 'techback';
                        }
                    }
                    else if (controller.right) {
                        if (entity.face === 1) {
                            option = 'techforth';
                        }
                        else {
                            option = 'techback';
                        }
                    }
                    entity.schedule(option, true);
                    entity.stats.tech++;
                    entity.stun = 0;
                    entity.teched = 0;
                    entity.activeAnimation.data.has('tech')
                        && entity.activeAnimation.data.get('tech')(entity, 3);
                }
            }
        },
        fallen: (entity, controller, _animation, _keyframe) => {
            if (controller.attackPress
                || controller.specialPress
                || controller.grabPress) {
                entity.schedule('floorattack');
            }
            else if (controller.shieldPress || controller.jumpPress) {
                entity.schedule('getup');
            }
            else if (controller.right || controller.rright) {
                entity.playAudio('roll');
                if (entity.face === 1) {
                    entity.schedule('floorforth');
                }
                else {
                    entity.schedule('floorback');
                }
            }
            else if (controller.left || controller.rleft) {
                entity.playAudio('roll');
                if (entity.face === 1) {
                    entity.schedule('floorback');
                }
                else {
                    entity.schedule('floorforth');
                }
            }
            else if (controller.up || controller.rup) {
                entity.schedule('getup');
            }
        },
        ledgestand: (entity, controller, animation, _keyframe) => {
            if (animation.frame < 4
                && ((!controller.noTapJump && controller.upTap === 0)
                    || controller.jumpPress)) {
                const setOccupied = ((entity.animations['ledgehop'].duration
                    - entity.animations['ledgehop'].iasa)
                    * constants.LEDGE_OCCUPANCY_PERCENT)
                    | 0;
                entity.dx = entity.platform.dx;
                entity.dy = entity.platform.dy;
                entity.schedule('ledgehop', true, true);
                entity.ledgeReleased = 0;
                if (entity.ledgeHang) {
                    if (setOccupied > entity.platform.leftOccupied) {
                        entity.platform.leftOccupied = setOccupied;
                    }
                }
                else {
                    if (setOccupied > entity.platform.rightOccupied) {
                        entity.platform.rightOccupied = setOccupied;
                    }
                }
            }
        },
        ledgehang: (entity, controller, animation, _keyframe) => {
            let setOccupied = 2;
            if (!animation.data.has('pause')
                || animation.frame > animation.data.get('pause')) {
                let next = '';
                entity.stats.hangtime++;
                if (engine_1.getTicks() > entity.grabbedOn + constants.LEDGE_HANG_TIME
                    || controller.rdown === 2
                    || (entity.face === 1 && controller.rleft === 2)
                    || (entity.face === -1 && controller.rright === 2)
                    || controller.down === 2
                    || (entity.face === 1 && controller.left === 2)
                    || (entity.face === -1 && controller.right === 2)) {
                    entity.airborne = true;
                    if (controller.downTap === 0) {
                        entity.fastfall = true;
                    }
                    entity.dx = entity.platform.dx;
                    entity.dy = entity.platform.dy;
                    entity.setAnimation('ledgedrop', true);
                    entity.activeAnimation.step();
                    entity.ledgeReleased = 0;
                    entity.intangible = Math.min(entity.intangible, ((constants.LEDGE_INTANGIBILITY * 2) / 3) | 0);
                }
                else if ((controller.up > 1
                    && controller.vmove > -0.7
                    && (controller.noTapJump || controller.upTap > 0))
                    || (entity.face === 1 && controller.right === 2)
                    || (entity.face === -1 && controller.left === 2)) {
                    next = 'ledgestand';
                    entity.ledgeReleased = 0;
                }
                else if ((controller.upTap === 0 && !controller.noTapJump)
                    || controller.jumpPress) {
                    entity.dx = entity.platform.dx;
                    entity.dy = entity.platform.dy;
                    setOccupied
                        = ((entity.animations['ledgehop'].duration
                            - entity.animations['ledgehop'].iasa)
                            * constants.LEDGE_OCCUPANCY_PERCENT)
                            | 0;
                    entity.schedule('ledgehop', true, true);
                    entity.ledgeReleased = 0;
                }
                else if (controller.shieldPress
                    || (entity.face === 1 && controller.rright === 2)
                    || (entity.face === -1 && controller.rleft === 2)) {
                    next = 'ledgeroll';
                }
                else if (controller.attackPress
                    || controller.specialPress
                    || controller.rup === 2) {
                    next = 'ledgeattack';
                }
                if (next !== '') {
                    next = Math.round(entity.damage) >= 100 ? next + '100' : next + '0';
                    setOccupied
                        = ((entity.animations[next].duration - entity.animations[next].iasa)
                            * constants.LEDGE_OCCUPANCY_PERCENT)
                            | 0;
                    entity.schedule(next, true, true);
                    entity.ledgeReleased = 0;
                }
            }
            if (entity.ledgeHang) {
                if (setOccupied > entity.platform.leftOccupied) {
                    entity.platform.leftOccupied = setOccupied;
                }
            }
            else {
                if (setOccupied > entity.platform.rightOccupied) {
                    entity.platform.rightOccupied = setOccupied;
                }
            }
        },
        ledgegrab: (entity, _controller, animation, _keyframe) => {
            let setOccupied = 2;
            if (!animation.data.has('pause')
                || animation.frame > animation.data.get('pause')) {
                let next = '';
                entity.stats.hangtime++;
                if (next !== '') {
                    next = Math.round(entity.damage) >= 100 ? next + '100' : next + '0';
                    setOccupied
                        = ((entity.animations[next].duration - entity.animations[next].iasa)
                            * constants.LEDGE_OCCUPANCY_PERCENT)
                            | 0;
                    entity.schedule(next, true, true);
                    entity.ledgeReleased = 0;
                }
            }
            if (entity.ledgeHang) {
                if (setOccupied > entity.platform.leftOccupied) {
                    entity.platform.leftOccupied = setOccupied;
                }
            }
            else {
                if (setOccupied > entity.platform.rightOccupied) {
                    entity.platform.rightOccupied = setOccupied;
                }
            }
        },
        respawn: (entity, controller, _animation, _keyframe) => {
            if (entity.dummy
                || controller.left
                || controller.right
                || controller.up
                || controller.down) {
                entity.schedule('airborne', true);
                if (!entity.dummy) {
                    entity.invincible = 120;
                }
            }
        },
        hop: (entity, controller, _animation, _keyframe) => {
            if (entity.airborne && !entity.activeAnimation.data.has('airborne')) {
                const fallAnim = entity.animations[entity.animation + 'fall']
                    ? entity.animation + 'fall'
                    : 'hopfall';
                if (entity.dy <= entity.fallSpeed * 3 && entity.animations[fallAnim]) {
                    entity.schedule(fallAnim, true);
                }
            }
            if (constants.AIR_DODGE_OK
                && (controller.hmove || controller.vmove)
                && controller.shieldHardPress) {
                entity.animations['airdodge'].data.set('bufferAngle', controller.radians());
                entity.buffer = 'airdodge';
                entity.buffertime = 8;
            }
        },
        step: (_entity, _controller, _animation, _keyframe) => { },
        helpless: (entity, _controller, _animation, _keyframe) => {
            if (!entity.airborne) {
                entity.schedule('idle', true, true);
            }
        },
    },
    start: {
        pummel: (entity, _controller, _animation, _keyframe) => {
            entity.pummels++;
        },
        airdodge: (entity, controller, animation, _keyframe) => {
            entity.dx = 0;
            entity.dy = 0;
            if (animation.data.get('bufferAngle') >= 0
                || controller.hmove
                || controller.vmove) {
                const a = animation.data.get('bufferAngle') >= 0
                    ? animation.data.get('bufferAngle')
                    : controller.radians();
                entity.kbDecay = entity.activeAnimation.data.get('decay');
                entity.kb = entity.activeAnimation.data.get('airdodgeSpeed');
                entity.kbx = -Math.cos(a) * entity.kb;
                entity.kby = Math.sin(a) * entity.kb;
                entity.kba = ((Math.PI - a) / Math.PI) * 180;
            }
            entity.y -= 0.1;
            if (!entity.airborne) {
                entity.airborne = true;
                entity.hover = entity.platform;
            }
            animation.data.set('bufferAngle', -1);
        },
        hop: (entity, _controller, _animation, _keyframe) => {
            entity.animations['airdodge'].data.set('bufferAngle', -1);
        },
        grab: (entity, _controller, _animation, _keyframe) => {
            const target = entity.lastCollision.entity;
            if (target === null) {
                return;
            }
            if (target.animation !== entity.activeAnimation.data.get('heldAnimation')) {
                target.setAnimation(entity.activeAnimation.data.get('heldAnimation'), true);
                target.face = -entity.face;
                target.lag = 0;
                target.hitlag = false;
                target.kbx = 0;
                target.kby = 0;
                target.kb = 0;
                target.stun = 0;
                target.dx = 0;
                target.dy = 0;
                target.slide = 0;
                target.airjumps = 0;
                entity.grabTime = engine_1.getTicks();
                entity.pummels = 0;
            }
        },
        crumple: (entity, _controller, _animation, _keyframe) => {
            entity.intangible = 12;
        },
        skid: (entity, _controller, _animation, _keyframe) => {
            rendering_1.Effects.skid(entity.x, entity.y - 3, entity.dx * 1.1);
        },
        dash: (entity, controller, animation, _keyframe) => {
            const moonwalkF1 = (controller.hmove < -0.6 && entity.face === 1)
                || (controller.hmove > 0.6 && entity.face === -1);
            const speed = entity.face
                * animation.data.get('initialSpeed')
                * constants.DASHINITIAL_MOD;
            animation.data.set('passthrough', moonwalkF1);
            animation.data.set('moonwalking', moonwalkF1);
            if (entity.lastAnimation !== 'dash') {
                animation.data.set('storedSpeed', 0);
            }
            if (animation.data.has('storedSpeed')
                && animation.data.get('storedSpeed') !== 0) {
                entity.dx -= animation.data.get('storedSpeed') * constants.DASHDANCE_MOD;
                animation.data.set('storedSpeed', 0);
            }
            entity.dx = entity.dx * constants.DASHGRIP;
            if (entity.face === 1) {
                entity.dx = Math.min(entity.dx + speed, speed);
            }
            else {
                entity.dx = Math.max(entity.dx + speed, speed);
            }
            rendering_1.Effects.skid(entity.x, entity.y - 3, animation.data.get('initialSpeed') * -entity.face * 1.5);
        },
        shield: (entity, controller, animation, _keyframe) => {
            entity.shield.stun = 0;
            entity.stun = 0;
            animation.data.set('powershield', false);
            animation.data.set('lasttilt', Math.abs(controller.hmove) > 0.9 && controller.vmove < 0.1
                ? engine_1.getTicks()
                : 0);
            if (controller.shieldHardPress && constants.POWERSHIELD_OK) {
                rendering_1.Effects.powershield(entity, 0, 0, 30, 30);
                animation.data.set('powershield', true);
            }
            entity.shield.density = Math.max(entity.controller.shield, 0.25);
            if (controller.rright) {
                entity.playAudio('roll');
                entity.shield.wait = constants.ENERGY_CHARGE_DELAY;
                entity.setAnimation(entity.face === 1 ? 'dodgeforth' : 'dodgeback', true);
                return;
            }
            if (controller.rleft) {
                entity.playAudio('roll');
                entity.shield.wait = constants.ENERGY_CHARGE_DELAY;
                entity.setAnimation(entity.face === -1 ? 'dodgeforth' : 'dodgeback', true);
                return;
            }
            if (constants.SPOT_DODGE_OK && controller.rdown) {
                entity.shield.wait = constants.ENERGY_CHARGE_DELAY;
                entity.setAnimation('spotdodge', true);
                return;
            }
            if (controller.rup
                || controller.jumpPress
                || (!controller.noTapJump && controller.upTap < 2)) {
                entity.shield.wait = constants.ENERGY_CHARGE_DELAY;
                entity.animations['hop'].data.set('tapJumped', !(controller.jumpPress || controller.rup));
                entity.setAnimation('hop', true);
                return;
            }
        },
        release: (entity, _controller, _animation, _keyframe) => {
            if (entity.held === null || entity.held.removed) {
                return;
            }
            entity.held.lag = 2;
            entity.held.dx = 0;
            entity.held.dy = 0;
            entity.held.slide = 0;
            entity.held.setAnimation(!entity.held.airborne ? 'idle' : 'airborne', true);
        },
        ledgegrab: (_entity, _controller, _animation, _keyframe) => { },
        respawn: (entity, _controller, _animation, _keyframe) => {
            rendering_1.Effects.respawn(entity);
        },
        pivot: (entity, _controller, _animation, _keyframe) => {
            entity.face = -entity.face;
        },
        airjump: (entity, controller, animation, _keyframe) => {
            entity.airjumps++;
            if (animation.data.has('upward')) {
                entity.dy
                    = animation.data.get('upward')
                        - (animation.data.has('jumpDecay')
                            ? animation.data.get('jumpDecay') * entity.airjumps
                            : 0);
            }
            if (animation.data.has('turn')) {
                if ((entity.face === 1 && controller.hmove < -0.5)
                    || (entity.face === -1 && controller.hmove > 0.5)) {
                    entity.face = entity.face * -1;
                }
            }
            if (animation.data.has('di')) {
                const di = animation.data.get('di') * entity.airSpeed;
                if (entity.controller.hmove === 0
                    || Math.sign(entity.controller.hmove) === Math.sign(entity.dx)) {
                    if (entity.dx > 0) {
                        entity.dx = Math.max(entity.dx * entity.controller.hmove, entity.controller.hmove * di);
                    }
                    else {
                        entity.dx = Math.min(entity.dx * -entity.controller.hmove, entity.controller.hmove * di);
                    }
                }
                else {
                    entity.dx = entity.controller.hmove * di;
                }
            }
        },
        cancel: (entity, _controller, _animation, _keyframe) => {
            if (entity.airborne) {
                entity.setAnimation('airborne');
            }
            else {
                entity.setAnimation('idle');
            }
        },
    },
    end: {
        walkpivot: (entity, controller, _animation, _keyframe) => {
            if ((entity.face === 1
                && controller.rightTap <= 5
                && controller.hmove > 0.3)
                || (entity.face === -1
                    && controller.leftTap <= 5
                    && controller.hmove < 0.3)) {
                entity.schedule('dash', true);
            }
        },
        dash: (entity, controller, animation, _keyframe) => {
            animation.data.set('passthrough', false);
            animation.data.set('moonwalking', false);
            entity.storedSpeed = 0;
            entity.animations['dash'].data.set('storedSpeed', 0);
            if ((!controller.right && entity.face === 1)
                || (!controller.left && entity.face === -1)) {
                entity.schedule('dashskid', true);
                return;
            }
            entity.schedule('run', true);
        },
        pivot: (entity, controller, _animation, _keyframe) => {
            if ((controller.hmove > 0.3 && entity.face === 1)
                || (controller.hmove < -0.3 && entity.face === -1)) {
                entity.schedule('dash', true);
                if (entity.storedSpeed !== 0) {
                    entity.animations['dash'].data.set('storedSpeed', entity.storedSpeed);
                }
            }
            entity.storedSpeed = 0;
        },
        respawn: (entity, _controller, _animation, _keyframe) => {
            entity.invincible = 120;
        },
        platformdrop: (entity, _controller, _animation, _keyframe) => {
            entity.y = entity.y + 3;
            entity.dy = entity.platform.dy;
            entity.dx = entity.dx + entity.slide + entity.platform.dx;
            entity.airborne = true;
        },
        turnaround: (entity, controller, animation, _keyframe) => {
            entity.face = -entity.face;
            if (!controller.right && !controller.left) {
                entity.schedule('idle', true);
            }
            else if ((controller.right && entity.face === -1)
                || (controller.left && entity.face === 1)) {
                entity.schedule('turnaround', true);
                entity.dx = entity.dx - animation.accel * entity.face;
            }
            else {
                entity.schedule('run', true);
                entity.dx = entity.dx - animation.accel * entity.face;
            }
        },
    },
    collided: {
        grab: (entity, _controller, animation, _keyframe) => {
            if (entity.lastCollision.entity === null
                || entity.lastCollision.entity.removed) {
                return;
            }
            entity.held = entity.lastCollision.entity;
            entity.schedule(animation.data.get('holdingAnimation'), true);
        },
    },
    shielded: {
        shield: (entity, controller, animation, collision) => {
            if (animation.name === 'shieldup'
                && animation.frame < constants.POWERSHIELD_FRAMES
                && (controller.shieldHardPress || animation.data.get('powershield'))) {
                if (collision.type === HitbubbleType.object) {
                    entity.stun = 0;
                    entity.shield.stun = 0;
                    entity.kb = 0;
                    entity.kbx = 0;
                    entity.kby = 0;
                }
                else {
                    entity.kb = entity.kb * 0.25;
                    entity.kbx = entity.kbx * 0.25;
                    entity.shield.stun = entity.stun;
                }
                entity.lag = 0;
                entity.playAudio('blip');
                return;
            }
            entity.shield.energy
                = entity.shield.energy
                    - (collision.damage / 100) * entity.shield.multiplier;
        },
    },
    grabbed: {
        grab: (entity, _controller, animation, _keyframe) => {
            if (entity.lastCollision.entity === null
                || entity.lastCollision.entity.removed) {
                return;
            }
            entity.held = entity.lastCollision.entity;
            entity.schedule(animation.data.get('holdingAnimation'), true);
        },
        telegrab: (entity, _controller, animation, _keyframe) => {
            if (entity.lastCollision.entity === null
                || entity.lastCollision.entity.removed) {
                return;
            }
            entity.held = entity.lastCollision.entity;
            entity.y = entity.held.y;
            entity.schedule(animation.data.get('holdingAnimation'), true);
        },
    },
    interrupted: {
        release: (entity, _controller, _animation, _keyframe) => {
            if (entity.held === null || entity.held.removed) {
                return;
            }
            entity.held.slide = entity.slide;
            entity.held.dx = 0;
            entity.held.dy = 0;
            entity.held.schedule(!entity.held.airborne ? 'released' : 'airreleased', true);
        },
        ledgehit: (entity, _controller, animation, _keyframe) => {
            entity.x
                = entity.x + animation.data.get('xOffset') * (entity.ledgeHang ? 1 : -1);
            entity.y = entity.y + animation.data.get('yOffset');
            entity.ecb.update(entity.x, entity.y);
            if (entity.launched) {
                console.warn('errr here?', entity.kb, entity.kbx, entity.kby, entity.kbDecay, entity.animation);
                entity.phase = true;
            }
        },
        respawn: (entity, _controller, _animation, _keyframe) => {
            entity.invincible = 120;
        },
        airjump: (entity, _controller, animation, _keyframe) => {
            if (animation.frame < 5) {
                if (entity.teched > constants.V_CANCEL_WINDOW) {
                    entity.stun = 0;
                }
                if (animation.frame < 3) {
                    entity.airjumps = Math.max(entity.airjumps - 1, 0);
                }
            }
        },
    },
    canceled: {
        meteor: (entity, _controller, _animation, _keyframe) => {
            entity.flash = 8;
            entity.playAudio('meteorcancel');
        },
    },
    effect: {
        hop: (entity, controller, _animation, _keyframe) => {
            rendering_1.Effects.skid(entity.x, entity.y - 3, ((controller.left && entity.face === 1)
                || (controller.right && entity.face === -1)
                ? 1
                : -1)
                * (entity.dx + entity.slide)
                * 0.5);
        },
        burst: (entity, _controller, _animation, keyframe) => {
            const colorData = keyframe.burstColor || null;
            if (colorData === null) {
                tColor.setHSLA(gl_matrix_1.vec4.set(v4, 0, 0, 1, 1));
            }
            else {
                tColor.setHSLA(gl_matrix_1.vec4.set(v4, colorData[0], colorData[1], colorData[2], colorData[3]));
            }
            rendering_1.Effects.burst(entity.x + entity.headbubble.x, entity.y + entity.headbubble.y, 1.5, 4, tColor, v2);
        },
    },
    onInjured: {
        parry: ((_b, hb) => {
            if (hb.state === HurtbubbleState.heavyArmor) {
                return true;
            }
            return false;
        }),
    },
};
class Animation {
    constructor(entity, animation) {
        this.type = AnimationType.movement;
        this.keyframe = 0;
        this.frame = 0;
        this.partial = 0;
        this.midframe = 0;
        this.ticks = 1;
        this.repeated = 0;
        this.continued = false;
        this.cancellable = null;
        this.keyframes = null;
        this.frameDuration = 0;
        this.freshKeyframe = true;
        this.hit = false;
        this.keepCollisions = false;
        this.staled = -1;
        this.charged = 0;
        this.duration = 0;
        this.iasa = 0;
        this.start = null;
        this.freshStart = null;
        this.noFastfall = false;
        this.dx = 0;
        this.dy = 0;
        this.accel = 0;
        this.slide = 0;
        this.grabDirections = 0;
        this.interact = null;
        this.handler = null;
        this.alwaysHandle = false;
        this.speed = 0;
        this.transition = '';
        this.tiltAnimation = '';
        this.name = '';
        this.end = null;
        this.angles = 0;
        this.pseudojump = false;
        this.events = null;
        this.data = new Map();
        this.disableIK = false;
        this.hardIK = false;
        this.gravity = 1;
        this.aerodynamics = 1;
        this.airResistance = 0;
        this.riseFriction = 1;
        this.fallFriction = 1;
        this.nodi = false;
        this.itan = false;
        this.itanStart = -1;
        this.itanEnd = -1;
        this.invin = false;
        this.invinStart = -1;
        this.invinEnd = -1;
        this.armored = false;
        this.armorStart = -1;
        this.armorEnd = -1;
        this.entity = entity;
        if (typeof animation.keyframes === 'string') {
            animation.keyframes = entity.animations[animation.keyframes].keyframes;
        }
        for (const i of Object.getOwnPropertyNames(animation)) {
            if (i === 'type') {
                if (!utils_1.objHas(AnimationType, animation.type)) {
                    console.log('Error loading animation type:', animation.type);
                    this.type = AnimationType.passive;
                    continue;
                }
                this.type = +(typeof animation.type === 'string'
                    ? AnimationType[animation.type]
                    : animation.type);
            }
            else if (utils_1.objHas(this, i)) {
                ;
                this[i] = animation[i];
            }
            else {
                this.data.set(i, animation[i]);
            }
        }
        if (this.events === null) {
            for (const kf of this.keyframes) {
                if (kf.hurtbubbles && Array.isArray(kf.hurtbubbles)) {
                    for (let i = 3; i < kf.hurtbubbles.length; i += 4) {
                        if (typeof kf.hurtbubbles[i] === 'string') {
                            const state = HurtbubbleState[kf.hurtbubbles[i]] ?? 1;
                            kf.hurtbubbles[i] = state;
                        }
                    }
                }
                if (kf.hitbubbles) {
                    const followed = new Set();
                    this.events = [];
                    for (const hb of kf.hitbubbles) {
                        if (hb.follow && !followed.has(hb.follow)) {
                            let color = null;
                            if (utils_1.objHas(hb, 'color')) {
                                color = gl_matrix_1.vec4.create();
                                color_1.hsla2rgba(color, hb.color[0], hb.color[1], hb.color[2], hb.color[3]);
                                color[3] = hb.color[3] * 2 * hb.radius;
                            }
                            followed.add(hb.follow);
                            this.events.push({
                                effect: 'glow',
                                when: 'hitbubbles',
                                follow: hb.follow,
                                color: color,
                            });
                        }
                    }
                    if (this.events.length === 0) {
                        this.events.push(entity.namedbubbles.has('rhand')
                            ? {
                                effect: 'glow',
                                when: 'hitbubbles',
                                follow: 'rhand',
                            }
                            : {
                                effect: 'glow',
                                when: 'hitbubbles',
                                offset: [0, entity.height * 0.5],
                            });
                    }
                    break;
                }
            }
        }
        else {
            for (const e of this.events) {
                if (utils_1.objHas(e, 'color')) {
                    const color = gl_matrix_1.vec4.create();
                    color_1.hsla2rgba(color, e.color[0], e.color[1], e.color[2], e.color[3]);
                    color[3] = e.color[3];
                    e.color = color;
                }
            }
        }
        this.frames = precomputeAnimation(entity, animation);
        this.duration = animation.duration | 0;
        if (this.iasa < 0) {
            this.iasa = this.duration + this.iasa;
        }
        this.keyframeData = this.keyframes[this.keyframe];
        this.frameDuration = this.keyframeData.duration;
        this.freshKeyframe = true;
    }
    reset() {
        this.staled = -1;
        this.frame = 0;
        this.partial = 0;
        this.midframe = 0;
        this.keyframe = 0;
        this.ticks = 1;
        this.repeated = 0;
        this.continued = false;
        this.hit = false;
        if (!this.keepCollisions) {
            this.entity.collided.length = 0;
        }
        this.keyframeData = this.keyframes[this.keyframe];
        this.frameDuration = this.keyframeData.duration;
        this.freshKeyframe = true;
    }
    resetBubbles() {
        setBubbles(this.entity, this.frames[this.frame], this.keyframeData);
    }
    applyAcceleration() {
        if (utils_1.objHas(this.keyframeData, 'accel')) {
            this.entity.dx
                = this.entity.dx + this.entity.face * this.keyframeData.accel;
        }
    }
    apply() {
        if (!this.entity.airborne
            && (this.speed || utils_1.objHas(this.keyframeData, 'speed'))) {
            this.entity.dx
                = (this.keyframeData.speed || this.speed) * this.entity.face * this.ticks;
        }
        if (this.slide !== 0) {
            this.entity.slide = this.entity.face * this.slide * this.ticks;
        }
        if (utils_1.objHas(this.keyframeData, 'setSlide')) {
            this.entity.slide
                = this.entity.face * this.keyframeData.setSlide * this.ticks;
        }
        if (this.entity.intangible <= 0) {
            if (this.itan) {
                this.entity.intangible = 1;
            }
            if (this.itanStart >= 0
                && this.frame >= this.itanStart
                && (this.itanEnd < 0 || this.frame <= this.itanEnd)) {
                this.entity.intangible = 1;
            }
            if (this.itanStart < 0 && this.frame <= this.itanEnd) {
                this.entity.intangible = 1;
            }
        }
        if (this.entity.invincible <= 0) {
            if (this.invin) {
                this.entity.invincible = 1;
            }
            if (this.invinStart >= 0
                && this.frame >= this.invinStart
                && (this.invinEnd < 0 || this.frame <= this.invinEnd)) {
                this.entity.invincible = 1;
            }
            if (this.invinStart < 0 && this.frame <= this.invinEnd) {
                this.entity.invincible = 1;
            }
        }
        if (this.entity.armored <= 0) {
            if (this.armored) {
                this.entity.armored = 1;
            }
            if (this.armorStart >= 0
                && this.frame >= this.armorStart
                && (this.armorEnd < 0 || this.frame <= this.armorEnd)) {
                this.entity.armored = 1;
            }
            if (this.armorStart < 0 && this.frame <= this.armorEnd) {
                this.entity.armored = 1;
            }
        }
    }
    applyFreshKeyframe() {
        if (this.keyframe === 0) {
            this.freshStart
                && !this.continued
                && this.freshStart(this.entity, this.entity.controller, this, this.keyframeData);
            if (this.noFastfall || utils_1.objHas(this.keyframeData, 'noFastfall')) {
                this.entity.fastfall = false;
            }
            if (this.dy) {
                this.entity.dy = this.dy;
            }
            if (this.dx) {
                this.entity.dx = this.entity.face * this.dx;
            }
            if (this.accel) {
                this.entity.dx = this.entity.dx + this.accel * this.entity.face;
            }
            if (this.slide) {
                this.entity.slide = this.entity.slide + this.entity.dx;
                this.entity.dx = 0;
            }
            if (this.pseudojump) {
                this.entity.pseudojumps++;
            }
        }
        if (utils_1.objHas(this.keyframeData, 'airjump')) {
            this.entity.airjumps++;
        }
        if (utils_1.objHas(this.keyframeData, 'effect')) {
            this.keyframeData.effect(this.entity, this.entity.controller, this, this.keyframeData);
        }
        if (utils_1.objHas(this.keyframeData, 'airborne')) {
            if (utils_1.objHas(this.keyframeData, 'speed')) {
                this.entity.dy = this.keyframeData.speed;
            }
            if (!this.entity.airborne) {
                this.entity.hover = this.entity.platform;
                this.entity.airborne = true;
                if (this.entity.px) {
                    this.entity.dx = this.entity.px;
                    this.entity.px = 0;
                }
                this.entity.dx
                    = this.entity.dx + this.entity.slide + this.entity.platform.dx;
                this.entity.dy = this.entity.dy + this.entity.platform.dy;
            }
            if (utils_1.objHas(this.keyframeData, 'jump')) {
                const jumpDI = this.entity.airSpeed;
                this.entity.hover = this.entity.platform;
                if (this.entity.controller
                    && (this.entity.controller.jump
                        || (!this.entity.controller.noTapJump
                            && this.entity.animations['hop'].data.get('tapJumped')
                            && this.entity.controller.up))) {
                    this.entity.dy = this.keyframeData.fullJump;
                }
                else {
                    this.entity.dy = this.keyframeData.jump;
                }
                if (this.entity.controller.hmove === 0
                    || Math.sign(this.entity.controller.hmove) === Math.sign(this.entity.dx)) {
                    if (this.entity.dx > 0) {
                        this.entity.dx = Math.max(this.entity.dx * (this.entity.controller.hmove * 0.5 + 0.5), this.entity.controller.hmove * jumpDI);
                    }
                    else {
                        this.entity.dx = Math.min(this.entity.dx * -(this.entity.controller.hmove * 0.5 - 0.5), this.entity.controller.hmove * jumpDI);
                    }
                }
                else {
                    this.entity.dx
                        = this.entity.controller.hmove * jumpDI;
                }
                const h = this.entity.controller.hmove;
                if (Math.abs(h) > 0.7) {
                    if (Math.sign(h) === this.entity.face) {
                        if (this.entity.animations.hopforth) {
                            this.entity.setAnimation('hopforth', true);
                        }
                    }
                    else if (this.entity.animations.hopback) {
                        this.entity.setAnimation('hopback', true);
                    }
                }
            }
        }
        if (utils_1.objHas(this.keyframeData, 'cost')) {
            this.entity.shield.energy = Math.max(this.entity.shield.energy - this.keyframeData.cost, 0);
            this.entity.shield.lastReduced = engine_1.getTicks();
        }
        if (utils_1.objHas(this.keyframeData, 'audio')) {
            this.entity.playAudio(this.keyframeData.audio);
        }
        if (utils_1.objHas(this.keyframeData, 'start')) {
            this.keyframeData.start(this.entity, this.entity.controller, this, this.keyframeData);
        }
        if (utils_1.objHas(this.keyframeData, 'reset') && this.entity.lag <= 0) {
            this.entity.collided.length = 0;
        }
        if (utils_1.objHas(this.keyframeData, 'slide')) {
            this.entity.slide = this.entity.face * this.keyframeData.slide;
        }
        if (utils_1.objHas(this.keyframeData, 'dy')) {
            this.entity.dy = this.keyframeData.dy;
        }
        if (utils_1.objHas(this.keyframeData, 'spawn')) {
            if (typeof this.keyframeData.spawn === 'function') {
                this.entity.spawn(this.keyframeData.spawn(this.entity, this.entity.controller, this, this.keyframeData));
            }
            else {
                this.entity.spawn(this.keyframeData.spawn);
            }
        }
        if (utils_1.objHas(this.keyframeData, 'brake')) {
            this.entity.dx = this.entity.dx * this.keyframeData.brake;
            this.entity.dy = this.entity.dy * this.keyframeData.brake;
        }
        if (utils_1.objHas(this.keyframeData, 'upward')) {
            this.entity.dy
                = this.keyframeData.upward
                    - (this.keyframeData.jumpDecay
                        ? this.keyframeData.jumpDecay * this.entity.airjumps
                        : 0);
        }
        if (utils_1.objHas(this.keyframeData, 'di')) {
            if (this.entity.controller.hmove < 0) {
                this.entity.dx = Math.min(this.entity.controller.hmove * this.entity.airSpeed, this.entity.dx);
            }
            else if (this.entity.controller.hmove > 0) {
                this.entity.dx = Math.max(this.entity.controller.hmove * this.entity.airSpeed, this.entity.dx);
            }
        }
        if (utils_1.objHas(this.keyframeData, 'dx')) {
            this.entity.dx = this.entity.face * this.keyframeData.dx;
        }
        this.freshKeyframe = false;
    }
    endAnimation() {
        const entity = this.entity;
        this.end !== null
            && this.end(entity, entity.controller, this, this.keyframeData);
        if (entity.scheduledAnimation[0] !== '') {
            const a = entity.scheduledAnimation[0];
            entity.scheduledAnimation[0] = '';
            if (entity.setAnimation(a, entity.scheduledAnimation[1], entity.scheduledAnimation[2])) {
                return;
            }
        }
        if (this.transition !== 'tilt') {
            let transition = this.transition;
            if (entity.buffertime > 0 && entity.buffer) {
                if (!entity.animations[entity.buffer].data.has('cost')
                    || +entity.animations[entity.buffer].data.get('cost')
                        <= entity.shield.energy) {
                    if (entity.setAnimation(entity.buffer, true)) {
                        entity.buffertime = 0;
                        entity.buffer = '';
                        return;
                    }
                }
            }
            if (transition === '' && entity.buffer === '') {
                transition = entity.airborne ? defaultAirborne : defaultAnimation;
            }
            if (!entity.setAnimation(transition, true)) {
                transition = entity.airborne ? defaultAirborne : defaultAnimation;
                entity.setAnimation(transition, true);
            }
            if (this.transition === this.name) {
                this.continued = true;
            }
            return;
        }
        else {
            const angle = math_1.computeAngle(entity.controller.hmove, entity.controller.vmove);
            let transition = this.tiltAnimation || this.name;
            const tiltAngle = 30;
            if (this.angles & 8 && entity.controller.hmove * entity.face < -0.1) {
                transition = transition + '-back';
            }
            else if ((!this.angles || this.angles & 2)
                && angle <= 180 - tiltAngle
                && angle >= tiltAngle) {
                transition = transition + '-up';
            }
            else if ((!this.angles || this.angles & 4)
                && angle >= 180 + tiltAngle
                && angle <= 360 - tiltAngle) {
                transition = transition + '-down';
            }
            if (transition === this.name) {
                transition = transition + '-neutral';
            }
            entity.setAnimation(transition, true);
        }
    }
    step() {
        const entity = this.entity;
        if (this.freshKeyframe) {
            this.applyFreshKeyframe();
        }
        if (!utils_1.objHas(this.keyframeData, 'interpolate')) {
            setBubbles(entity, this.frames[this.frame], this.keyframeData);
        }
        else {
            if (!utils_1.objHas(this.keyframeData, 'hurtbubbles')) {
                this.keyframeData.hurtbubbles = this.keyframes[this.keyframe + 1].hurtbubbles;
            }
            setBubbles(entity, exports.tweenKeyframes(entity.lastFrame, this.keyframeData.hurtbubbles, this.keyframeData.easeFn((this.frame + 1) / (this.keyframeData.duration + 2))), this.keyframeData);
        }
        if (entity.lag <= 0
            && utils_1.objHas(this.keyframeData, 'repeat')
            && this.repeated <= this.midframe - this.keyframeData.repeat
            && this.midframe > 0) {
            this.repeated = this.midframe;
            entity.collided.length = 0;
        }
        this.handler !== null
            && (entity.lag <= 0 || this.alwaysHandle)
            && this.handler(entity, entity.controller, this, this.keyframeData);
        utils_1.objHas(this.keyframeData, 'handler')
            && entity.lag <= 0
            && this.keyframeData.handler(entity, entity.controller, this, this.keyframeData);
        this.apply();
        this.applyAcceleration();
        if (entity.lag <= 0) {
            const diff = this.ticks * entity.animationSpeed + this.partial;
            this.frame = (this.frame + diff) | 0;
            this.midframe = (this.midframe + diff) | 0;
            this.partial = diff % 1;
        }
        if (this.frame >= this.duration) {
            this.endAnimation();
        }
        while (this.midframe >= this.frameDuration) {
            this.midframe = this.midframe - this.frameDuration;
            this.keyframe = this.keyframe + 1;
            if (this.keyframe < this.keyframes.length - 1) {
                this.keyframeData = this.keyframes[this.keyframe];
                this.frameDuration = this.keyframeData.duration;
                this.freshKeyframe = true;
                return;
            }
            this.endAnimation();
        }
    }
    static prepareAnimationData(animationsDict, handlers) {
        const cancelAnim = animationsDict['airborne-cancel'];
        const generatedCancels = new Set();
        const animationNames = Object.getOwnPropertyNames(animationsDict);
        for (let i = 0; i < animationNames.length; i++) {
            const anim = animationsDict[animationNames[i]];
            const keys = anim.keyframes.length;
            const animProps = Object.getOwnPropertyNames(anim);
            anim.name = animationNames[i];
            if (!isNaN(anim.cancel)) {
                const cancel = anim.cancel | 0;
                anim.cancel = 'cancel-' + cancel;
                if (!generatedCancels.has(cancel)) {
                    const kfs = [];
                    let d = 0;
                    const newKf = {};
                    generatedCancels.add(cancel);
                    for (let j = 0; j < cancelAnim.keyframes.length; j++) {
                        const kf = {};
                        for (const key of Object.getOwnPropertyNames(cancelAnim.keyframes[j])) {
                            kf[key] = cancelAnim.keyframes[j][key];
                        }
                        if (j > 0 && j < cancelAnim.keyframes.length - 1) {
                            d = d + (kf.duration | 0);
                        }
                        kfs.push(kf);
                    }
                    d--;
                    kfs[0].duration = cancel - 1;
                    for (const key of Object.getOwnPropertyNames(cancelAnim)) {
                        newKf[key] = cancelAnim[key];
                    }
                    newKf.name = anim.cancel;
                    newKf.keyframes = kfs;
                    newKf.iasa = d;
                    animationsDict[newKf.name] = newKf;
                }
            }
            for (let j = 0; j < animProps.length; j++) {
                const p = animProps[j];
                if (typeof anim[p] !== 'string') {
                    continue;
                }
                if (handlers
                    && handlerEvents.has(p)
                    && utils_1.objHas(handlers, anim[p])) {
                    anim[p] = handlers[anim[p]];
                    continue;
                }
                if (utils_1.objHas(defaultHandlers, p)
                    && utils_1.objHas(defaultHandlers[p], anim[p])) {
                    anim[p] = defaultHandlers[p][anim[p]];
                    continue;
                }
                if (handlerEvents.has(p) && utils_1.objHas(defaultActions, anim[p])) {
                    anim[p] = defaultActions[anim[p]];
                    continue;
                }
            }
            if (anim.handler && typeof anim.handler !== 'function') {
                console.warn('Possible bug: handler is ' + anim.handler);
            }
            for (let key = 0; key < keys; key++) {
                const kf = anim.keyframes[key];
                const kfProps = Object.getOwnPropertyNames(kf);
                for (let j = 0; j < kfProps.length; j++) {
                    const p = kfProps[j];
                    if (typeof kf[p] !== 'string') {
                        continue;
                    }
                    if (handlers
                        && handlerEvents.has(p)
                        && utils_1.objHas(handlers, kf[p])) {
                        kf[p] = handlers[kf[p]];
                        continue;
                    }
                    if (utils_1.objHas(defaultHandlers, p)
                        && utils_1.objHas(defaultHandlers[p], kf[p])) {
                        kf[p] = defaultHandlers[p][kf[p]];
                        continue;
                    }
                    if (handlerEvents.has(p) && utils_1.objHas(defaultActions, kf[p])) {
                        kf[p] = defaultActions[kf[p]];
                        continue;
                    }
                }
                if (kf.hurtbubbles) {
                    const bubbles = kf.hurtbubbles.length;
                    for (let bubble = 1; bubble < bubbles; bubble = bubble + 4) {
                        kf.hurtbubbles[bubble] = -kf.hurtbubbles[bubble];
                    }
                }
                if (kf.hitbubbles && kf.hitbubbles !== true) {
                    for (let j = 0; j < kf.hitbubbles.length; j++) {
                        const hb = kf.hitbubbles[j];
                        const hbProps = Object.getOwnPropertyNames(hb);
                        for (let ii = 0; ii < hbProps.length; ii++) {
                            const p = hbProps[ii];
                            if (typeof hb[p] !== 'string') {
                                continue;
                            }
                            if (handlers
                                && (p.startsWith('on') || handlerEvents.has(p))
                                && utils_1.objHas(handlers, hb[p])) {
                                hb[p] = handlers[hb[p]];
                                continue;
                            }
                        }
                    }
                }
            }
        }
    }
}
exports.Animation = Animation;
const getHitbubbleCount = () => hitbubbleCount;
exports.getHitbubbleCount = getHitbubbleCount;
utils_1.Sync.loading.promise.then(() => {
    engine_1.getConstants(c => {
        constants = c;
    });
});
//# sourceMappingURL=animation.js.map