orbit/js/ui/CombatBar.js

550 lines
23 KiB
JavaScript

import Phaser from '../vendor/phaser.js';
import { config } from '../config/Config.js';
import { XpManager } from '../combat/XpManager.js';
import { toColor, toCss } from '../utils/Color.js';
import { fontStack } from '../utils/Theme.js';
import { canvasTexture } from '../utils/Textures.js';
const FONT_FALLBACK = "'Segoe UI', 'Helvetica Neue', Arial, sans-serif";
const DOT_KEY = '__bar_orb_dot'; // the shared soft radial dot (orb gloss/shade)
const DEPTH = 50; // the deck's depth (ActionBar) — the bar swaps in its place
/** Nudge a 0xRRGGBB toward white (t = 0..1). Pure math. */
function mixWhite(n, t) {
const m = (c) => Math.round(c + (255 - c) * t);
return (m((n >> 16) & 255) << 16) | (m((n >> 8) & 255) << 8) | m(n & 255);
}
/** Thousands-separate an XP count (the XP row's numbers column). */
function fmtXp(n) {
return Math.round(Math.max(0, Number(n) || 0)).toLocaleString('en-US');
}
/**
* The soft radial dot (white, 64px) the orb shading layers use — a
* white-tinted copy reads as highlight, a black tint as shadow. Cached
* by key, so the bar + any other consumer share one texture.
*/
function orbDotKey(scene) {
if (scene.textures.exists(DOT_KEY)) return DOT_KEY;
canvasTexture(scene, DOT_KEY, 64, 64, (ctx, w, h) => {
const c = w / 2;
const g = ctx.createRadialGradient(c, c, 0, c, c, c);
g.addColorStop(0.0, 'rgba(255,255,255,1)');
g.addColorStop(0.25, 'rgba(255,255,255,0.8)');
g.addColorStop(0.6, 'rgba(255,255,255,0.22)');
g.addColorStop(1.0, 'rgba(255,255,255,0)');
ctx.fillStyle = g;
ctx.fillRect(0, 0, w, h);
});
return DOT_KEY;
}
/**
* The COMBAT BAR — the combat-mode bar (data/combat.json → bar), SHOWN
* IN PLACE OF THE ACTION DECK while combat is live (GameScene swaps them:
* the deck hides, this takes its place, and back when the pack is
* wiped). The concept (assets/bar.png — a storyboard, NOT a color ref):
*
* (HULL orb + SHIELD orb) [1][2][3][4][5][6][7][8] (ENERGY orb)
* ────────────────────────────────────────────────────────────────────
* ┌─ LV n ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ a / b XP ─┐
* └──────────── [1]'s left edge ──────────── [8]'s right edge ─────────┘
*
* left — the hull ORB (red) with the SHIELD orb (cyan, semi-
* transparent) concentric over it: the shield sits IN FRONT of
* the hull, so the red reads through the cyan.
* bottom — the XP bar (the combat level + bank — data/progression.json,
* the xp gold): ONE full bar spanning the skill seats' length
* (the number buttons' row), the summary embedded — "LV n"
* on the left edge, the numbers on the right (banked XP over
* the next level's cost; the banked total at the cap, where
* the bar holds full with a soft glow and the xp keeps
* counting — the seam for the post-cap currency). Each text
* rides a soft dark chip (sized to the text) so the gold
* reads over the gold fill as it does over the track; the
* fill wears the orbs' glass idiom (sheen + meniscus edge).
* A snapshot without xp state (the range's bar) hides the row.
* middle — the eight SKILL seats, wearing keys 1-8. The skills land
* later — for now they are empty seats (a press lands on
* o.onCast(index); the system's cast() is the no-op stub).
* right — the ENERGY orb (yellow — the skills' resource).
*
* BOTTOM-ALIGNED and CENTERED — sized to its contents, not stretched
* edge-to-edge like the deck. The orbs fill like a Diablo orb: the
* liquid column drains down as the value drops (setStats → _drawLiquid).
*
* Orb shading (so the circles read as ORBS, not 2D circles): a deep
* interior disc, the liquid column, a soft bottom shadow, a soft top
* gloss + small specular (additive), and the colored rim on top — all
* procedural, the soft layers from one shared soft-dot texture.
*
* Built the way ActionBar is (the deck's pattern, verified on this
* Giedi build): children created with scene.add.*, parented into the
* container with this.add(), each setScrollFactor(0) so they sit over
* the world at any zoom. Insertion order IS draw order — the shield
* orb's layers come after the hull orb's, so it sits in front.
*/
export class CombatBar extends Phaser.GameObjects.Container {
/**
* @param {Phaser.Scene} scene a scale/texture-equipped scene (GameScene)
* @param {object} [o]
* onCast (index) => {} — a skill seat was pressed (index 0-7)
*/
constructor(scene, o = {}) {
super(scene, 0, 0);
// v4 quirk (same note as ActionBar): register the directly-built
// container on the scene's display list or it never renders.
this.scene.add.existing(this);
this.setScrollFactor(0); // UI — pinned to the screen, not the world
this.setDepth(DEPTH); // the deck's depth
this.onCast = typeof o?.onCast === 'function' ? o.onCast : null;
const bar = config.section('combat.bar', {});
const { width: W, height: H } = scene.scale;
const c = bar.colors ?? {};
const bg = toColor(c.bg, 0x070d18);
const bgAlpha = Number(c.bgAlpha) || 0.92;
const edge = toColor(c.edge, 0x22405f);
const hullC = toColor(c.hull, 0xff2d6f);
const shieldC = toColor(c.shield, 0x00e5ff);
const energyC = toColor(c.energy, 0xffd84d);
const slotBg = toColor(c.slotBg, 0x0b1322);
const slotBorder = toColor(c.slotBorder, 0x22405f);
const keyCss = toCss(c.key, '#5c7196');
// ---- geometry (the concept: centered strip, orbs at the ends) ----
const mB = Number(bar.margin?.bottom) || 26;
let stripH = Number(bar.strip?.height) || 64;
let edgePad = Number(bar.margin?.edge) || 10;
let orbGap = Number(bar.orbGap) || 24;
let R = Math.max(18, Number(bar.circle?.radius) || 46);
const sh = bar.circle?.shield ?? {};
const shScale = Number(sh.scale) || 1.12;
const shOX = Number(sh.offsetX) ?? 0;
const shOY = Number(sh.offsetY) ?? 0;
const shAlpha = Number(sh.fillAlpha) || 0.55;
const hullAlpha = Number(bar.circle?.hull?.fillAlpha) || 0.95;
const n = Math.max(1, Number(bar.slot?.count) || 8);
let S = Math.max(20, Number(bar.slot?.size) || 44);
let G = Number(bar.slot?.gap) || 10;
const keyPx = Math.max(8, Number(bar.slot?.keySize) || 12);
// Narrow viewports: scale the linear dims down (keep the anchor).
const fit = () => {
const sR = R * shScale;
const SW = n * S + (n - 1) * G;
return (R + shOX * R + sR) + 2 * orbGap + SW + R + 2 * edgePad;
};
let totalW = fit();
if (totalW > W - 16) {
const k = Math.max(0.5, (W - 16) / totalW);
stripH *= k; edgePad *= k; orbGap *= k; R *= k; S *= k; G *= k;
totalW = fit();
}
const sR = R * shScale;
const SW = n * S + (n - 1) * G;
const x0 = (W - totalW) / 2;
// The XP row's band (the bar grows DOWNWARD from the orbs' row —
// the panel's bottom edge stays put, the top grows): the separator
// hairline + the bar's band (the bar is tall enough for its
// embedded summary text).
const sepBandH = 14;
const xpBandH = 24;
const panelBottom = H - mB;
const panelTop = panelBottom - (stripH + sepBandH + xpBandH);
const stripTop = panelTop;
const stripBottom = panelTop + stripH; // the orbs' row (the original strip)
const cY = (stripTop + stripBottom) / 2;
const sepY = stripBottom + sepBandH / 2;
const xpRowCY = stripBottom + sepBandH + xpBandH / 2;
const hull = { cx: x0 + edgePad + R, cy: cY, r: R };
const shield = { cx: hull.cx + shOX * R, cy: cY + shOY * R, r: sR };
const firstSlotL = shield.cx + shield.r + orbGap;
const eCx = firstSlotL + SW + orbGap + R;
const x1 = eCx + R + edgePad;
// ---- the panel (the strip the seats sit in + the XP row below) ----
const panelH = panelBottom - panelTop;
const panel = scene.add.graphics();
panel.fillStyle(bg, bgAlpha);
panel.fillRoundedRect(x0, panelTop, x1 - x0, panelH, Math.min(12, panelH * 0.1));
panel.lineStyle(2, edge, 0.55); // the top rule (the concept's double line)
panel.lineBetween(x0 + 12, panelTop + 1.5, x1 - 12, panelTop + 1.5);
panel.lineStyle(1.5, edge, 0.4);
panel.lineBetween(x0 + 12, panelBottom - 1.5, x1 - 12, panelBottom - 1.5);
panel.lineStyle(1, edge, 0.3); // the separator (the orbs' row / the XP row)
panel.lineBetween(x0 + 12, sepY, x1 - 12, sepY);
panel.setScrollFactor(0).setDepth(DEPTH);
this.add(panel);
// ---- the orbs ------------------------------------------------------
const dot = orbDotKey(scene);
const track = bg; // the deep interior (the "empty" orb body)
const mkOrb = (cx, cy, r, color, fillAlpha, bodyAlpha = 0.94, shadeAlpha = 0.32) => {
// 1 — the body: a dark outer edge + the deep interior disc.
const base = scene.add.graphics();
base.lineStyle(3, 0x01040b, 0.65);
base.strokeCircle(cx, cy, r + 1);
base.fillStyle(track, bodyAlpha);
base.fillCircle(cx, cy, r);
// 2 — the liquid column (dynamic — setStats drains it).
const liquid = scene.add.graphics();
// 3 — the soft bottom shadow (weight, below the liquid line).
const shade = scene.add.image(cx, cy + r * 0.6, dot)
.setTint(0x000000).setAlpha(shadeAlpha)
.setScale((r * 2.3) / 64, (r * 1.15) / 64);
// 4 — the glass: a soft top gloss + a small specular (additive).
const gloss = scene.add.image(cx - r * 0.28, cy - r * 0.44, dot)
.setTint(0xffffff).setAlpha(0.15).setBlendMode(Phaser.BlendModes.ADD)
.setScale((r * 2.0) / 64, (r * 1.05) / 64);
const spec = scene.add.image(cx - r * 0.36, cy - r * 0.52, dot)
.setTint(0xffffff).setAlpha(0.35).setBlendMode(Phaser.BlendModes.ADD)
.setScale((r * 0.7) / 64);
// 5 — the colored rim, on top of everything (crisp orb edge).
const rim = scene.add.graphics();
rim.lineStyle(2, color, 0.95);
rim.strokeCircle(cx, cy, r - 0.5);
rim.lineStyle(1, mixWhite(color, 0.4), 0.28); // rim light, just inside
rim.strokeCircle(cx, cy, r - 2.5);
for (const gobj of [base, liquid, shade, gloss, spec, rim]) {
gobj.setScrollFactor(0).setDepth(DEPTH);
this.add(gobj);
}
const orb = { cx, cy, r, color, fillAlpha, liquid, frac: 1 };
this._drawLiquid(orb);
return orb;
};
// The left cluster: HULL first, SHIELD on top — insertion order is
// draw order, so the shield's semi-transparent liquid sits OVER the
// hull's (the shield shares the hull's center and reads as the
// ship's bubble: shields in front of hull).
this.orbHull = mkOrb(hull.cx, hull.cy, hull.r, hullC, hullAlpha);
this.orbShield = mkOrb(shield.cx, shield.cy, shield.r, shieldC, shAlpha, 0.38, 0.16);
// ---- the skill seats (middle; keys 1-8; the skills land later) ----
const rad = Math.max(4, S * 0.18);
const slotG = scene.add.graphics();
for (let i = 0; i < n; i++) {
const lx = firstSlotL + i * (S + G);
const ly = cY - S / 2;
slotG.fillStyle(slotBg, 1);
slotG.fillRoundedRect(lx, ly, S, S, rad);
slotG.lineStyle(1.5, slotBorder, 0.9);
slotG.strokeRoundedRect(lx, ly, S, S, rad);
slotG.lineStyle(1.5, edge, 0.45); // the top lip (the deck's button idiom)
slotG.lineBetween(lx + rad, ly + 1, lx + S - rad, ly + 1);
}
slotG.setScrollFactor(0).setDepth(DEPTH);
this.add(slotG);
this.slots = [];
for (let i = 0; i < n; i++) {
const lx = firstSlotL + i * (S + G);
const ly = cY - S / 2;
const hit = scene.add.rectangle(lx + S / 2, cY, S, S, 0xffffff, 0);
hit.setScrollFactor(0).setDepth(DEPTH);
this.add(hit);
const keyT = scene.add
.text(lx + 6, ly + 5, String(i + 1), {
fontFamily: fontStack('body', FONT_FALLBACK),
fontSize: `${keyPx}px`,
color: keyCss,
})
.setOrigin(0, 0.5)
.setScrollFactor(0)
.setDepth(DEPTH);
this.add(keyT);
hit.setInteractive({ useHandCursor: true });
hit.on('pointerdown', () => {
if (this.onCast) this.onCast(i);
});
this.slots.push({ x: lx + S / 2, hit, keyT });
}
// ---- the energy orb (right end) -----------------------------------
this.orbEnergy = mkOrb(eCx, cY, R, energyC, 0.95);
// ---- the XP row (below the orbs' row — the level + the bank) -----
// The combat level + the XP bank (data/progression.json — the row is
// the xp gold, the progression currency's color). ONE full bar
// spanning the skill seats' length (the number buttons' row), the
// summary embedded in it:
//
// LV n ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ a / b XP
//
// "LV n" rides the bar's left edge, the numbers the right — banked
// XP over the next level's cost; at the cap the bar holds full (a
// soft glow) and the numbers read the banked total (the xp keeps
// counting — the seam for the post-cap currency, when the skills
// land). Each sits on a soft dark chip (sized to the text, redrawn
// when it changes) so the gold reads over the gold fill as it does
// over the track. The fill wears the orbs' glass idiom (sheen + the
// meniscus line at its edge).
const xpGold = toColor(c.energy, 0xffd84d);
const xpCss = toCss(c.energy, '#ffd84d');
const _cap = new XpManager(); // data-driven — the level-1 cost
const barX = firstSlotL; // the seats' left edge — the bar spans their row
const barW = SW; // the number buttons' full length
const barH = 18; // tall enough for the embedded summary text
const barY = xpRowCY - barH / 2;
const xpTrack = scene.add.graphics();
xpTrack.fillStyle(0x0a121d, 0.9); // the track (a step darker than the panel)
xpTrack.fillRoundedRect(barX, barY, barW, barH, barH / 2);
xpTrack.lineStyle(1, xpGold, 0.5); // the gold keyline
xpTrack.strokeRoundedRect(barX + 0.5, barY + 0.5, barW - 1, barH - 1, (barH - 1) / 2);
xpTrack.setScrollFactor(0).setDepth(DEPTH).setVisible(false); // hidden until a snapshot carries xp
this.add(xpTrack);
const xpFill = scene.add.graphics().setScrollFactor(0).setDepth(DEPTH).setVisible(false);
this.add(xpFill);
const xpLabelChip = scene.add.graphics()
.setScrollFactor(0).setDepth(DEPTH).setVisible(false);
xpLabelChip.setPosition(barX + 10, xpRowCY);
this.add(xpLabelChip);
const xpLabel = scene.add
.text(barX + 10, xpRowCY, 'LV 0', {
fontFamily: fontStack('body', FONT_FALLBACK),
fontSize: '13px',
fontStyle: 'bold',
color: xpCss,
stroke: '#02131c',
strokeThickness: 2.5,
})
.setOrigin(0, 0.5)
.setScrollFactor(0)
.setDepth(DEPTH)
.setVisible(false);
this.add(xpLabel);
this._refreshXpChip(xpLabelChip, xpLabel);
const startNums = `0 / ${fmtXp(_cap.xpForLevel(1))} XP`;
const xpNumbersChip = scene.add.graphics()
.setScrollFactor(0).setDepth(DEPTH).setVisible(false);
xpNumbersChip.setPosition(barX + barW - 10, xpRowCY);
this.add(xpNumbersChip);
const xpNumbers = scene.add
.text(barX + barW - 10, xpRowCY, startNums, {
fontFamily: fontStack('body', FONT_FALLBACK),
fontSize: '11px',
color: xpCss,
stroke: '#02131c',
strokeThickness: 2,
})
.setOrigin(1, 0.5)
.setScrollFactor(0)
.setDepth(DEPTH)
.setVisible(false);
this.add(xpNumbers);
this._refreshXpChip(xpNumbersChip, xpNumbers);
this.xpBar = { x: barX, y: barY, w: barW, h: barH, color: xpGold, fill: xpFill, frac: -1 };
this.xpLabel = xpLabel;
this.xpLabelChip = xpLabelChip;
this.xpNumbers = xpNumbers;
this.xpNumbersChip = xpNumbersChip;
this.xpTrack = xpTrack;
this._xpLabel = 'LV 0';
this._xpNumbers = startNums;
this._xpShown = false;
this._xpMax = false;
// The bar's footprint (contains()'s click gate) — the orbs overhang
// the panel, so their bounds dominate (the XP row below is
// display-only — no clicks to gate).
const topY = Math.min(hull.cy - hull.r, shield.cy - shield.r);
const botY = Math.max(hull.cy + hull.r, shield.cy + shield.r);
this.rect = {
x: hull.cx - hull.r,
y: topY,
w: eCx + R - (hull.cx - hull.r),
h: botY - topY,
};
}
/**
* Feed the orbs. Accepts a CombatSystem (uses its stats()) or a plain
* snapshot { hull, maxHull, shields, maxShields, energy, maxEnergy }.
* Each orb's liquid drains as its value drops (Diablo-style) — redrawn
* only when the fraction actually changes.
*/
setStats(src) {
if (!src) return;
const s = typeof src.stats === 'function' ? src.stats() : src;
const frac = (v, m) => (m > 0 ? Math.max(0, Math.min(1, v / m)) : 0);
this._setOrb(this.orbHull, frac(s.hull, s.maxHull));
this._setOrb(this.orbShield, frac(s.shields, s.maxShields));
this._setOrb(this.orbEnergy, frac(s.energy, s.maxEnergy));
// The XP bar (the level + the bank — a snapshot without xp state
// hides it, so a bar fed a bare armor/energy snapshot — the range —
// keeps its old face).
if (this.xpLabel) {
const row = [this.xpLabel, this.xpLabelChip, this.xpNumbers, this.xpNumbersChip, this.xpTrack, this.xpBar?.fill];
if (s.xp) {
if (!this._xpShown) {
this._xpShown = true;
for (const o of row) o?.setVisible(true);
}
const { level, xp, xpForNext, isMax } = s.xp;
const label = isMax ? `LV ${level} MAX` : `LV ${level}`;
if (label !== this._xpLabel) {
this._xpLabel = label;
this.xpLabel.setText(label);
this._refreshXpChip(this.xpLabelChip, this.xpLabel);
}
const numbers = isMax
? `XP ${fmtXp(xp)} banked`
: `${fmtXp(xp)} / ${fmtXp(xpForNext)} XP`;
if (numbers !== this._xpNumbers) {
this._xpNumbers = numbers;
this.xpNumbers.setText(numbers);
this._refreshXpChip(this.xpNumbersChip, this.xpNumbers);
}
this._drawXpBar(isMax ? 1 : (s.xp.progress ?? 0), isMax);
} else if (this._xpShown) {
this._xpShown = false;
for (const o of row) o?.setVisible(false);
}
}
}
/**
* The embedded summary's dark chip — sized to the text it sits under,
* centered on the text's position (the text's own anchor is
* irrelevant — the chip is symmetric).
*/
_refreshXpChip(chip, t) {
if (!chip || !t) return;
chip.clear();
chip.fillStyle(0x02131c, 0.55);
chip.fillRoundedRect(-t.width / 2 - 5, -t.height / 2 - 2.5, t.width + 10, t.height + 5, 4);
}
/**
* The XP bar's fill (epsilon-guarded, like the orbs' liquids): the
* gold column, the glass sheen, the meniscus line at the fill's edge —
* and at the cap a soft outer glow (the bar is full and holding).
*/
_drawXpBar(f, isMax) {
const b = this.xpBar;
if (!b) return;
f = Math.max(0, Math.min(1, Number(f) || 0));
if (Math.abs(f - b.frac) < 0.002 && isMax === this._xpMax) return;
b.frac = f;
this._xpMax = isMax;
const g = b.fill;
g.clear();
if (f <= 0.004) return;
const x = b.x + 1.5;
const y = b.y + 1.5;
const h = b.h - 3;
const fw = Math.max(h, (b.w - 3) * f);
g.fillStyle(b.color, 0.95);
g.fillRoundedRect(x, y, fw, h, h / 2);
g.fillStyle(0xffffff, 0.2); // the sheen (the orbs' glass, top stripe)
g.fillRoundedRect(x + 2, y + 1, Math.max(1, fw - 4), Math.max(1.2, h * 0.3), 1.2);
if (f < 0.999) {
g.fillStyle(0xffffff, 0.55); // the meniscus — the fill's leading edge
g.fillRoundedRect(x + fw - 2.5, y, 2.5, h, 1.25);
}
if (isMax) {
g.fillStyle(b.color, 0.22); // the cap: a soft glow around the full bar
g.fillRoundedRect(x - 1.5, y - 1.5, fw + 3, h + 3, (h + 3) / 2);
}
}
_setOrb(orb, f) {
if (!orb) return;
if (Math.abs(f - orb.frac) < 0.0004) return;
orb.frac = f;
this._drawLiquid(orb);
}
/**
* The liquid column: a bottom-anchored fill clipped to the circle (this
* Phaser build has no circle mask — the fill is drawn as thin horizontal
* strips, each clipped to the chord width at that height), with a
* brighter surface line where the liquid meets the air.
*/
_drawLiquid(orb) {
const g = orb.liquid;
g.clear();
const f = Math.max(0, Math.min(1, orb.frac));
if (f <= 0.002) return;
const { cx, cy, r } = orb;
const top = cy + r - 2 * r * f; // the surface (f=1 → the top of the orb)
const bottom = cy + r;
const step = 2;
g.fillStyle(orb.color, orb.fillAlpha);
for (let y = top; y < bottom; y += step) {
const yMid = Math.min(bottom - 0.5, y + step * 0.5);
const dy = yMid - cy;
const hw = Math.sqrt(Math.max(0, r * r - dy * dy));
if (hw <= 0.25) continue;
g.fillRect(cx - hw, y, hw * 2, Math.min(step, bottom - y) + 0.6);
}
if (f < 0.998) {
const hw = Math.sqrt(Math.max(0, r * r - (top - cy) ** 2));
g.lineStyle(2, mixWhite(orb.color, 0.55), 0.7); // the meniscus
g.lineBetween(cx - hw, top, cx + hw, top);
}
}
/** Is (px, py) inside the bar's footprint? (GameScene's click gate.) */
contains(px, py) {
const m = 6; // a little slop, like the deck's
const r = this.rect;
return (
px >= r.x - m &&
px <= r.x + r.w + m &&
py >= r.y - m &&
py <= r.y + r.h + m
);
}
/**
* The ORB shape (for the readability layer's hit-flash —
* js/combat/CombatHud.js blooms the orb that took the hit):
* 'hull', 'shield' or 'energy' → { type:'circle', cx, cy, r }.
*/
gaugeShape(kind) {
const o =
kind === 'hull' ? this.orbHull
: kind === 'shield' ? this.orbShield
: kind === 'energy' ? this.orbEnergy
: null;
return o ? { type: 'circle', cx: o.cx, cy: o.cy, r: o.r } : null;
}
/**
* The orb's TRACK rect (the old contract — CombatHud's rect fallback):
* the bounding square of 'hull'/'shield'/'energy'.
*/
gaugeRect(kind) {
const s = this.gaugeShape(kind);
if (!s) return null;
return { x: s.cx - s.r, y: s.cy - s.r, w: s.r * 2, h: s.r * 2 };
}
/** Tear down (scene destroy) — the children go with the scene anyway. */
destroy() {
this.onCast = null;
this.xpLabel = null;
this.xpLabelChip = null;
this.xpNumbers = null;
this.xpNumbersChip = null;
this.xpTrack = null;
this.xpBar = null;
super.destroy();
}
}