Skip to content

🤖 Cannot save fiddle #2416

@Hudson6688

Description

@Hudson6688

Error code

ERRW:0.81:K1.0:TTH0.0:AS

Were you logged in?

Yes

Your username (if logged in)

DONUTPLANET

Your HTML

-

Your JavaScript

if (!globalThis.GALACTIC_CLICKER_V3) globalThis.GALACTIC_CLICKER_V3 = {};
var G = globalThis.GALACTIC_CLICKER_V3;

G.VERSION = "GalacticClicker-V3.0";
G.SAVE_SLOT = 9;
G.ADMINS = ["TheGalaxyz","MountainAli"];
G.OFFLINE_RATE = 0.05;
G.OFFLINE_CAP_MS = 259200000;
G.SHOP_CD_MS = 450;
G.CLICK_CD_MS = 75;
G.QTE_CD_MS = 10000;

G.POS = {
    spawn: [0.5, 20, 12.5],
    shop: [0.5, 21, 8.5],
    clickBuy: [-8.5, 21, 3.5],
    autoBuy: [-8.5, 21, -1.5],
    multiBuy: [8.5, 21, -1.5],
    luckBuy: [8.5, 21, 3.5],
    casino: [4.5, 21, 8.5],
    qte: [-4.5, 21, 8.5],
    prestige: [0.5, 21, -8.5]
};

G.CORE_POS = [
    [-4.5, 21, 1.5],
    [-1.5, 21, 1.5],
    [1.5, 21, 1.5],
    [4.5, 21, 1.5],
    [-4.5, 21, -2.5],
    [-1.5, 21, -2.5],
    [1.5, 21, -2.5],
    [4.5, 21, -2.5],
    [-1.5, 21, -5.5],
    [1.5, 21, -5.5]
];

G.CORE_TIERS = [
    { name: "Galactic Cookie", block: "Block of Diamond", cost: 0, add: 0, q: 1, color: "#66ffff" },
    { name: "Solar Biscuit", block: "Block of Gold", cost: 25000, add: 4, q: 3, color: "#ffd84a" },
    { name: "Emerald Nebula", block: "Block of Emerald", cost: 250000, add: 14, q: 8, color: "#55ff99" },
    { name: "Lapis Moon", block: "Block of Lapis Lazuli", cost: 2500000, add: 42, q: 21, color: "#7799ff" },
    { name: "Quartz Planet", block: "Block of Quartz", cost: 25000000, add: 120, q: 60, color: "#eeeeff" },
    { name: "Beacon Star", block: "Beacon", cost: 250000000, add: 340, q: 170, color: "#ff66ff" },
    { name: "Trophy Sun", block: "Gold Trophy", cost: 2500000000, add: 950, q: 480, color: "#ffffff" },
    { name: "Lucky Galaxy", block: "Ultra Lucky Block", cost: 25000000000, add: 2700, q: 1400, color: "#ff99ff" },
    { name: "Rainbow Quasar", block: "Rainbow Wool", cost: 250000000000, add: 7800, q: 4200, color: "#aaffff" },
    { name: "Void Singularity", block: "Black Concrete", cost: 2500000000000, add: 23000, q: 13000, color: "#cc66ff" }
];

G.AUTO_TIERS = [
    { name: "Cursor Drone", block: "Golden Decoration", mult: 1, cost: 0 },
    { name: "Copper Swarm", block: "Block of Iron", mult: 2.2, cost: 50000 },
    { name: "Moon Factory", block: "Block of Quartz", mult: 5.4, cost: 500000 },
    { name: "Portal Oven", block: "Purple Glass", mult: 13, cost: 5000000 },
    { name: "Beacon Reactor", block: "Beacon", mult: 32, cost: 50000000 },
    { name: "Radar Array", block: "Radar", mult: 80, cost: 500000000 },
    { name: "Active Radar AI", block: "Active Radar", mult: 210, cost: 5000000000 },
    { name: "Trophy Dyson", block: "Gold Trophy", mult: 560, cost: 50000000000 }
];

G.QTE_TYPES = {
    progress: { name: "Progress Bar", item: "Gold Coin", mult: 30, color: "#ffd84a", w: 34 },
    timed: { name: "Timed Click", item: "Arrow of Speed", mult: 55, color: "#aaffff", w: 25 },
    precision: { name: "Precision Bar", item: "Block of Emerald", mult: 90, color: "#55ff99", w: 20 },
    rhythm: { name: "Rhythm Click", item: "Block of Lapis Lazuli", mult: 140, color: "#8899ff", w: 14 },
    gravity: { name: "Gravity Bar", item: "Moonstone", mult: 230, color: "#ff66ff", w: 7 }
};

G.LOOT = [
    { key: "coin", name: "Solar Voucher", item: "Gold Coin", color: "#ffd84a", weight: 32 },
    { key: "click", name: "Quantum Tap", item: "Moonstone Fragment", color: "#66ffff", weight: 18 },
    { key: "auto", name: "Nebula Battery", item: "Diamond", color: "#55aaff", weight: 17 },
    { key: "multi", name: "Multiplier Prism", item: "Block of Emerald", color: "#55ff99", weight: 14 },
    { key: "luck", name: "Luck Core", item: "Lucky Block", color: "#55ff99", weight: 12 },
    { key: "nova", name: "Nova Bomb", item: "Fireball Block", color: "#ff5533", weight: 9 },
    { key: "qte", name: "Rift Ticket", item: "Block of Lapis Lazuli", color: "#8899ff", weight: 8 },
    { key: "offline", name: "Offline Comet", item: "Compass", color: "#aaffff", weight: 7 },
    { key: "refund", name: "Cosmic Refund", item: "Gold Bar", color: "#ffd84a", weight: 6 },
    { key: "star", name: "Star Shard", item: "Moonstone", color: "#ff66ff", weight: 5 },
    { key: "drone", name: "Drone Blueprint", item: "Golden Decoration", color: "#aaffaa", weight: 4 },
    { key: "core", name: "Core Spark", item: "Beacon", color: "#ffffff", weight: 3 },
    { key: "jackpot", name: "Jackpot Charm", item: "Gold Trophy", color: "#ffffff", weight: 2 },
    { key: "crown", name: "Astral Crown", item: "Block of Gold", color: "#ffffff", weight: 1 }
];

G.TIPS = [
    { icon: "Block of Diamond", text: "Click unlocked core meshes. Higher cores are stronger but must be unlocked.", color: "#66ffff" },
    { icon: "Block of Gold", text: "Click upgrade meshes to buy 1. Crouch-click an upgrade mesh to open shop.", color: "#ffd84a" },
    { icon: "Golden Decoration", text: "Auto drones earn online and 5% offline when you return.", color: "#aaffaa" },
    { icon: "Block of Emerald", text: "Multiplier gives global income, QTE, casino, and loot-value scaling.", color: "#55ff99" },
    { icon: "Block of Lapis Lazuli", text: "QTE mesh now randomly starts one of 5 QTE types.", color: "#8899ff" },
    { icon: "Gold Trophy", text: "Ascension requires run coins since last rebirth, so it cannot be spammed.", color: "#ff66ff" }
];

G.ACH = [
    { key: "c50", name: "Warm Engine", reward: 250, need: function (s) { return s.lifetimeClicks >= 50; } },
    { key: "c500", name: "Click Pilot", reward: 2500, need: function (s) { return s.lifetimeClicks >= 500; } },
    { key: "c5000", name: "Finger Nova", reward: 35000, need: function (s) { return s.lifetimeClicks >= 5000; } },
    { key: "r100k", name: "First Orbit", reward: 5000, need: function (s) { return s.runCoins >= 100000; } },
    { key: "r10m", name: "Million Run", reward: 100000, need: function (s) { return s.runCoins >= 10000000; } },
    { key: "a50", name: "Drone Cloud", reward: 25000, need: function (s) { return s.autoLevel >= 50; } },
    { key: "t5", name: "Beacon Age", reward: 250000, need: function (s) { return s.clickTier >= 5; } },
    { key: "p1", name: "Ascended", reward: 500000, need: function (s) { return s.prestige >= 1; } }
];

G.players = G.players || {};
G.meshes = G.meshes || {};
G.meshAction = G.meshAction || {};
G.meshByKey = G.meshByKey || {};
G.qtes = G.qtes || {};
G.spin = G.spin || {};
G.clickCd = G.clickCd || {};
G.useCd = G.useCd || {};
G.shopCd = G.shopCd || {};
G.qteCd = G.qteCd || {};
G.timers = G.timers || { auto: 0, ui: 0, save: 0, tip: 0, pulse: 0 };
G.tipIndex = G.tipIndex || 0;
G.started = G.started || false;

G.now = function () {
    try { return api.now(); } catch (e) { return Date.now(); }
};

G.isAdmin = function (p) {
    try { return G.ADMINS.indexOf(api.getEntityName(p)) !== -1; } catch (e) { return false; }
};

G.floor = function (n) {
    n = Number(n) || 0;
    if (n > 9000000000000000) n = 9000000000000000;
    return Math.floor(n);
};

G.fmt = function (n) {
    n = Math.floor(Number(n) || 0);
    var u = ["", "K", "M", "B", "T", "Qa", "Qi", "Sx", "Sp"];
    var i = 0;
    while (n >= 1000 && i < u.length - 1) {
        n = n / 1000;
        i++;
    }
    if (i === 0) return String(Math.floor(n));
    return n.toFixed(n >= 100 ? 0 : n >= 10 ? 1 : 2) + u[i];
};

G.defaultStats = function () {
    return {
        coins: 0,
        runCoins: 0,
        totalCoins: 0,
        lifetimeClicks: 0,
        clickLevel: 0,
        autoLevel: 0,
        multiLevel: 0,
        luckLevel: 0,
        clickTier: 0,
        autoTier: 0,
        prestige: 0,
        shards: 0,
        lootFound: 0,
        casinoWins: 0,
        qteWins: 0,
        qteFails: 0,
        playMs: 0,
        lastSeen: G.now(),
        boosts: {},
        ach: {},
        touch: false,
        clearUpperAt: 0,
        offlineAward: 0,
        dirty: true,
        uiDirty: true
    };
};

G.cleanStats = function (s) {
    var d = G.defaultStats();
    if (!s) return d;
    for (var k in d) if (s[k] === undefined || s[k] === null) s[k] = d[k];
    if (!s.boosts) s.boosts = {};
    if (!s.ach) s.ach = {};
    if (s.runCoins === undefined || s.runCoins === null) s.runCoins = Math.max(0, Number(s.coins) || 0);
    if (s.clickTier >= G.CORE_TIERS.length) s.clickTier = G.CORE_TIERS.length - 1;
    if (s.autoTier >= G.AUTO_TIERS.length) s.autoTier = G.AUTO_TIERS.length - 1;
    return s;
};

G.cleanBoosts = function (s) {
    var now = G.now();
    var b = s.boosts || {};
    for (var k in b) if (b[k] && b[k].until && b[k].until <= now) delete b[k];
};

G.boostMul = function (s, key) {
    G.cleanBoosts(s);
    var b = s.boosts || {};
    if (!b[key] || !b[key].mul) return 1;
    return Number(b[key].mul) || 1;
};

G.boostAdd = function (s, key) {
    G.cleanBoosts(s);
    var b = s.boosts || {};
    if (!b[key] || !b[key].add) return 0;
    return Number(b[key].add) || 0;
};

G.globalMul = function (s) {
    var m = 1;
    m *= 1 + s.multiLevel * 0.035;
    m *= 1 + s.prestige * 0.12 + s.shards * 0.02;
    m *= G.boostMul(s, "multi");
    m *= G.boostMul(s, "all");
    return m;
};

G.tierPower = function (s, tierI) {
    if (tierI < 0) tierI = 0;
    if (tierI > s.clickTier) tierI = s.clickTier;
    var t = G.CORE_TIERS[tierI];
    var base = 1 + s.clickLevel * 0.35 + t.add;
    base *= 1 + tierI * 0.15;
    base *= G.globalMul(s);
    base *= G.boostMul(s, "click");
    return Math.max(1, G.floor(base));
};

G.clickPower = function (s) {
    return G.tierPower(s, s.clickTier);
};

G.cps = function (s) {
    var t = G.AUTO_TIERS[Math.min(s.autoTier, G.AUTO_TIERS.length - 1)];
    var levelPower = Math.pow(Math.max(0, s.autoLevel), 1.08);
    var base = levelPower * t.mult * 0.65;
    base *= 1 + s.clickTier * 0.05;
    base *= G.globalMul(s);
    base *= G.boostMul(s, "auto");
    return Math.max(0, Math.floor(base * 10) / 10);
};

G.clickCost = function (s) {
    return G.floor(18 * Math.pow(1.09, s.clickLevel));
};

G.autoCost = function (s) {
    return G.floor(120 * Math.pow(1.115, s.autoLevel));
};

G.multiCost = function (s) {
    return G.floor(220 * Math.pow(1.12, s.multiLevel));
};

G.luckCost = function (s) {
    return G.floor(280 * Math.pow(1.13, s.luckLevel));
};

G.coreTierCost = function (s) {
    var n = G.CORE_TIERS[s.clickTier + 1];
    return n ? n.cost : 0;
};

G.autoTierCost = function (s) {
    var n = G.AUTO_TIERS[s.autoTier + 1];
    return n ? n.cost : 0;
};

G.prestigeReq = function (s) {
    return G.floor(10000000 * Math.pow(4.2, s.prestige));
};

G.prestigeMinTier = function (s) {
    return Math.min(G.CORE_TIERS.length - 1, 4 + Math.floor(s.prestige / 2));
};

G.prestigeGain = function (s) {
    var req = G.prestigeReq(s);
    if (s.runCoins < req) return 0;
    if (s.clickTier < G.prestigeMinTier(s)) return 0;
    return Math.max(1, Math.floor(Math.pow(s.runCoins / req, 0.42)));
};

G.load = function (p) {
    var raw = null;
    try {
        var it = api.getMoonstoneChestItemSlot(p, G.SAVE_SLOT);
        var a = it && it.attributes && it.attributes.customAttributes;
        raw = a && (a.gcSaveV3 || a.gcSaveV2 || a.gcSave);
    } catch (e) {}
    var s = G.cleanStats(raw);
    G.cleanBoosts(s);
    var now = G.now();
    var offlineMs = Math.max(0, now - (Number(s.lastSeen) || now));
    if (offlineMs > G.OFFLINE_CAP_MS) offlineMs = G.OFFLINE_CAP_MS;
    var off = G.floor(G.cps(s) * (offlineMs / 1000) * G.OFFLINE_RATE);
    if (off > 0) {
        s.coins = G.floor(s.coins + off);
        s.runCoins = G.floor(s.runCoins + off);
        s.totalCoins = G.floor(s.totalCoins + off);
        s.offlineAward = off;
    }
    s.lastSeen = now;
    s.dirty = true;
    s.uiDirty = true;
    G.players[p] = s;
    return s;
};

G.save = function (p) {
    var s = G.players[p];
    if (!s) return;
    try {
        s.lastSeen = G.now();
        api.setMoonstoneChestItemSlot(p, G.SAVE_SLOT, "Block of Diamond", 1, {
            customDisplayName: "Galactic Clicker V3 Save",
            customDescription: "Stores Galactic Clicker V3 progress.",
            customAttributes: { gcSaveV3: s, version: G.VERSION }
        });
        s.dirty = false;
    } catch (e) {}
};

G.saveAll = function () {
    var ids = [];
    try { ids = api.getPlayerIds(); } catch (e) {}
    for (var i = 0; i < ids.length; i++) G.save(ids[i]);
};

G.isMobile = function (p) {
    try { return !!api.isMobile(p); } catch (e) {}
    var s = G.players[p];
    return !!(s && s.touch);
};

G.isCrouch = function (p) {
    try { return !!api.isPlayerCrouching(p); } catch (e) { return false; }
};

G.px = function (p, n) {
    var f = G.isMobile(p) ? 0.25 : 1;
    var v = Math.max(7, Math.floor(n * f));
    return v + "px";
};

G.i = function (p, icon, size) {
    return { icon: icon, style: { fontSize: G.px(p, size || 24) } };
};

G.t = function (p, str, color, size, bold) {
    return { str: str, style: { color: color || "#ffffff", fontSize: G.px(p, size || 18), fontWeight: bold ? "bold" : "normal" } };
};

G.say = function (p, msg, color) {
    try { api.sendMessage(p, msg, { color: color || "white" }); } catch (e) {}
};

G.sound = function (p, snd, vol, rate) {
    try { api.playSound(p, snd, vol || 0.65, rate || 1, { playerIdOrPos: p, maxHearDist: 20 }); } catch (e) {}
};

G.upper = function (p, arr, ms) {
    try { api.setClientOption(p, "middleTextUpper", arr); } catch (e) {}
    var s = G.players[p];
    if (s && ms) s.clearUpperAt = G.now() + ms;
};

G.lower = function (p, arr) {
    try { api.setClientOption(p, "middleTextLower", arr); } catch (e) {}
};

G.addCoins = function (p, amt, why) {
    var s = G.players[p] || G.load(p);
    amt = G.floor(amt);
    if (amt <= 0) return 0;
    s.coins = G.floor(s.coins + amt);
    s.runCoins = G.floor(s.runCoins + amt);
    s.totalCoins = G.floor(s.totalCoins + amt);
    s.dirty = true;
    s.uiDirty = true;
    if (why) {
        try { api.sendFlyingMiddleMessage(p, ["+" + G.fmt(amt) + " " + why], 80, 900); } catch (e) {}
    }
    return amt;
};

G.pay = function (p, cost) {
    var s = G.players[p] || G.load(p);
    cost = G.floor(cost);
    if (s.coins < cost) {
        G.say(p, "Need " + G.fmt(cost - s.coins) + " more coins.", "#ff7777");
        try { api.sendOverShopInfo(p, "Need " + G.fmt(cost - s.coins) + " more coins."); } catch (e) {}
        return false;
    }
    s.coins = G.floor(s.coins - cost);
    s.dirty = true;
    s.uiDirty = true;
    return true;
};

G.fxAt = function (pos, texture, a, b, count) {
    try {
        api.playParticleEffect({
            dir1: [-1, 0, -1],
            dir2: [1, 2, 1],
            pos1: [pos[0] - 0.7, pos[1], pos[2] - 0.7],
            pos2: [pos[0] + 0.7, pos[1] + 1.6, pos[2] + 0.7],
            texture: texture || "glint",
            minLifeTime: 0.25,
            maxLifeTime: 0.85,
            minEmitPower: 1,
            maxEmitPower: 3,
            minSize: 0.15,
            maxSize: 0.45,
            manualEmitCount: count || 25,
            gravity: [0, -2, 0],
            colorGradients: [{ timeFraction: 0, minColor: a || [80, 220, 255, 1], maxColor: b || [255, 80, 255, 1] }],
            velocityGradients: [{ timeFraction: 0, factor: 1, factor2: 1 }],
            blendMode: 1
        });
    } catch (e) {}
};

G.fxPlayer = function (p, texture, a, b, count) {
    try {
        var pos = api.getPosition(p);
        G.fxAt([pos[0], pos[1] + 1, pos[2]], texture, a, b, count);
    } catch (e) {}
};

G.effect = function (p, name, duration, icon, display) {
    try { api.applyEffect(p, name, duration, { icon: icon, displayName: display || name }); } catch (e) {}
};

G.rankName = function (s) {
    if (s.prestige >= 12) return "Singularity God";
    if (s.prestige >= 8) return "Void Emperor";
    if (s.prestige >= 4) return "Galaxy Baron";
    if (s.clickTier >= 7) return "Quasar Tycoon";
    if (s.totalCoins >= 1000000000) return "Star Tycoon";
    if (s.totalCoins >= 10000000) return "Nebula Banker";
    if (s.totalCoins >= 100000) return "Orbit Manager";
    return "Space Intern";
};

G.rightInfo = function (p) {
    var s = G.players[p] || G.load(p);
    var ct = G.CORE_TIERS[s.clickTier];
    var at = G.AUTO_TIERS[s.autoTier];
    var req = G.prestigeReq(s);
    var minT = G.prestigeMinTier(s) + 1;
    var multPct = Math.floor((G.globalMul(s) - 1) * 100);
    return [
        G.i(p, "Block of Diamond", 20),
        G.t(p, " Galactic Clicker V3\n", "#66ffff", 18, true),
        G.i(p, "Gold Coin", 17),
        G.t(p, " Coins: " + G.fmt(s.coins) + "\n", "#ffd84a", 15, false),
        G.i(p, "Damage", 16),
        G.t(p, " Click: +" + G.fmt(G.clickPower(s)) + "\n", "#ffffff", 15, false),
        G.i(p, "Haste", 16),
        G.t(p, " Auto: " + G.fmt(G.cps(s)) + "/s\n", "#aaffaa", 15, false),
        G.i(p, "Block of Emerald", 16),
        G.t(p, " Multiplier: +" + multPct + "% all\n", "#55ff99", 15, false),
        G.i(p, ct.block, 16),
        G.t(p, " Core: T" + (s.clickTier + 1) + " " + ct.name + "\n", ct.color, 15, false),
        G.i(p, at.block, 16),
        G.t(p, " Drone Tier: " + at.name + "\n", "#ffcc66", 15, false),
        G.i(p, "Lucky Block", 16),
        G.t(p, " Luck: " + s.luckLevel + "\n", "#55ff99", 15, false),
        G.i(p, "Moonstone", 16),
        G.t(p, " Shards: " + s.shards + " | Asc: " + s.prestige + "\n", "#ff66ff", 15, false),
        G.i(p, "Gold Trophy", 16),
        G.t(p, " Run: " + G.fmt(s.runCoins) + "/" + G.fmt(req) + " T" + minT + "\n", "#ffffff", 15, false),
        G.t(p, "\nClick upgrade mesh = buy 1\nCrouch-click upgrade mesh = shop", "#aaaaff", 13, false)
    ];
};

G.refreshUi = function (p) {
    var s = G.players[p];
    if (!s) return;
    try { api.setClientOption(p, "RightInfoText", G.rightInfo(p)); } catch (e) {}
    try {
        api.setTargetedPlayerSettingForEveryone(p, "lobbyLeaderboardValues", {
            name: api.getEntityName(p),
            rank: G.rankName(s),
            coins: G.fmt(s.coins),
            run: G.fmt(s.runCoins),
            asc: s.prestige
        }, true);
        api.setTargetedPlayerSettingForEveryone(p, "colorInLobbyLeaderboard", s.prestige > 0 ? "#ff66ff" : "#66ffff", true);
    } catch (e2) {}
    s.uiDirty = false;
};

G.showTip = function (p) {
    var tip = G.TIPS[G.tipIndex % G.TIPS.length];
    G.lower(p, [
        G.i(p, tip.icon, 26),
        G.t(p, " " + tip.text, tip.color, 22, true)
    ]);
};

G.showWelcome = function (p) {
    var s = G.players[p] || G.load(p);
    var line = G.isMobile(p) ? "Mobile: tap meshes. Use /shop for bulk upgrades." : "PC: click upgrade meshes for +1, crouch-click for shop.";
    G.upper(p, [
        G.i(p, "Block of Diamond", 42),
        G.t(p, " Galactic Clicker V3\n", "#66ffff", 36, true),
        G.i(p, "Gold Coin", 28),
        G.t(p, " Long-run economy, random QTEs, casino events, loot, offline profit, and real ascension gates.\n", "#ffffff", 22, false),
        G.t(p, line, "#ffd84a", 20, true)
    ], 9000);
    G.showTip(p);
    if (s.offlineAward > 0) {
        G.say(p, "Offline profit: +" + G.fmt(s.offlineAward) + " coins at 5% auto rate.", "#aaffaa");
        s.offlineAward = 0;
    }
};

G.applyLook = function (p) {
    try {
        api.setClientOptions(p, {
            skyBox: "interstellar",
            cameraTint: [0.08, 0.03, 0.14, 0.08],
            fogColourOverride: "#080014",
            fogChunkDistanceOverride: 18,
            canChange: G.isAdmin(p),
            canCraft: G.isAdmin(p),
            creative: G.isAdmin(p),
            canPickUpItems: true,
            autoRespawn: true,
            secsToRespawn: 0,
            compassTarget: G.CORE_POS[0],
            crosshairText: "Click cores. Crouch-click upgrades for shop.",
            touchscreenActionButton: [{ str: "Action", style: { color: "#66ffff", fontWeight: "bold" } }]
        });
    } catch (e) {}
};

G.spawnOne = function (key, block, pos, name, size) {
    var id = null;
    try { id = api.attemptCreateMeshEntity("BloxdBlock", { blockName: block, size: size || 1.2, autoRotate: true, hideDist: 95 }, name); } catch (e) { id = null; }
    if (id) {
        try { api.setPosition(id, pos[0], pos[1], pos[2]); } catch (e2) {}
        G.meshes[id] = 1;
        G.meshAction[id] = key;
        G.meshByKey[key] = id;
    }
    return id;
};

G.deleteMeshes = function () {
    for (var id in G.meshes) {
        try { api.deleteMeshEntity(id); } catch (e) {}
    }
    G.meshes = {};
    G.meshAction = {};
    G.meshByKey = {};
};

G.spawnMeshes = function () {
    G.deleteMeshes();
    for (var i = 0; i < G.CORE_TIERS.length; i++) G.spawnOne("core" + i, G.CORE_TIERS[i].block, G.CORE_POS[i], "T" + (i + 1) + " " + G.CORE_TIERS[i].name, 1.55 + i * 0.07);
    G.spawnOne("shop", "Bookshelf", G.POS.shop, "GUIDE SHOP", 1.25);
    G.spawnOne("buyClick", "Block of Gold", G.POS.clickBuy, "CLICK POWER", 1.25);
    G.spawnOne("buyAuto", "Golden Decoration", G.POS.autoBuy, "AUTO DRONES", 1.25);
    G.spawnOne("buyMulti", "Block of Emerald", G.POS.multiBuy, "MULTIPLIER", 1.25);
    G.spawnOne("buyLuck", "Lucky Block", G.POS.luckBuy, "LUCK LAB", 1.25);
    G.spawnOne("casino", "Ultra Lucky Block", G.POS.casino, "COSMIC CASINO", 1.35);
    G.spawnOne("qte", "Block of Lapis Lazuli", G.POS.qte, "RANDOM QTE RIFT", 1.25);
    G.spawnOne("prestige", "Gold Trophy", G.POS.prestige, "ASCENSION", 1.35);
};

G.dist2 = function (a, b) {
    var x = a[0] - b[0];
    var y = a[1] - b[1];
    var z = a[2] - b[2];
    return x * x + y * y + z * z;
};

G.nearAction = function (p) {
    var pos = null;
    try { pos = api.getPosition(p); } catch (e) {}
    if (!pos) return null;
    var best = null;
    var bestD = 999999;
    for (var i = 0; i < G.CORE_POS.length; i++) {
        var d = G.dist2(pos, G.CORE_POS[i]);
        if (d <= 16 && d < bestD) {
            best = "core" + i;
            bestD = d;
        }
    }
    var list = [
        ["shop", G.POS.shop, 13],
        ["buyClick", G.POS.clickBuy, 12],
        ["buyAuto", G.POS.autoBuy, 12],
        ["buyMulti", G.POS.multiBuy, 12],
        ["buyLuck", G.POS.luckBuy, 12],
        ["casino", G.POS.casino, 12],
        ["qte", G.POS.qte, 12],
        ["prestige", G.POS.prestige, 12]
    ];
    for (var j = 0; j < list.length; j++) {
        var d2 = G.dist2(pos, list[j][1]);
        if (d2 <= list[j][2] && d2 < bestD) {
            best = list[j][0];
            bestD = d2;
        }
    }
    return best;
};

G.coreClick = function (p, tierI) {
    var now = G.now();
    if (now < (G.clickCd[p] || 0)) return;
    G.clickCd[p] = now + G.CLICK_CD_MS;
    var s = G.players[p] || G.load(p);
    if (tierI > s.clickTier) {
        G.upper(p, [
            G.i(p, G.CORE_TIERS[tierI].block, 32),
            G.t(p, " Locked Core\n", "#ff7777", 28, true),
            G.t(p, "Unlock this core from Core Evolution. Next cost: " + G.fmt(G.coreTierCost(s)) + ".", "#ffffff", 20, false)
        ], 3000);
        G.sound(p, "doorClose", 0.5, 0.8);
        return;
    }
    var gain = G.tierPower(s, tierI);
    s.lifetimeClicks++;
    G.addCoins(p, gain, "coins");
    G.sound(p, "cashRegister", 0.32, 1.28 + tierI * 0.05);
    try { api.sendHitmarker(p, tierI >= 4, null); } catch (e) {}
    if (s.lifetimeClicks % 8 === 0) G.fxAt(G.CORE_POS[tierI], "glint", [80, 220, 255, 1], [255, 255, 80, 1], 18);
    G.tryLootDrop(p);
    G.checkAch(p);
};

G.buyClick = function (p, quiet) {
    var s = G.players[p] || G.load(p);
    var c = G.clickCost(s);
    if (!G.pay(p, c)) return false;
    s.clickLevel++;
    if (!quiet) G.say(p, "Click Power +" + 1 + ". Level " + s.clickLevel + ". Each level is small but cheap.", "#ffd84a");
    G.sound(p, "cashRegister", 0.65, 1.1);
    G.checkAch(p);
    return true;
};

G.buyAuto = function (p, quiet) {
    var s = G.players[p] || G.load(p);
    var c = G.autoCost(s);
    if (!G.pay(p, c)) return false;
    s.autoLevel++;
    if (!quiet) G.say(p, "Auto Drone +" + 1 + ". Level " + s.autoLevel + ".", "#aaffaa");
    G.sound(p, "cashRegister", 0.65, 1.05);
    G.checkAch(p);
    return true;
};

G.buyMulti = function (p, quiet) {
    var s = G.players[p] || G.load(p);
    var c = G.multiCost(s);
    if (!G.pay(p, c)) return false;
    s.multiLevel++;
    if (!quiet) G.say(p, "Multiplier +" + 1 + ". It boosts click, auto, QTE, casino, and loot value.", "#55ff99");
    G.sound(p, "cashRegister", 0.7, 1);
    return true;
};

G.buyLuck = function (p, quiet) {
    var s = G.players[p] || G.load(p);
    var c = G.luckCost(s);
    if (!G.pay(p, c)) return false;
    s.luckLevel++;
    if (!quiet) G.say(p, "Luck +" + 1 + ". Better loot chance, QTE type rolls, and casino outcomes.", "#55ff99");
    G.sound(p, "cashRegister", 0.7, 1.2);
    return true;
};

G.buyCoreTier = function (p) {
    var s = G.players[p] || G.load(p);
    if (s.clickTier >= G.CORE_TIERS.length - 1) {
        G.say(p, "All core tiers are unlocked.", "#ffcc66");
        return false;
    }
    var n = G.CORE_TIERS[s.clickTier + 1];
    if (!G.pay(p, n.cost)) return false;
    s.clickTier++;
    G.say(p, "Unlocked T" + (s.clickTier + 1) + " " + n.name + ". Click its visible core mesh now.", n.color);
    G.fxAt(G.CORE_POS[s.clickTier], "soul_0", [80, 220, 255, 1], [255, 80, 255, 1], 80);
    G.sound(p, "levelUp", 0.9, 1);
    G.syncShopPlayer(p);
    return true;
};

G.buyAutoTier = function (p) {
    var s = G.players[p] || G.load(p);
    if (s.autoTier >= G.AUTO_TIERS.length - 1) {
        G.say(p, "All drone tiers are unlocked.", "#ffcc66");
        return false;
    }
    var n = G.AUTO_TIERS[s.autoTier + 1];
    if (!G.pay(p, n.cost)) return false;
    s.autoTier++;
    G.say(p, "Drone tier upgraded to " + n.name + ".", "#aaffaa");
    G.fxPlayer(p, "glint", [120, 255, 120, 1], [255, 255, 120, 1], 45);
    G.sound(p, "levelUp", 0.85, 1.1);
    G.syncShopPlayer(p);
    return true;
};

G.buyBulk = function (p, kind, amt) {
    var done = 0;
    var limit = amt === "max" ? 1000 : amt;
    for (var i = 0; i < limit; i++) {
        var ok = false;
        if (kind === "click") ok = G.buyClick(p, true);
        else if (kind === "auto") ok = G.buyAuto(p, true);
        else if (kind === "multi") ok = G.buyMulti(p, true);
        else if (kind === "luck") ok = G.buyLuck(p, true);
        if (!ok) break;
        done++;
    }
    if (done > 0) {
        var s = G.players[p];
        s.dirty = true;
        s.uiDirty = true;
        G.say(p, "Bought " + done + " " + kind + " upgrades.", "#aaffaa");
        try { api.sendOverShopInfo(p, "Bought " + done + " " + kind + " upgrades."); } catch (e) {}
    }
    G.syncShopPlayer(p);
};

G.randomQteType = function (s) {
    var arr = ["progress", "timed", "precision", "rhythm", "gravity"];
    var total = 0;
    for (var i = 0; i < arr.length; i++) {
        var d = G.QTE_TYPES[arr[i]];
        var w = d.w + Math.min(20, s.luckLevel * 0.25 + s.clickTier * 0.6);
        if (arr[i] === "gravity") w += Math.min(10, s.luckLevel * 0.15 + s.prestige);
        total += w;
    }
    var r = Math.random() * total;
    var a = 0;
    for (var j = 0; j < arr.length; j++) {
        var d2 = G.QTE_TYPES[arr[j]];
        var w2 = d2.w + Math.min(20, s.luckLevel * 0.25 + s.clickTier * 0.6);
        if (arr[j] === "gravity") w2 += Math.min(10, s.luckLevel * 0.15 + s.prestige);
        a += w2;
        if (r <= a) return arr[j];
    }
    return "progress";
};

G.qteReward = function (s, type) {
    var q = G.QTE_TYPES[type] || G.QTE_TYPES.progress;
    var tier = G.CORE_TIERS[s.clickTier];
    var base = tier.q * q.mult;
    base *= 1 + s.multiLevel * 0.025 + s.luckLevel * 0.006 + s.prestige * 0.11 + s.shards * 0.018;
    base += G.clickPower(s) * (q.mult / 18);
    return Math.max(50, G.floor(base));
};

G.startRandomQte = function (p) {
    var s = G.players[p] || G.load(p);
    G.startQte(p, G.randomQteType(s));
};

G.startQte = function (p, type) {
    var now = G.now();
    if (now < (G.qteCd[p] || 0)) {
        G.say(p, "QTE cooldown: " + Math.ceil((G.qteCd[p] - now) / 1000) + "s.", "#ffcc66");
        return;
    }
    try {
        if (api.hasActiveQTE(p)) {
            G.say(p, "Finish your current QTE first.", "#ffcc66");
            return;
        }
    } catch (e) {}
    if (!G.QTE_TYPES[type]) type = "progress";
    var id = null;
    try {
        if (type === "progress") {
            id = api.addQTE(p, {
                type: "progressBar",
                parameters: {
                    progressStartValue: 28,
                    progressDecreasePerTick: 0.09,
                    progressPerClick: 5.5,
                    canFail: true,
                    description: [{ str: "Overcharge the Galactic Core!" }],
                    clickIcon: "fa-solid fa-computer-mouse",
                    scale: 1,
                    rotation: 15
                }
            });
        } else if (type === "timed") {
            id = api.addQTE(p, {
                type: "timedClick",
                parameters: {
                    timeWindow: 2100,
                    icon: "fa-solid fa-bolt",
                    label: [{ str: "Click before the star collapses!" }],
                    showTimer: true,
                    scale: 1,
                    rotation: 10,
                    breatheCenter: true
                }
            });
        } else if (type === "precision") {
            id = api.addQTE(p, {
                type: "precisionBar",
                parameters: {
                    speed: 0.82,
                    successZoneSize: 0.16,
                    label: [{ str: "Click inside the star zone." }],
                    icon: "fa-solid fa-star",
                    scale: 1,
                    rotation: 0
                }
            });
        } else if (type === "rhythm") {
            id = api.addQTE(p, {
                type: "rhythmClick",
                parameters: {
                    requiredSuccesses: 5,
                    shrinkDurationMs: 1050,
                    toleranceFraction: 0.16,
                    maxMisses: 3,
                    label: [{ str: "Click when the cosmic rings align!" }],
                    icon: "fa-solid fa-circle"
                }
            });
        } else if (type === "gravity") {
            id = api.addQTE(p, {
                type: "gravityBar",
                parameters: {
                    progressStartValue: 25,
                    catchZoneSize: 0.22,
                    moverSpeed: 3.1,
                    moverErraticness: 0.92,
                    gravity: 1,
                    riseSpeed: 1.55,
                    progressGainPerSecond: 10,
                    progressDrainPerSecond: 5,
                    canFail: true,
                    description: [{ str: "Hold and release to catch the star!" }],
                    icon: "Moonstone"
                }
            });
        }
    } catch (e2) {}
    if (id) {
        G.qtes[id] = { p: p, type: type, start: now };
        G.qteCd[p] = now + G.QTE_CD_MS;
        G.upper(p, [
            G.i(p, G.QTE_TYPES[type].item, 30),
            G.t(p, " Random QTE: " + G.QTE_TYPES[type].name + "\n", G.QTE_TYPES[type].color, 25, true),
            G.t(p, "Reward scales with core tier, multiplier, luck, shards, and ascensions.", "#ffffff", 18, false)
        ], 3000);
    }
};

G.finishQte = function (p, id, result) {
    var q = G.qtes[id] || { type: "progress" };
    delete G.qtes[id];
    var s = G.players[p] || G.load(p);
    var def = G.QTE_TYPES[q.type] || G.QTE_TYPES.progress;
    if (result) {
        var reward = G.qteReward(s, q.type);
        s.qteWins++;
        if (q.type === "progress") s.boosts.click = { mul: 1.25, until: G.now() + 30000 };
        if (q.type === "timed") s.boosts.auto = { mul: 1.35, until: G.now() + 35000 };
        if (q.type === "precision") s.boosts.click = { mul: 1.55, until: G.now() + 40000 };
        if (q.type === "rhythm") s.boosts.multi = { mul: 1.45, until: G.now() + 45000 };
        if (q.type === "gravity") s.boosts.all = { mul: 1.85, until: G.now() + 60000 };
        G.addCoins(p, reward, def.name);
        G.effect(p, def.name, 30000, def.item, def.name + " Reward");
        G.upper(p, [
            G.i(p, def.item, 36),
            G.t(p, " " + def.name + " Success\n", def.color, 30, true),
            G.t(p, "+" + G.fmt(reward) + " coins. Scaled by T" + (s.clickTier + 1) + " core.", "#ffffff", 20, false)
        ], 4500);
        G.fxPlayer(p, "soul_0", [80, 220, 255, 1], [255, 255, 255, 1], 55);
        G.sound(p, "levelUp", 0.9, 1.15);
    } else {
        s.qteFails++;
        G.say(p, def.name + " failed.", "#ff7777");
        G.sound(p, "glass", 0.6, 0.7);
    }
    s.dirty = true;
    s.uiDirty = true;
    G.syncShopPlayer(p);
    G.checkAch(p);
};

G.rollLootDef = function (s) {
    var total = 0;
    for (var i = 0; i < G.LOOT.length; i++) total += G.LOOT[i].weight;
    var luck = Math.min(70, s.luckLevel * 1.2 + s.prestige * 3 + G.boostAdd(s, "luck") * 100);
    var r = Math.random() * total;
    if (Math.random() * 100 < luck) r = r * 0.48;
    var a = 0;
    for (var j = 0; j < G.LOOT.length; j++) {
        a += G.LOOT[j].weight;
        if (r <= a) return G.LOOT[j];
    }
    return G.LOOT[0];
};

G.giveLoot = function (p, forced) {
    var s = G.players[p] || G.load(p);
    var l = forced || G.rollLootDef(s);
    try {
        api.giveItem(p, l.item, 1, {
            customDisplayName: l.name,
            customDescription: "Right-click or type /use to activate.",
            customAttributes: { gcLootV3: l.key, gcName: l.name }
        });
    } catch (e) {}
    s.lootFound++;
    s.dirty = true;
    s.uiDirty = true;
    G.upper(p, [
        G.i(p, l.item, 34),
        G.t(p, " Loot Found\n", l.color, 28, true),
        G.t(p, l.name, "#ffffff", 22, false)
    ], 3200);
    G.sound(p, "chestOpen", 0.85, 1.1);
    G.fxPlayer(p, "glint", [80, 220, 255, 1], [255, 220, 80, 1], 38);
};

G.tryLootDrop = function (p) {
    var s = G.players[p];
    if (!s) return;
    var chance = 0.012 + s.luckLevel * 0.0016 + s.prestige * 0.0012 + G.boostAdd(s, "luck");
    if (chance > 0.1) chance = 0.1;
    if (Math.random() < chance) G.giveLoot(p);
};

G.useHeldLoot = function (p) {
    var now = G.now();
    if (now < (G.useCd[p] || 0)) return;
    G.useCd[p] = now + 600;
    var slot = -1;
    var item = null;
    try {
        slot = api.getSelectedInventorySlotI(p);
        item = api.getItemSlot(p, slot);
    } catch (e) {}
    if (!item || !item.attributes || !item.attributes.customAttributes || !item.attributes.customAttributes.gcLootV3) {
        G.say(p, "Hold a Galactic loot item first.", "#ffcc66");
        return;
    }
    var s = G.players[p] || G.load(p);
    var key = item.attributes.customAttributes.gcLootV3;
    var name = item.attributes.customAttributes.gcName || "Loot";
    var value = Math.max(500, G.clickPower(s) * 90 + G.cps(s) * 25);
    if (key === "coin") {
        G.addCoins(p, value * 2, name);
    } else if (key === "click") {
        s.boosts.click = { mul: 2.6, until: now + 90000 };
        G.effect(p, "Quantum Tap", 90000, "Damage", "2.6x Click");
        G.say(p, "Quantum Tap active: 2.6x click for 90s.", "#66ffff");
    } else if (key === "auto") {
        s.boosts.auto = { mul: 2.4, until: now + 90000 };
        G.effect(p, "Nebula Battery", 90000, "Haste", "2.4x Auto");
        G.say(p, "Nebula Battery active: 2.4x auto for 90s.", "#55aaff");
    } else if (key === "multi") {
        s.boosts.multi = { mul: 1.8, until: now + 120000 };
        G.effect(p, "Multiplier Prism", 120000, "Block of Emerald", "1.8x Global");
        G.say(p, "Multiplier Prism active: 1.8x global multiplier for 120s.", "#55ff99");
    } else if (key === "luck") {
        s.boosts.luck = { add: 0.04, until: now + 150000 };
        G.effect(p, "Luck Core", 150000, "Lucky Block", "+4% Loot");
        G.say(p, "Luck Core active: +4% loot chance for 150s.", "#55ff99");
    } else if (key === "nova") {
        G.addCoins(p, value * 8, "Nova");
        G.effect(p, "Nova Surge", 30000, "Fireball Block", "Nova Coins");
        G.fxPlayer(p, "critical_hit", [255, 80, 40, 1], [255, 220, 80, 1], 80);
    } else if (key === "qte") {
        G.qteCd[p] = 0;
        G.startRandomQte(p);
    } else if (key === "offline") {
        G.addCoins(p, Math.max(value * 5, G.cps(s) * 1800 * G.OFFLINE_RATE), "Offline Comet");
    } else if (key === "refund") {
        G.addCoins(p, Math.max(value * 4, G.clickCost(s) + G.autoCost(s)), "Refund");
    } else if (key === "star") {
        s.shards++;
        G.effect(p, "Star Shard", 60000, "Moonstone", "Permanent +2%");
        G.say(p, "Star Shard absorbed. Permanent power increased.", "#ff66ff");
    } else if (key === "drone") {
        s.autoLevel += 3;
        G.say(p, "Drone Blueprint added +3 auto levels.", "#aaffaa");
    } else if (key === "core") {
        s.clickLevel += 5;
        G.say(p, "Core Spark added +5 click levels.", "#ffffff");
    } else if (key === "jackpot") {
        s.boosts.all = { mul: 2.5, until: now + 90000 };
        G.effect(p, "Jackpot Charm", 90000, "Gold Trophy", "2.5x All");
        G.say(p, "Jackpot Charm active: 2.5x all income for 90s.", "#ffffff");
    } else if (key === "crown") {
        s.boosts.all = { mul: 4, until: now + 120000 };
        s.shards += 2;
        G.effect(p, "Astral Crown", 120000, "Gold Trophy", "4x All");
        G.say(p, "Astral Crown: 4x all income for 120s and +2 shards.", "#ffffff");
    }
    try {
        if ((item.amount || 1) > 1) api.setItemSlot(p, slot, item.name, item.amount - 1, item.attributes);
        else api.setItemSlot(p, slot, "Air");
    } catch (e3) {}
    s.dirty = true;
    s.uiDirty = true;
    G.syncShopPlayer(p);
    G.sound(p, "levelUp", 0.8, 1.25);
    G.fxPlayer(p, "glint", [80, 220, 255, 1], [255, 80, 255, 1], 45);
};

G.spinNames = ["Tiny Comet", "Solar Voucher", "Drone Burst", "Quantum Tap", "Star Shard", "Void Tax", "JACKPOT", "QTE Rift", "Luck Core", "Nova Bomb", "Tier Refund", "Mega Frenzy", "Black Hole", "Double Loot"];

G.startCasino = function (p) {
    var s = G.players[p] || G.load(p);
    var cost = Math.max(2500, Math.floor(5000 * Math.pow(1.35, s.prestige) * (1 + s.clickTier * 0.2)));
    if (G.spin[p]) {
        G.say(p, "Casino already spinning.", "#ffcc66");
        return;
    }
    if (!G.pay(p, cost)) return;
    var q = Math.random() * 100 + s.luckLevel * 0.9 + s.prestige * 1.8 + s.clickTier * 1.2;
    var result = "loss";
    if (q > 113) result = "mega";
    else if (q > 104) result = "jackpot";
    else if (q > 96) result = "crown";
    else if (q > 88) result = "loot";
    else if (q > 78) result = "qte";
    else if (q > 66) result = "boost";
    else if (q > 54) result = "auto";
    else if (q > 42) result = "coins";
    else if (q > 30) result = "refund";
    G.spin[p] = { start: G.now(), next: 0, i: 0, result: result, paid: cost, final: false };
    G.sound(p, "cashRegister", 0.9, 0.75);
};

G.casinoReward = function (p, sp) {
    var s = G.players[p] || G.load(p);
    var icon = "Ultra Lucky Block";
    var title = "Void Tax";
    var text = "The casino ate your coins.";
    var color = "#ff7777";
    if (sp.result === "coins") {
        var a = sp.paid * (1 + Math.floor(Math.random() * 3)) + G.clickPower(s) * 20;
        G.addCoins(p, a, "casino");
        title = "Coin Burst";
        text = "+" + G.fmt(a) + " coins";
        color = "#ffd84a";
        icon = "Gold Coin";
        s.casinoWins++;
    } else if (sp.result === "refund") {
        var r = Math.floor(sp.paid * 1.2);
        G.addCoins(p, r, "refund");
        title = "Cosmic Refund";
        text = "+" + G.fmt(r) + " coins";
        color = "#aaffff";
        icon = "Gold Bar";
        s.casinoWins++;
    } else if (sp.result === "auto") {
        s.autoLevel += 2;
        title = "Drone Burst";
        text = "+2 auto levels";
        color = "#aaffaa";
        icon = "Golden Decoration";
        s.casinoWins++;
    } else if (sp.result === "boost") {
        s.boosts.click = { mul: 2.4, until: G.now() + 60000 };
        G.effect(p, "Click Frenzy", 60000, "Damage", "2.4x Click");
        title = "Click Frenzy";
        text = "2.4x click power for 60s";
        color = "#66ffff";
        icon = "Damage";
        s.casinoWins++;
    } else if (sp.result === "qte") {
        title = "QTE Rift";
        text = "Random QTE opened";
        color = "#8899ff";
        icon = "Block of Lapis Lazuli";
        s.casinoWins++;
        G.qteCd[p] = 0;
        G.startRandomQte(p);
    } else if (sp.result === "loot") {
        title = "Double Loot";
        text = "Two cosmic loot items dropped";
        color = "#55ff99";
        icon = "Lucky Block";
        s.casinoWins++;
        G.giveLoot(p);
        G.giveLoot(p);
    } else if (sp.result === "crown") {
        title = "Astral Crown";
        text = "Rare crown loot dropped";
        color = "#ffffff";
        icon = "Gold Trophy";
        s.casinoWins++;
        G.giveLoot(p, G.LOOT[G.LOOT.length - 1]);
    } else if (sp.result === "jackpot") {
        var j = sp.paid * 18 + G.qteReward(s, "gravity") * 4;
        G.addCoins(p, j, "JACKPOT");
        s.boosts.all = { mul: 2.2, until: G.now() + 120000 };
        G.effect(p, "Jackpot", 120000, "Gold Trophy", "2.2x All");
        title = "JACKPOT";
        text = "+" + G.fmt(j) + " coins and 2.2x all for 120s";
        color = "#ffffff";
        icon = "Gold Trophy";
        s.casinoWins++;
    } else if (sp.result === "mega") {
        var m = sp.paid * 50 + G.qteReward(s, "gravity") * 12;
        G.addCoins(p, m, "MEGA");
        s.shards++;
        s.boosts.all = { mul: 3.5, until: G.now() + 150000 };
        G.effect(p, "Mega Frenzy", 150000, "Beacon", "3.5x All");
        title = "MEGA FRENZY";
        text = "+" + G.fmt(m) + " coins, +1 shard, 3.5x all";
        color = "#ff66ff";
        icon = "Beacon";
        s.casinoWins++;
        G.giveLoot(p);
    }
    s.dirty = true;
    s.uiDirty = true;
    G.upper(p, [
        G.i(p, icon, 42),
        G.t(p, " " + title + "\n", color, 34, true),
        G.t(p, text, "#ffffff", 22, false)
    ], 3000);
    G.fxPlayer(p, sp.result === "loss" ? "scary_face" : "glint", [255, 220, 80, 1], [255, 80, 255, 1], sp.result === "mega" ? 120 : 45);
    G.sound(p, sp.result === "loss" ? "doorClose" : "levelUp", 0.85, sp.result === "mega" ? 1.55 : 1.05);
    G.syncShopPlayer(p);
    G.checkAch(p);
};

G.tickSpins = function () {
    var now = G.now();
    for (var p in G.spin) {
        var sp = G.spin[p];
        try {
            if (!api.playerIsInGame(p)) {
                delete G.spin[p];
                continue;
            }
        } catch (e) {}
        if (sp.final) {
            if (now >= sp.finalUntil) delete G.spin[p];
            continue;
        }
        if (now - sp.start < 2600) {
            if (now >= sp.next) {
                sp.next = now + 105;
                sp.i++;
                var name = G.spinNames[(sp.i + Math.floor(Math.random() * G.spinNames.length)) % G.spinNames.length];
                G.upper(p, [
                    G.i(p, "Ultra Lucky Block", 38),
                    G.t(p, " Cosmic Casino\n", "#ffd84a", 30, true),
                    G.t(p, ">>> " + name + " <<<", ["#66ffff", "#ff66ff", "#ffffff", "#ffd84a"][sp.i % 4], 26, true)
                ], 0);
                G.sound(p, "beep", 0.28, 1 + sp.i * 0.02);
            }
        } else {
            sp.final = true;
            sp.finalUntil = now + 3000;
            G.casinoReward(p, sp);
        }
    }
};

G.prestige = function (p) {
    var s = G.players[p] || G.load(p);
    var req = G.prestigeReq(s);
    var minTier = G.prestigeMinTier(s);
    var gain = G.prestigeGain(s);
    if (gain <= 0) {
        G.upper(p, [
            G.i(p, "Gold Trophy", 34),
            G.t(p, " Ascension Locked\n", "#ff7777", 28, true),
            G.t(p, "Need " + G.fmt(req) + " run coins and Core T" + (minTier + 1) + ". Current run: " + G.fmt(s.runCoins) + ", Core T" + (s.clickTier + 1) + ".", "#ffffff", 19, false)
        ], 5000);
        G.sound(p, "doorClose", 0.6, 0.8);
        return false;
    }
    s.coins = 0;
    s.runCoins = 0;
    s.clickLevel = 0;
    s.autoLevel = 0;
    s.multiLevel = 0;
    s.luckLevel = 0;
    s.clickTier = 0;
    s.autoTier = 0;
    s.prestige++;
    s.shards += gain;
    s.boosts = {};
    s.dirty = true;
    s.uiDirty = true;
    G.upper(p, [
        G.i(p, "Beacon", 44),
        G.t(p, " Ascension Complete\n", "#ff66ff", 34, true),
        G.t(p, "+" + gain + " Star Shards. Next requirement increased.", "#ffffff", 22, false)
    ], 6500);
    G.fxPlayer(p, "soul_0", [255, 80, 255, 1], [255, 255, 255, 1], 120);
    G.sound(p, "levelUp", 1, 0.8);
    G.save(p);
    G.syncShopPlayer(p);
    G.checkAch(p);
    return true;
};

G.checkAch = function (p) {
    var s = G.players[p];
    if (!s) return;
    for (var i = 0; i < G.ACH.length; i++) {
        var a = G.ACH[i];
        if (!s.ach[a.key] && a.need(s)) {
            s.ach[a.key] = 1;
            G.addCoins(p, a.reward, "achievement");
            G.upper(p, [
                G.i(p, "Gold Trophy", 34),
                G.t(p, " Achievement Unlocked\n", "#ffd84a", 28, true),
                G.t(p, a.name + " +" + G.fmt(a.reward) + " coins", "#ffffff", 22, false)
            ], 3500);
            G.sound(p, "cashRegister", 0.8, 1.2);
            G.fxPlayer(p, "glint", [255, 220, 80, 1], [255, 80, 255, 1], 45);
        }
    }
};

G.shopBase = function (cat, key, image, title, desc, pri, badge) {
    try {
        api.createShopItem(cat, key, {
            image: image,
            customTitle: title,
            description: desc,
            buyButtonText: [{ str: "Open" }],
            canBuy: true,
            sortPriority: pri || 100,
            badge: badge || undefined
        });
    } catch (e) {}
};

G.shopDyn = function (p, cat, key, image, title, desc, buy, pri, can) {
    try {
        api.createShopItemForPlayer(p, cat, key, {
            image: image,
            customTitle: title,
            description: desc,
            buyButtonText: [{ str: buy }],
            canBuy: can !== false,
            sortPriority: pri || 100
        });
    } catch (e) {}
};

G.setupShop = function () {
    try { api.configureShopCategory("gc_guide", { customTitle: "Galactic Guide", sortPriority: 1000, description: "How to play and what each mesh does." }); } catch (e) {}
    try { api.configureShopCategory("gc_upgrades", { customTitle: "Bulk Upgrades", sortPriority: 900, description: "Bulk click, auto, multiplier, luck, core, and drone tier upgrades." }); } catch (e2) {}
    try { api.configureShopCategory("gc_actions", { customTitle: "Actions", sortPriority: 800, description: "Casino, loot, prestige and utility." }); } catch (e3) {}
    G.shopBase("gc_guide", "guide_play", "Book", "How To Play", "Click unlocked core meshes for coins. Click upgrade meshes to buy exactly 1. Crouch-click an upgrade mesh to open this shop. Auto drones earn online and 5% while offline.", 1000, { text: "guide", type: "new" });
    G.shopBase("gc_guide", "guide_meshes", "Bookshelf", "Mesh Functions", "Core meshes: coins. Bookshelf: guide shop. Gold: click upgrade. Drone: auto upgrade. Emerald: global multiplier. Lucky Block: luck. Lapis: random QTE. Ultra Lucky: casino. Trophy: ascension.", 990);
    G.shopBase("gc_guide", "guide_balance", "Block of Emerald", "Balance Guide", "Small upgrades are cheap but small. Core evolution and drone tiers are expensive gates. Multiplier affects click, auto, QTE rewards, casino value, and loot value.", 980);
    G.shopBase("gc_guide", "guide_rebirth", "Gold Trophy", "Ascension Guide", "Ascension requires run coins since your last ascension and a minimum core tier. The requirement rises after every ascension, preventing unlimited rebirth.", 970);
    G.shopBase("gc_guide", "guide_loot", "Moonstone", "Loot Guide", "Loot items have visible effects through messages, effects, UI changes, or direct upgrades. Hold the item and use /use.", 960);
};

G.syncShopPlayer = function (p) {
    var s = G.players[p] || G.load(p);
    var amounts = [1, 10, 100];
    for (var i = 0; i < amounts.length; i++) {
        var a = amounts[i];
        G.shopDyn(p, "gc_upgrades", "click_" + a, "Block of Gold", "Click Power x" + a, "Level " + s.clickLevel + ". Next cost " + G.fmt(G.clickCost(s)) + ". Small cheap upgrades for manual clicking.", "Buy x" + a, 950 - i, true);
        G.shopDyn(p, "gc_upgrades", "auto_" + a, "Golden Decoration", "Auto Drone x" + a, "Level " + s.autoLevel + ". Next cost " + G.fmt(G.autoCost(s)) + ". Gives coins per second.", "Buy x" + a, 910 - i, true);
        G.shopDyn(p, "gc_upgrades", "multi_" + a, "Block of Emerald", "Multiplier x" + a, "Level " + s.multiLevel + ". Next cost " + G.fmt(G.multiCost(s)) + ". Boosts every major reward source.", "Buy x" + a, 870 - i, true);
        G.shopDyn(p, "gc_upgrades", "luck_" + a, "Lucky Block", "Luck x" + a, "Level " + s.luckLevel + ". Next cost " + G.fmt(G.luckCost(s)) + ". Better loot, casino, and random QTE rolls.", "Buy x" + a, 830 - i, true);
    }
    G.shopDyn(p, "gc_upgrades", "click_max", "Block of Gold", "Click Power MAX", "Buys up to 1000 levels or until coins run out.", "Buy Max", 790, true);
    G.shopDyn(p, "gc_upgrades", "auto_max", "Golden Decoration", "Auto Drone MAX", "Buys up to 1000 levels or until coins run out.", "Buy Max", 780, true);
    G.shopDyn(p, "gc_upgrades", "multi_max", "Block of Emerald", "Multiplier MAX", "Buys up to 1000 levels or until coins run out.", "Buy Max", 770, true);
    G.shopDyn(p, "gc_upgrades", "luck_max", "Lucky Block", "Luck MAX", "Buys up to 1000 levels or until coins run out.", "Buy Max", 760, true);
    var coreNext = G.CORE_TIERS[s.clickTier + 1];
    var autoNext = G.AUTO_TIERS[s.autoTier + 1];
    G.shopDyn(p, "gc_upgrades", "core_tier", coreNext ? coreNext.block : "Gold Trophy", "Core Evolution", coreNext ? "Unlock T" + (s.clickTier + 2) + " " + coreNext.name + ". Cost " + G.fmt(coreNext.cost) + ". Higher visible core becomes clickable." : "All core tiers unlocked.", coreNext ? "Buy " + G.fmt(coreNext.cost) : "MAX", 730, !!coreNext);
    G.shopDyn(p, "gc_upgrades", "auto_tier", autoNext ? autoNext.block : "Gold Trophy", "Drone Tier", autoNext ? "Upgrade drones to " + autoNext.name + ". Cost " + G.fmt(autoNext.cost) + "." : "All drone tiers unlocked.", autoNext ? "Buy " + G.fmt(autoNext.cost) : "MAX", 720, !!autoNext);
    G.shopDyn(p, "gc_actions", "casino", "Ultra Lucky Block", "Cosmic Casino", "Spin for refund, coins, auto levels, boosts, random QTEs, loot, crown, jackpot, or mega frenzy.", "Spin", 990, true);
    G.shopDyn(p, "gc_actions", "loot_use", "Moonstone Fragment", "Use Held Loot", "Activates the loot item in your hand.", "Use", 980, true);
    G.shopDyn(p, "gc_actions", "prestige", "Gold Trophy", "Ascension", "Requires " + G.fmt(G.prestigeReq(s)) + " run coins and Core T" + (G.prestigeMinTier(s) + 1) + ". Current gain: " + G.prestigeGain(s) + " shards.", "Ascend", 970, true);
    G.shopDyn(p, "gc_actions", "stats", "Book", "Refresh Stats", "Refresh UI and guide text.", "Refresh", 960, true);
};

G.openShop = function (p, cat) {
    G.syncShopPlayer(p);
    try { api.openShop(p, false, cat || "gc_guide", true); } catch (e) { G.showGuidePage(p, "guide_play"); }
};

G.showGuidePage = function (p, key) {
    if (key === "guide_meshes") {
        G.upper(p, [
            G.i(p, "Bookshelf", 34),
            G.t(p, " Mesh Function Guide\n", "#ffd84a", 28, true),
            G.t(p, "Cores: click coins. Bookshelf: shop. Gold: click power. Drone: auto. Emerald: multiplier. Lucky: luck. Lapis: random QTE. Ultra Lucky: casino. Trophy: ascension.", "#ffffff", 18, false)
        ], 12000);
    } else if (key === "guide_balance") {
        G.upper(p, [
            G.i(p, "Block of Emerald", 34),
            G.t(p, " Balance Guide\n", "#55ff99", 28, true),
            G.t(p, "Small upgrades are cheaper but weaker. Core and drone tiers are expensive gates. Multiplier quietly boosts all major reward math.", "#ffffff", 18, false)
        ], 12000);
    } else if (key === "guide_rebirth") {
        var s = G.players[p] || G.load(p);
        G.upper(p, [
            G.i(p, "Gold Trophy", 34),
            G.t(p, " Ascension Guide\n", "#ff66ff", 28, true),
            G.t(p, "Requirement uses run coins since last ascension. Need " + G.fmt(G.prestigeReq(s)) + " run coins and Core T" + (G.prestigeMinTier(s) + 1) + ". Requirement rises after each ascension.", "#ffffff", 18, false)
        ], 12000);
    } else if (key === "guide_loot") {
        G.upper(p, [
            G.i(p, "Moonstone", 34),
            G.t(p, " Loot Guide\n", "#ff66ff", 28, true),
            G.t(p, "Loot gives coins, boosts, random QTEs, offline coins, free levels, shards, or rare all-income effects. Hold loot and type /use.", "#ffffff", 18, false)
        ], 12000);
    } else {
        G.upper(p, [
            G.i(p, "Book", 34),
            G.t(p, " How To Play\n", "#66ffff", 28, true),
            G.t(p, "1. Click unlocked cores.\n2. Click upgrade meshes for +1.\n3. Crouch-click upgrades for shop.\n4. Buy many small upgrades, then expensive tiers.\n5. Random QTE, casino, loot, offline profit, then ascension.", "#ffffff", 18, false)
        ], 14000);
    }
};

G.shopBuy = function (p, cat, key) {
    var now = G.now();
    if (now < (G.shopCd[p] || 0)) {
        try { api.sendOverShopInfo(p, "Slow down."); } catch (e) {}
        return;
    }
    G.shopCd[p] = now + G.SHOP_CD_MS;
    if (cat === "gc_guide" || key.indexOf("guide_") === 0) {
        G.showGuidePage(p, key);
        return;
    }
    if (key === "click_1") G.buyBulk(p, "click", 1);
    else if (key === "click_10") G.buyBulk(p, "click", 10);
    else if (key === "click_100") G.buyBulk(p, "click", 100);
    else if (key === "click_max") G.buyBulk(p, "click", "max");
    else if (key === "auto_1") G.buyBulk(p, "auto", 1);
    else if (key === "auto_10") G.buyBulk(p, "auto", 10);
    else if (key === "auto_100") G.buyBulk(p, "auto", 100);
    else if (key === "auto_max") G.buyBulk(p, "auto", "max");
    else if (key === "multi_1") G.buyBulk(p, "multi", 1);
    else if (key === "multi_10") G.buyBulk(p, "multi", 10);
    else if (key === "multi_100") G.buyBulk(p, "multi", 100);
    else if (key === "multi_max") G.buyBulk(p, "multi", "max");
    else if (key === "luck_1") G.buyBulk(p, "luck", 1);
    else if (key === "luck_10") G.buyBulk(p, "luck", 10);
    else if (key === "luck_100") G.buyBulk(p, "luck", 100);
    else if (key === "luck_max") G.buyBulk(p, "luck", "max");
    else if (key === "core_tier") G.buyCoreTier(p);
    else if (key === "auto_tier") G.buyAutoTier(p);
    else if (key === "casino") G.startCasino(p);
    else if (key === "loot_use") G.useHeldLoot(p);
    else if (key === "prestige") G.prestige(p);
    else if (key === "stats") { G.refreshUi(p); G.showTip(p); }
};

G.handleUpgradeMesh = function (p, key) {
    if (G.isCrouch(p)) {
        G.openShop(p, "gc_upgrades");
        return true;
    }
    if (key === "buyClick") return G.buyClick(p, false);
    if (key === "buyAuto") return G.buyAuto(p, false);
    if (key === "buyMulti") return G.buyMulti(p, false);
    if (key === "buyLuck") return G.buyLuck(p, false);
    return false;
};

G.handleAction = function (p, key) {
    if (!key) return false;
    if (key.indexOf("core") === 0) {
        G.coreClick(p, Number(key.slice(4)) || 0);
        return true;
    }
    if (key === "shop") { G.openShop(p, "gc_guide"); return true; }
    if (key === "buyClick" || key === "buyAuto" || key === "buyMulti" || key === "buyLuck") { G.handleUpgradeMesh(p, key); return true; }
    if (key === "casino") { G.startCasino(p); return true; }
    if (key === "qte") { G.startRandomQte(p); return true; }
    if (key === "prestige") { G.prestige(p); return true; }
    return false;
};

G.showTop = function (p) {
    var ids = [];
    try { ids = api.getPlayerIds(); } catch (e) {}
    var arr = [];
    for (var i = 0; i < ids.length; i++) {
        var s = G.players[ids[i]];
        if (s) arr.push({ id: ids[i], total: s.totalCoins });
    }
    arr.sort(function (a, b) { return b.total - a.total; });
    var msg = [G.i(p, "Gold Trophy", 25), G.t(p, " Galactic Top\n", "#ffd84a", 24, true)];
    for (var j = 0; j < Math.min(8, arr.length); j++) {
        var nm = "Player";
        try { nm = api.getEntityName(arr[j].id); } catch (e2) {}
        msg.push(G.t(p, (j + 1) + ". " + nm + " total " + G.fmt(arr[j].total) + "\n", j === 0 ? "#ffffff" : "#aaffff", 18, false));
    }
    try { api.sendMessage(p, msg); api.setClientOption(p, "middleTextUpper", msg); } catch (e3) {}
};

G.command = function (p, cmd) {
    var raw = String(cmd || "").trim();
    if (raw.charAt(0) === "/" || raw.charAt(0) === "!") raw = raw.slice(1);
    var parts = raw.split(/\s+/);
    var c = String(parts[0] || "").toLowerCase();
    var sub = String(parts[1] || "").toLowerCase();
    if (c === "help" || c === "gchelp") { G.openShop(p, "gc_guide"); return true; }
    if (c === "shop") { G.openShop(p, sub === "actions" ? "gc_actions" : sub === "upgrades" ? "gc_upgrades" : "gc_guide"); return true; }
    if (c === "guide") { G.showGuidePage(p, "guide_play"); return true; }
    if (c === "qte") { G.startRandomQte(p); return true; }
    if (c === "casino") { G.startCasino(p); return true; }
    if (c === "use") { G.useHeldLoot(p); return true; }
    if (c === "prestige" || c === "ascend" || c === "rebirth") { G.prestige(p); return true; }
    if (c === "top") { G.showTop(p); return true; }
    if (c === "stats") { G.refreshUi(p); G.showTip(p); return true; }
    if (c === "spawn") { try { api.setPosition(p, G.POS.spawn[0], G.POS.spawn[1], G.POS.spawn[2]); } catch (e) {} return true; }
    if (c === "buy") {
        if (sub === "click") G.buyBulk(p, "click", Number(parts[2]) || 1);
        else if (sub === "auto") G.buyBulk(p, "auto", Number(parts[2]) || 1);
        else if (sub === "multi") G.buyBulk(p, "multi", Number(parts[2]) || 1);
        else if (sub === "luck") G.buyBulk(p, "luck", Number(parts[2]) || 1);
        else if (sub === "tier") G.buyCoreTier(p);
        else if (sub === "autotier") G.buyAutoTier(p);
        else G.openShop(p, "gc_upgrades");
        return true;
    }
    if (c === "resetme") {
        G.players[p] = G.defaultStats();
        G.save(p);
        G.refreshUi(p);
        G.syncShopPlayer(p);
        G.say(p, "Your Galactic Clicker data reset.", "#ffcc66");
        return true;
    }
    if (c === "gcfix" && G.isAdmin(p)) {
        G.spawnMeshes();
        G.setupShop();
        var ids = [];
        try { ids = api.getPlayerIds(); } catch (e2) {}
        for (var i = 0; i < ids.length; i++) {
            G.applyLook(ids[i]);
            G.refreshUi(ids[i]);
            G.syncShopPlayer(ids[i]);
        }
        G.say(p, "Galactic Clicker V3 fixed.", "#aaffaa");
        return true;
    }
    if (c === "gcgive" && G.isAdmin(p)) { G.addCoins(p, Number(parts[1]) || 1000000, "admin"); G.syncShopPlayer(p); return true; }
    if (c === "gcspawn" && G.isAdmin(p)) { G.spawnMeshes(); G.say(p, "Meshes spawned.", "#aaffaa"); return true; }
    if (c === "gcsaveall" && G.isAdmin(p)) { G.saveAll(); G.say(p, "Saved all.", "#aaffaa"); return true; }
    return false;
};

G.fixChatMessage = function (p, text) {
    var s = G.players[p] || G.load(p);
    var name = "Player";
    try { name = api.getEntityName(p); } catch (e) {}
    try {
        api.broadcastMessage([
            { str: "[", style: { color: "#5555ff" } },
            { str: G.rankName(s), style: { color: s.prestige > 0 ? "#ff66ff" : "#66ffff", fontWeight: "bold" } },
            { str: "] ", style: { color: "#5555ff" } },
            { str: name, style: { color: "#ffffff", fontWeight: "bold" } },
            { str: ": ", style: { color: "#aaaaaa" } },
            { str: String(text || ""), style: { color: "#ffffff" } }
        ]);
    } catch (e2) {}
};

G.tickAuto = function () {
    var ids = [];
    try { ids = api.getPlayerIds(); } catch (e) {}
    var now = G.now();
    for (var i = 0; i < ids.length; i++) {
        var p = ids[i];
        var s = G.players[p] || G.load(p);
        s.playMs += 1000;
        G.cleanBoosts(s);
        var cps = G.cps(s);
        if (cps > 0) G.addCoins(p, cps, "");
        if (s.clearUpperAt && now >= s.clearUpperAt && !G.spin[p]) {
            s.clearUpperAt = 0;
            try { api.setClientOption(p, "middleTextUpper", ""); } catch (e2) {}
        }
        G.checkAch(p);
    }
};

G.tickUi = function () {
    var ids = [];
    try { ids = api.getPlayerIds(); } catch (e) {}
    for (var i = 0; i < ids.length; i++) {
        var s = G.players[ids[i]];
        if (!s || s.uiDirty) G.refreshUi(ids[i]);
    }
};

G.tickTips = function () {
    var ids = [];
    try { ids = api.getPlayerIds(); } catch (e) {}
    G.tipIndex++;
    for (var i = 0; i < ids.length; i++) G.showTip(ids[i]);
};

G.tick = function (ms) {
    ms = ms || 50;
    G.timers.auto += ms;
    G.timers.ui += ms;
    G.timers.save += ms;
    G.timers.tip += ms;
    G.tickSpins();
    if (G.timers.auto >= 1000) { G.timers.auto = 0; G.tickAuto(); }
    if (G.timers.ui >= 1500) { G.timers.ui = 0; G.tickUi(); }
    if (G.timers.save >= 15000) { G.timers.save = 0; G.saveAll(); }
    if (G.timers.tip >= 9000) { G.timers.tip = 0; G.tickTips(); }
};

G.init = function () {
    if (G.started) return;
    G.started = true;
    try { api.setCallbackValueFallback("onPlayerDropItem", "preventDrop"); } catch (e0) {}
    try { api.setCallbackValueFallback("onPlayerChangeBlock", "preventChange"); } catch (e1) {}
    try { api.setCallbackValueFallback("onPlayerAttemptCraft", "preventCraft"); } catch (e2) {}
    try { api.setCallbackValueFallback("onPlayerDamagingOtherPlayer", "preventDamage"); } catch (e3) {}
    G.spawnMeshes();
    G.setupShop();
};

G.init();

onPlayerJoin = function (p) {
    G.load(p);
    G.applyLook(p);
    try { api.setPosition(p, G.POS.spawn[0], G.POS.spawn[1], G.POS.spawn[2]); } catch (e) {}
    G.syncShopPlayer(p);
    G.refreshUi(p);
    G.showWelcome(p);
};

onPlayerLeave = function (p) {
    G.save(p);
    delete G.players[p];
    delete G.clickCd[p];
    delete G.useCd[p];
    delete G.shopCd[p];
    delete G.qteCd[p];
    delete G.spin[p];
};

onClose = function () {
    G.saveAll();
};

doPeriodicSave = function () {
    G.saveAll();
};

tick = function (ms) {
    try { G.tick(ms); } catch (e) { try { api.log("Galactic Clicker V3 tick error: " + String(e)); } catch (e2) {} }
};

onPlayerClick = function (p, alt, x, y, z, block, targetEId) {
    if (alt && !targetEId) {
        G.useHeldLoot(p);
        return "preventAction";
    }
    var key = targetEId ? G.meshAction[targetEId] : null;
    if (!key) key = G.nearAction(p);
    if (key) {
        G.handleAction(p, key);
        return "preventAction";
    }
};

onTouchscreenActionButton = function (p, touchDown) {
    var s = G.players[p] || G.load(p);
    s.touch = true;
    if (!touchDown) return;
    var key = G.nearAction(p);
    if (key) G.handleAction(p, key);
    else G.useHeldLoot(p);
};

onPlayerBreakMeshEntity = function (p, entityId) {
    if (G.meshAction[entityId]) return "preventBreak";
};

onPlayerFinishQTE = function (p, qteId, result) {
    G.finishQte(p, qteId, result);
};

onPlayerBoughtShopItem = function (p, categoryKey, itemKey, item, userInput) {
    G.shopBuy(p, String(categoryKey || ""), String(itemKey || ""));
};

onPlayerToggledShopMenu = function (p, isOpen) {
    if (isOpen) G.syncShopPlayer(p);
};

playerCommand = function (p, cmd) {
    if (G.command(p, cmd)) return true;
};

onPlayerChat = function (p, msg, channelName) {
    var t = String(msg || "");
    var trimmed = t.trim();
    if (!trimmed) return false;
    if (trimmed.charAt(0) === "/" || trimmed.charAt(0) === "!") {
        G.command(p, trimmed);
        return false;
    }
    G.fixChatMessage(p, t);
    return false;
};

onPlayerDropItem = function (p) {
    if (G.isAdmin(p)) return;
    return "preventDrop";
};

Your CSS

-

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions