smoothretro82

DiceBot 1.4

Nov 24th, 2025 (edited)
69
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 36.92 KB | None | 0 0
  1. // =============================
  2. // Incremental Dice Game (All Upgrades Merged)
  3. // Features: Third Die, Crits, Risk, Random Events, Smart Scaling, Items & Inventory,
  4. // Prestige Shop + PP, Prestige Shop Rotation, Auto-unlocks, Rebirths, Slots, Relics,
  5. // Leaderboards, Save/Load JSON export, Autosave, STEAL/GIVE 💰
  6. // =============================
  7.  
  8. // NOTE: This script assumes a Node-like environment for saving (fs) OR a browser environment (localStorage).
  9.  
  10. // -----------------------------
  11. // Configuration & Constants
  12. // -----------------------------
  13. const fs = (typeof require !== "undefined") ? require('fs') : null;
  14. const SAVE_PATH = '/tmp/dice_game_save.json'; // change to a writable path for your environment
  15. const LOCAL_STORAGE_KEY = 'diceGameSaveData'; // Key for localStorage
  16. const AUTOSAVE_INTERVAL_MS = 60000; // 30s
  17.  
  18. let players = {}; // main in-memory store
  19.  
  20. const ROLL_COOLDOWN_MS_BASE = 30000;
  21. const RISK_COOLDOWN_MS = 25000;
  22. const STEAL_COOLDOWN_MS = 60000; // 1 minute cooldown on stealing
  23. const STEAL_BASE_MAX_PERCENT = 0.20; // Max 5% of target's points
  24. const STEAL_SUCCESS_CHANCE = 0.4; // 40% chance of successful steal
  25. const PRESTIGE_REQUIREMENT = 2500;
  26. const PRESTIGE_BONUS = 0.75;
  27.  
  28. const THIRD_DIE_BASE_COST = 3500;
  29. const BASE_CRIT_CHANCE = 0.05;
  30. const CRIT_MULTIPLIER = 3;
  31. const CRIT_UPGRADE_BASE_COST = 500;
  32. const RANDOM_EVENT_CHANCE_BASE = 0.050;
  33. const ITEM_DROP_CHANCE_BASE = 0.15;
  34. const RISK_LOSS_PERCENT = 0.75;
  35. const JACKPOT_FLAT = 150;
  36. const LUCKY_PERCENT = 0.20;
  37.  
  38. const ADMINS = ["dominoguy_"]; // replace with your admin nicknames
  39. function isAdmin(nick) {
  40. return ADMINS.includes(nick);
  41. }
  42.  
  43.  
  44. // Smart scaling
  45. const LEVEL_SOFT_CAP_MULT = 50;
  46. const MULT_EXP_BASE = 1.075;
  47. const DICE_SOFT_CAP = 100;
  48. const DICE_EXP_BASE = 1.06;
  49. const CRIT_SOFT_CAP = 20;
  50. const CRIT_EXP_BASE = 1.12;
  51.  
  52. // Items & Pool
  53. const ITEM_POOL = [
  54. { id: "lucky_charm", name: "Lucky Charm", weight: 50, description: "+5% crit for next 10 rolls", type: "consumable" },
  55. { id: "iron_dice", name: "Iron Dice", weight: 35, description: "+2 dice bonus for next 20 rolls", type: "consumable" },
  56. { id: "wind_token", name: "Wind Token", weight: 14, description: "Removes roll cooldown once", type: "consumable" },
  57. { id: "golden_sphere", name: "Golden Sphere", weight: 1, nameDisplay: "Golden Sphere", description: "Permanent +1 multiplier (ultra-rare)", type: "perm" }
  58. ];
  59.  
  60. const LUCKY_CHARM_ROLLS = 7;
  61. const LUCKY_CHARM_CRIT_BONUS = 0.05;
  62. const IRON_DICE_ROLLS = 20;
  63. const IRON_DICE_BONUS = 2;
  64.  
  65. // Prestige Shop full pool (items include auto-unlock prestige requirement)
  66. const PRESTIGE_SHOP_POOL = [
  67. { id: "perm_mult1", cost: 3, name: "Permanent Multiplier +1", desc: "Permanent +1 to multiplier.", unlockPrestige: 0, effect: { permMult: 1 }, rarity: "common" },
  68. { id: "perm_mult2", cost: 6, name: "Permanent Multiplier +2", desc: "Permanent +2 to multiplier.", unlockPrestige: 5, effect: { permMult: 2 }, rarity: "uncommon" },
  69. { id: "perm_crit1", cost: 4, name: "Permanent +1% Crit", desc: "Permanent +1% crit chance.", unlockPrestige: 0, effect: { permCrit: 1 }, rarity: "common" },
  70. { id: "perm_die1", cost: 5, name: "Permanent +1 Dice Bonus", desc: "Permanent +1 dice bonus.", unlockPrestige: 1, effect: { permDice: 1 }, rarity: "common" },
  71. { id: "event_boost", cost: 10, name: "Random Events +50%", desc: "Permanently +50% random event chance.", unlockPrestige: 3, effect: { permEventBoost: 0.5 }, rarity: "rare" },
  72. { id: "item_luck", cost: 8, name: "Item Drop Rate +50%", desc: "Permanently +50% item drop chance.", unlockPrestige: 2, effect: { permItemBoost: 0.5 }, rarity: "rare" },
  73. // Relics (tier 2, high cost)
  74. { id: "relic_fury", cost: 25, name: "Relic of Fury", desc: "Permanent +25% crit multiplier (multiplicative to CRIT_MULTIPLIER).", unlockPrestige: 8, effect: { relic: "fury" }, rarity: "legendary" },
  75. { id: "relic_titan", cost: 30, name: "Relic of Titans", desc: "Permanent +2 multiplier.", unlockPrestige: 10, effect: { relic: "titan" }, rarity: "legendary" }
  76. ];
  77.  
  78. // Shop rotation config
  79. const SHOP_ROTATION_INTERVAL_MS = 24 * 60 * 60 * 1000; // rotate daily
  80. let shopRotationSeed = Date.now(); // changes periodically to rotate items
  81.  
  82. // Slots config
  83. const SLOTS_COST = 850;
  84. const SLOTS_PAYOUTS = {
  85. 3: 10000,  // triple match highest
  86. 2: 2000,   // two match
  87. "jackpot": 50000 // rare same symbol special
  88. };
  89. const SLOTS_SYMBOLS = [1,2,3,4,5,6]; // simple numeric symbols
  90.  
  91. // Leaderboard config
  92. const LEADERBOARD_MAX = 10;
  93.  
  94. // Utility
  95. function int(x) { return Math.max(0, Math.floor(x)); }
  96. function randRange(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }
  97.  
  98. // -----------------------------
  99. // Persistence: Save / Load / Export (UPDATED FOR LOCALSTORAGE FALLBACK)
  100. // -----------------------------
  101. function savePlayersToDisk(path = SAVE_PATH) {
  102. const data = JSON.stringify(players);
  103.  
  104. // 1. Node.js (fs) Save
  105. if (fs) {
  106. try {
  107. fs.writeFileSync(path, data, 'utf8');
  108. return true;
  109. } catch (e) {
  110. console.error("FS Save failed:", e);
  111. return false;
  112. }
  113. }
  114.  
  115. // 2. LocalStorage (Browser Fallback) Save
  116. if (typeof localStorage !== "undefined") {
  117. try {
  118. localStorage.setItem(LOCAL_STORAGE_KEY, data);
  119. return true;
  120. } catch (e) {
  121. console.error("LocalStorage Save failed:", e);
  122. return false;
  123. }
  124. }
  125.  
  126. return false; // No persistence method available
  127. }
  128.  
  129. function loadPlayersFromDisk(path = SAVE_PATH) {
  130. let raw = null;
  131.  
  132. // 1. Node.js (fs) Load
  133. if (fs) {
  134. try {
  135. if (fs.existsSync(path)) {
  136. raw = fs.readFileSync(path, 'utf8');
  137. }
  138. } catch (e) {
  139. console.error("FS Load failed:", e);
  140. }
  141. }
  142.  
  143. // 2. LocalStorage (Browser Fallback) Load
  144. if (!raw && typeof localStorage !== "undefined") {
  145. try {
  146. raw = localStorage.getItem(LOCAL_STORAGE_KEY);
  147. } catch (e) {
  148. console.error("LocalStorage Load failed:", e);
  149. }
  150. }
  151.  
  152. if (raw) {
  153. try {
  154. players = JSON.parse(raw);
  155. return true;
  156. } catch (e) {
  157. console.error("JSON parsing failed:", e);
  158. return false;
  159. }
  160. }
  161.  
  162. return false; // No save data found
  163. }
  164.  
  165. function exportPlayers(path) {
  166. // For local storage, return the JSON string so the user can copy it.
  167. if (typeof localStorage !== "undefined" && !fs) {
  168. return JSON.stringify(players);
  169. }
  170. // For fs environments, stick to the original file export.
  171. return savePlayersToDisk(path);
  172. }
  173.  
  174. // Load on startup
  175. loadPlayersFromDisk();
  176.  
  177. // autosave - updated to use both fs and localStorage check
  178. if (typeof setInterval !== "undefined" && (fs || typeof localStorage !== "undefined")) {
  179. setInterval(() => {
  180. const ok = savePlayersToDisk();
  181. if (!ok) console.warn("Autosave failed.");
  182. }, AUTOSAVE_INTERVAL_MS);
  183. }
  184.  
  185. // -----------------------------
  186. // Player init and helpers
  187. // -----------------------------
  188. function getPlayer(nick) {
  189. if (!nick) return null;
  190. if (!players[nick]) {
  191. players[nick] = {
  192. points: 0,
  193. multiplier: 1,
  194. diceBonus: 0,
  195. prestige: 0,
  196. pp: 0, // Prestige Points
  197. rebirths: 0, // multi-prestige rebirth currency
  198. lastRoll: 0,
  199. extraDie: 0,
  200. critUpgrades: 0,
  201. lastRisk: 0,
  202. lastSteal: 0, // NEW: Steal Cooldown Tracker
  203. goldenNextRoll: false,
  204. brokenDiceTurns: 0,
  205. inventory: {},
  206. luckyCharmRolls: 0,
  207. ironDiceRolls: 0,
  208. windTokens: 0,
  209. goldenSpheres: 0,
  210. permMult: 0,
  211. permCrit: 0,
  212. permDice: 0,
  213. permEventBoost: 0,
  214. permItemBoost: 0,
  215. cdReduction: 0, // seconds
  216. relics: {}, // id -> true
  217. meta: { rolls: 0, crits: 0 } // metrics for achievements/leaderboard
  218. };
  219. }
  220. // Ensure new properties exist for players loaded from old saves
  221. if (typeof players[nick].lastSteal === 'undefined') players[nick].lastSteal = 0;
  222.  
  223. return players[nick];
  224. }
  225.  
  226. function getCritChance(player) {
  227. let base = BASE_CRIT_CHANCE + (player.critUpgrades * 0.01) + (player.permCrit * 0.01);
  228. if (player.luckyCharmRolls > 0) base += LUCKY_CHARM_CRIT_BONUS;
  229. return base;
  230. }
  231.  
  232. // Smart scaling costs
  233. function getMultCost(player) {
  234. const level = player.multiplier;
  235. const base = 50;
  236. if (level <= LEVEL_SOFT_CAP_MULT) return int(level * base);
  237. const over = level - LEVEL_SOFT_CAP_MULT;
  238. const linear = LEVEL_SOFT_CAP_MULT * base;
  239. return int(linear * Math.pow(MULT_EXP_BASE, over));
  240. }
  241. function getDiceCost(player) {
  242. const nextBonus = player.diceBonus + 1;
  243. const base = 100;
  244. if (nextBonus <= DICE_SOFT_CAP) return int(nextBonus * base);
  245. const over = nextBonus - DICE_SOFT_CAP;
  246. const linear = DICE_SOFT_CAP * base;
  247. return int(linear * Math.pow(DICE_EXP_BASE, over));
  248. }
  249. function getCritUpgradeCost(player) {
  250. const next = player.critUpgrades + 1;
  251. const base = CRIT_UPGRADE_BASE_COST;
  252. if (next <= CRIT_SOFT_CAP) return int(next * base);
  253. const over = next - CRIT_SOFT_CAP;
  254. const linear = CRIT_SOFT_CAP * base;
  255. return int(linear * Math.pow(CRIT_EXP_BASE, over));
  256. }
  257. function getThirdDieCost(player) { return int(THIRD_DIE_BASE_COST); }
  258.  
  259. // Item helpers
  260. function addItemToInventory(player, itemId, count = 1) {
  261. if (!player.inventory[itemId]) player.inventory[itemId] = 0;
  262. player.inventory[itemId] += count;
  263. if (itemId === "wind_token") player.windTokens = player.inventory[itemId];
  264. if (itemId === "golden_sphere") player.goldenSpheres = player.inventory[itemId];
  265. }
  266. function removeItemFromInventory(player, itemId, count = 1) {
  267. if (!player.inventory[itemId]) return false;
  268. player.inventory[itemId] = Math.max(0, player.inventory[itemId] - count);
  269. if (player.inventory[itemId] === 0) delete player.inventory[itemId];
  270. if (itemId === "wind_token") player.windTokens = player.inventory[itemId] || 0;
  271. if (itemId === "golden_sphere") player.goldenSpheres = player.inventory[itemId] || 0;
  272. return true;
  273. }
  274. function getItemDefById(itemId) { return ITEM_POOL.find(it => it.id === itemId); }
  275. function weightedItemDrop() {
  276. const total = ITEM_POOL.reduce((s, it) => s + (it.weight || 0), 0);
  277. let r = Math.random() * total;
  278. for (const it of ITEM_POOL) {
  279. if (r < (it.weight || 0)) return it;
  280. r -= (it.weight || 0);
  281. }
  282. return null;
  283. }
  284.  
  285. // Shop rotation / availability
  286. function rotateShopSeed() {
  287. shopRotationSeed = Date.now();
  288. }
  289. // compute available shop items given rotation + player's prestige unlocks
  290. function getAvailableShopForPlayer(player) {
  291. const now = Date.now();
  292. // rotate index based on days since epoch and seed
  293. const days = Math.floor((now + shopRotationSeed) / SHOP_ROTATION_INTERVAL_MS);
  294. // simple deterministic shuffle selection: pick items where hash%3 == days%3 to rotate in/out
  295. const pickBucket = days % 3;
  296. const pool = PRESTIGE_SHOP_POOL.filter(it => (it.unlockPrestige || 0) <= (player.prestige || 0));
  297. // apply rotation filter for variety
  298. const available = pool.filter(it => {
  299. // simple hash: sum char codes mod 3
  300. const h = (it.id.split('').reduce((s,c)=>s+c.charCodeAt(0),0)) % 3;
  301. return h === pickBucket || it.rarity === 'common';
  302. });
  303. // ensure there is always at least a few items
  304. return available.length ? available : pool.slice(0,4);
  305. }
  306.  
  307. // Leaderboards
  308. function topPlayersByPoints(limit = LEADERBOARD_MAX) {
  309. const arr = Object.entries(players).map(([nick, p]) => ({ nick, points: p.points || 0 }));
  310. arr.sort((a,b) => b.points - a.points);
  311. return arr.slice(0, limit);
  312. }
  313. function topPlayersByPrestige(limit = LEADERBOARD_MAX) {
  314. const arr = Object.entries(players).map(([nick,p]) => ({ nick, prestige: p.prestige || 0 }));
  315. arr.sort((a,b) => b.prestige - a.prestige);
  316. return arr.slice(0, limit);
  317. }
  318.  
  319. // Apply relic effects on runtime multipliers
  320. function applyRelicEffects(player, baseCritMultiplier = CRIT_MULTIPLIER) {
  321. let critMult = baseCritMultiplier;
  322. if (player.relics && player.relics["relic_fury"]) {
  323. critMult = Math.floor(critMult * 1.25); // 25% stronger
  324. }
  325. return critMult;
  326. }
  327. function relicsGrantMultiplier(player) {
  328. let add = 0;
  329. if (player.relics && player.relics["relic_titan"]) add += 2;
  330. return add;
  331. }
  332.  
  333. // -----------------------------
  334. // Core message handler
  335. // -----------------------------
  336. w.on("msg", (data) => {
  337. if (!data.msg || !data.msg.startsWith("=")) return;
  338. const nick = data.nick;
  339. if (!nick) return;
  340. const args = data.msg.slice(1).trim().split(/\s+/);
  341. const cmd = args[0].toLowerCase();
  342. const player = getPlayer(nick);
  343.  
  344. switch (cmd) {
  345.  
  346. // ----- ROLL -----
  347. case "roll": {
  348. const now = Date.now();
  349. const baseCooldown = Math.max(1000, ROLL_COOLDOWN_MS_BASE - (player.cdReduction * 1000)); // ms
  350. const timeLeft = player.lastRoll + baseCooldown - now;
  351. if (timeLeft > 0) {
  352. const seconds = (timeLeft / 1000).toFixed(1);
  353. w.chat.send(`${nick}, wait ${seconds}s. cooldown > 30 sec.`);
  354. break;
  355. }
  356. player.lastRoll = now;
  357.  
  358. // roll dice
  359. const d1 = randRange(1,6);
  360. const d2 = randRange(1,6);
  361. const d3 = player.extraDie ? randRange(1,6) : 0;
  362.  
  363. const tempDicePenalty = player.brokenDiceTurns > 0 ? 1 : 0;
  364. const ironDiceTemp = player.ironDiceRolls > 0 ? IRON_DICE_BONUS : 0;
  365. const effectiveDiceBonus = Math.max(0, player.diceBonus + ironDiceTemp + (player.permDice || 0) - tempDicePenalty);
  366.  
  367. const b1 = Math.max(1, d1 + effectiveDiceBonus);
  368. const b2 = Math.max(1, d2 + effectiveDiceBonus);
  369. const b3 = player.extraDie ? Math.max(1, d3 + effectiveDiceBonus) : 0;
  370. const totalRoll = b1 + b2 + b3;
  371.  
  372. const goldenPermanentMult = player.goldenSpheres || 0;
  373. const effectiveMultiplier = (player.multiplier + (player.permMult || 0) + goldenPermanentMult + relicsGrantMultiplier(player));
  374. const prestigeMult = 1 + (player.prestige * PRESTIGE_BONUS);
  375.  
  376. const critChance = getCritChance(player);
  377. const isCrit = player.goldenNextRoll || (Math.random() < critChance);
  378. if (player.goldenNextRoll) player.goldenNextRoll = false;
  379.  
  380. let critMult = applyRelicEffects(player, CRIT_MULTIPLIER);
  381. let gained = Math.floor(totalRoll * effectiveMultiplier * prestigeMult);
  382. if (isCrit) {
  383. gained = Math.floor(gained * critMult);
  384. player.meta.crits = (player.meta.crits || 0) + 1;
  385. }
  386. player.points += gained;
  387. player.meta.rolls = (player.meta.rolls || 0) + 1;
  388.  
  389. // decrement temporary item-based counters
  390. if (player.ironDiceRolls > 0) player.ironDiceRolls = Math.max(0, player.ironDiceRolls - 1);
  391. if (player.luckyCharmRolls > 0) player.luckyCharmRolls = Math.max(0, player.luckyCharmRolls - 1);
  392. if (player.brokenDiceTurns > 0) player.brokenDiceTurns = Math.max(0, player.brokenDiceTurns - 1);
  393.  
  394. // build message
  395. let msg = `${nick} rolled ${d1} & ${d2}` + (player.extraDie ? ` & ${d3}` : "") +
  396. ` (+${effectiveDiceBonus} each) → ${b1} & ${b2}` + (player.extraDie ? ` & ${b3}` : "") +
  397. `. Total ${totalRoll}. Mult: x${effectiveMultiplier}; P mult: x${prestigeMult.toFixed(2)}.`;
  398. if (isCrit) msg += ` CRIT! x${critMult} applied.`;
  399. msg += ` Gained ${gained}. Points: ${player.points}.`;
  400. w.chat.send(msg);
  401.  
  402. // Random events
  403. const eventChance = RANDOM_EVENT_CHANCE_BASE * (1 + (player.permEventBoost || 0));
  404. if (Math.random() < eventChance) {
  405. const ev = randRange(0,3);
  406. switch(ev) {
  407. case 0: {
  408. const bonus = Math.max(1, Math.floor(player.points * LUCKY_PERCENT));
  409. player.points += bonus;
  410. w.chat.send(`🌟 LUCKY FIND! +${bonus} points. New total: ${player.points}.`);
  411. break;
  412. }
  413. case 1: {
  414. player.points += JACKPOT_FLAT;
  415. w.chat.send(`🎰 JACKPOT! +${JACKPOT_FLAT} points. New total: ${player.points}.`);
  416. break;
  417. }
  418. case 2: {
  419. player.goldenNextRoll = true;
  420. w.chat.send(`✨ GOLDEN ROLL! Next roll guaranteed crit.`);
  421. break;
  422. }
  423. case 3: {
  424. player.brokenDiceTurns = Math.max(player.brokenDiceTurns, 1);
  425. w.chat.send(`⚠️ BROKEN DICE! -1 dice bonus for next roll.`);
  426. break;
  427. }
  428. }
  429. }
  430.  
  431. // Item drops
  432. const itemDropChance = ITEM_DROP_CHANCE_BASE * (1 + (player.permItemBoost || 0));
  433. if (Math.random() < itemDropChance) {
  434. const item = weightedItemDrop();
  435. if (item) {
  436. addItemToInventory(player, item.id, 1);
  437. w.chat.send(`📦 ITEM DROP! You found a ${item.name}: ${item.description}. Use =inventory or =use ${item.id}.`);
  438. }
  439. }
  440. break;
  441. }
  442.  
  443. // ----- BUY MULTIPLIER -----
  444. case "buymult": {
  445. const cost = getMultCost(player);
  446. if (player.points < cost) { w.chat.send(`${nick}, need ${cost} points to upgrade multiplier.`); break; }
  447. player.points -= cost;
  448. player.multiplier++;
  449. w.chat.send(`${nick} upgraded multiplier to x${player.multiplier}. Points left: ${player.points}`);
  450. break;
  451. }
  452.  
  453. // ----- BUY DICE -----
  454. case "buydice": {
  455. const cost = getDiceCost(player);
  456. if (player.points < cost) { w.chat.send(`${nick}, need ${cost} points to upgrade dice bonus.`); break; }
  457. player.points -= cost;
  458. player.diceBonus++;
  459. w.chat.send(`${nick} upgraded dice bonus to +${player.diceBonus}. Points left: ${player.points}`);
  460. break;
  461. }
  462.  
  463. // ----- BUY THIRD DIE -----
  464. case "buy3rd": {
  465. if (player.extraDie) { w.chat.send(`${nick}, third die already unlocked.`); break; }
  466. const cost = getThirdDieCost(player);
  467. if (player.points < cost) { w.chat.send(`${nick}, need ${cost} points to unlock third die.`); break; }
  468. player.points -= cost;
  469. player.extraDie = 1;
  470. w.chat.send(`${nick} unlocked the THIRD DIE! Points left: ${player.points}`);
  471. break;
  472. }
  473.  
  474. // ----- BUY CRIT UPGRADE -----
  475. case "buycrit": {
  476. const cost = getCritUpgradeCost(player);
  477. if (player.points < cost) { w.chat.send(`${nick}, need ${cost} points to buy crit upgrade.`); break; }
  478. player.points -= cost;
  479. player.critUpgrades++;
  480. w.chat.send(`${nick} bought crit upgrade. Crit chance: ${(getCritChance(player)*100).toFixed(2)}%. Points left: ${player.points}`);
  481. break;
  482. }
  483.  
  484. // ----- RISK -----
  485. case "risk": {
  486. const now = Date.now();
  487. const timeLeft = player.lastRisk + RISK_COOLDOWN_MS - now;
  488. if (timeLeft > 0) { const s = (timeLeft/1000).toFixed(1); w.chat.send(`${nick}, wait ${s}s. cooldown > 25 sec.`); break; }
  489. player.lastRisk = now;
  490.  
  491. const d1 = randRange(1,6);
  492. const d2 = randRange(1,6);
  493. const d3 = player.extraDie ? randRange(1,6) : 0;
  494. const effectiveDiceBonus = Math.max(0, player.diceBonus - (player.brokenDiceTurns>0?1:0) + (player.permDice||0));
  495. const b1 = Math.max(1, d1 + effectiveDiceBonus);
  496. const b2 = Math.max(1, d2 + effectiveDiceBonus);
  497. const b3 = player.extraDie ? Math.max(1, d3 + effectiveDiceBonus) : 0;
  498. const total = b1 + b2 + b3;
  499. const effMulti = player.multiplier + (player.permMult||0) + (player.goldenSpheres||0) + relicsGrantMultiplier(player);
  500. const baseGain = Math.floor(total * effMulti * (1 + player.prestige * PRESTIGE_BONUS));
  501. if (Math.random() < 0.5) {
  502. const gain = baseGain * 2;
  503. player.points += gain;
  504. w.chat.send(`${nick} <start #00A368>RISK WIN!<end> +${gain} points. Total: ${player.points}.`);
  505. } else {
  506. const loss = Math.floor(player.points * RISK_LOSS_PERCENT);
  507. player.points = Math.max(0, player.points - loss);
  508. w.chat.send(`${nick} <start #BE0039>RISK LOSS!<end> -${loss} points. Total: ${player.points}.`);
  509. }
  510. break;
  511. }
  512.  
  513. // ----- STEAL (NEW) -----
  514. case "steal": {
  515. const targetNick = args[1];
  516. if (!targetNick || targetNick.toLowerCase() === nick.toLowerCase()) {
  517. w.chat.send(`${nick}, usage: =steal <targetNick>`);
  518. break;
  519. }
  520.  
  521. const target = getPlayer(targetNick);
  522. if (!target) {
  523. w.chat.send(`${nick}, target player ${targetNick} not found.`);
  524. break;
  525. }
  526.  
  527. // Cooldown check
  528. const now = Date.now();
  529. const timeLeft = player.lastSteal + STEAL_COOLDOWN_MS - now;
  530. if (timeLeft > 0) {
  531. const s = (timeLeft / 1000).toFixed(1);
  532. w.chat.send(`${nick}, wait ${s}s before attempting another steal.`);
  533. break;
  534. }
  535. player.lastSteal = now;
  536.  
  537. // Target points check
  538. if (target.points < 1) { // allow stealing if target has at least 1 point
  539. w.chat.send(`${nick} failed to steal from ${targetNick}: Target has too few points.`);
  540. break;
  541. }
  542.  
  543. // Steal attempt
  544. if (Math.random() < STEAL_SUCCESS_CHANCE) {
  545. // Success — no cap
  546. const stolen = randRange(1, target.points); // steal up to all points
  547. target.points = Math.max(0, target.points - stolen);
  548. player.points += stolen;
  549. w.chat.send(`<start #00A368>SUCCESS!<end> ${nick} stole ${stolen} points from ${targetNick}. ${nick} now has ${player.points}.`);
  550. } else {
  551. // Failure - scaled penalty
  552. const penaltyPercent = 0.15; // lose 15% of own points on failure
  553. const penalty = Math.max(1, int(player.points * penaltyPercent));
  554. player.points = Math.max(0, player.points - penalty);
  555. w.chat.send(`<start #BE0039>FAILURE!<end> ${nick} was caught trying to steal from ${targetNick} and lost ${penalty} points as a fine. Total: ${player.points}.`);
  556. }
  557. break;
  558. }
  559.  
  560.  
  561. // ----- GIVE (NEW) -----
  562. case "give": {
  563. const targetNick = args[1];
  564. const amountStr = args[2];
  565. const amount = parseInt(amountStr, 10);
  566.  
  567. if (!targetNick || isNaN(amount) || amount <= 0) {
  568. w.chat.send(`${nick}, usage: =give <targetNick> <amount>`);
  569. break;
  570. }
  571.  
  572. if (targetNick.toLowerCase() === nick.toLowerCase()) {
  573. w.chat.send(`${nick}, you can't give points to yourself.`);
  574. break;
  575. }
  576.  
  577. const target = getPlayer(targetNick);
  578. if (!target) {
  579. w.chat.send(`${nick}, target player ${targetNick} not found.`);
  580. break;
  581. }
  582.  
  583. if (player.points < amount) {
  584. w.chat.send(`${nick}, you only have ${player.points} points. You can't give ${amount}.`);
  585. break;
  586. }
  587.  
  588. player.points -= amount;
  589. target.points += amount;
  590. w.chat.send(`${nick} gave ${amount} points to ${targetNick}. ${nick} left with ${player.points}.`);
  591. break;
  592. }
  593.  
  594. // ----- PRESTIGE -----
  595. case "prestige": {
  596. if (player.points < PRESTIGE_REQUIREMENT) { w.chat.send(`${nick}, need ${PRESTIGE_REQUIREMENT} points to prestige.`); break; }
  597. player.prestige++;
  598. player.pp = (player.pp || 0) + 1; // Option A: +1 PP per prestige
  599. // core reset but preserve perm upgrades, pp, relics, inventory
  600. player.points = 0;
  601. player.multiplier = 1;
  602. player.diceBonus = 0;
  603. player.extraDie = 0;
  604. player.critUpgrades = 0;
  605. player.goldenNextRoll = false;
  606. player.brokenDiceTurns = 0;
  607. player.lastRoll = 0;
  608. player.lastRisk = 0; // Reset risk cooldown
  609. player.lastSteal = 0; // Reset steal cooldown
  610. w.chat.send(`${nick} PRESTIGED! Level: ${player.prestige}. +1 PP (total: ${player.pp}).`);
  611. break;
  612. }
  613.  
  614. // ----- REBIRTH (multi-prestige rebirths) -----
  615. case "rebirth": {
  616. // requires at least 10 prestige levels to convert 10 prestige into 1 rebirth (example)
  617. if (player.prestige < 10) { w.chat.send(`${nick}, you need 10 prestige levels to rebirth.`); break; }
  618. // consume 10 prestige levels -> +1 rebirth, reset prestige (but keep PP and perm upgrades)
  619. player.prestige -= 10;
  620. player.rebirths = (player.rebirths || 0) + 1;
  621. // apply rebirth permanent bonus: +5% global multiplier boost stored in permMultFraction maybe, we use permMult
  622. player.permMult = (player.permMult || 0) + 1; // simple powerful bonus
  623. w.chat.send(`${nick} performed a REBIRTH! Rebirths: ${player.rebirths}. Permanent +1 multiplier granted.`);
  624. break;
  625. }
  626.  
  627. // ----- SHOP (rotating + auto-unlock per prestige) -----
  628. case "shop": {
  629. const avail = getAvailableShopForPlayer(player);
  630. if (!avail || avail.length === 0) { w.chat.send("Prestige shop is empty."); break; }
  631. const lines = ["=== PRESTIGE SHOP ==="];
  632. for (const it of avail) {
  633. lines.push(`ID:${it.id} Cost:${it.cost} PP — ${it.name} — ${it.desc}`);
  634. }
  635. w.chat.send(lines.join(" | "));
  636. break;
  637. }
  638.  
  639. // ----- BUY (PP) -----
  640. case "buy": {
  641. const id = args[1];
  642. if (!id) { w.chat.send(`${nick}, usage: !buy <id>`); break; }
  643. // search in available pool (so players can't buy items locked by prestige)
  644. const avail = getAvailableShopForPlayer(player);
  645. const item = avail.find(it => it.id === id) || PRESTIGE_SHOP_POOL.find(it => it.id === id);
  646. if (!item) { w.chat.send(`${nick}, item ${id} not found.`); break; }
  647. if ((player.pp || 0) < item.cost) { w.chat.send(`${nick}, need ${item.cost} PP. You have ${player.pp || 0}.`); break; }
  648. player.pp -= item.cost;
  649. // apply effects
  650. const eff = item.effect || {};
  651. if (eff.permMult) player.permMult = (player.permMult || 0) + eff.permMult;
  652. if (eff.permCrit) player.permCrit = (player.permCrit || 0) + eff.permCrit;
  653. if (eff.permDice) player.permDice = (player.permDice || 0) + eff.permDice;
  654. if (eff.permEventBoost) player.permEventBoost = (player.permEventBoost || 0) + eff.permEventBoost;
  655. if (eff.permItemBoost) player.permItemBoost = (player.permItemBoost || 0) + eff.permItemBoost;
  656. if (eff.relic) {
  657. // add relic to player.relics
  658. player.relics = player.relics || {};
  659. player.relics[eff.relic] = true;
  660. player.relics[eff.relic + "_bought_at"] = Date.now();
  661. }
  662. w.chat.send(`${nick} purchased ${item.name} for ${item.cost} PP.`);
  663. break;
  664. }
  665.  
  666. // ----- RELICS LIST -----
  667. case "relics": {
  668. const owned = Object.keys(player.relics || {}).filter(k => !k.endsWith('_bought_at'));
  669. if (!owned.length) { w.chat.send(`${nick}, you own no relics.`); break; }
  670. w.chat.send(`${nick} relics: ${owned.join(", ")}`);
  671. break;
  672. }
  673.  
  674. // ----- SHOP ROTATE (admin) -----
  675. case "rotateshop": {
  676. // admin command sometimes - here anyone can trigger rotation
  677. rotateShopSeed();
  678. w.chat.send("Shop rotation updated.");
  679. break;
  680. }
  681.  
  682. // ----- SLOTS (slot machine) -----
  683. case "slots":
  684. case "slot": {
  685. // cost to play
  686. if ((player.points || 0) < SLOTS_COST) { w.chat.send(`${nick}, ${SLOTS_COST} points required to play slots.`); break; }
  687. player.points -= SLOTS_COST;
  688. const s1 = SLOTS_SYMBOLS[Math.floor(Math.random() * SLOTS_SYMBOLS.length)];
  689. const s2 = SLOTS_SYMBOLS[Math.floor(Math.random() * SLOTS_SYMBOLS.length)];
  690. const s3 = SLOTS_SYMBOLS[Math.floor(Math.random() * SLOTS_SYMBOLS.length)];
  691. let payout = 0;
  692. if (s1 === s2 && s2 === s3) payout = SLOTS_PAYOUTS[3];
  693. else if (s1 === s2 || s1 === s3 || s2 === s3) payout = SLOTS_PAYOUTS[2];
  694. // super rare jackpot if all three are '6'
  695. if (s1 === 6 && s2 === 6 && s3 === 6) payout = SLOTS_PAYOUTS["jackpot"];
  696. if (payout > 0) { player.points += payout; w.chat.send(`${nick} spun ${s1}-${s2}-${s3} and won ${payout} points! Total: ${player.points}.`); }
  697. else w.chat.send(`${nick} spun ${s1}-${s2}-${s3}. No win.`); 
  698. break;
  699. }
  700.  
  701. // ----- LEADERBOARDS -----
  702. case "top":
  703. case "toppoints": {
  704. const top = topPlayersByPoints(5);
  705. if (!top.length) { w.chat.send("No players yet."); break; }
  706. const lines = top.map((t,i) => `${i+1}. ${t.nick}: ${t.points}`);
  707. w.chat.send("Top Points — " + lines.join(" | "));
  708. break;
  709. }
  710. case "topprestige": {
  711. const top = topPlayersByPrestige(5);
  712. if (!top.length) { w.chat.send("No players yet."); break; }
  713. const lines = top.map((t,i) => `${i+1}. ${t.nick}: Prestige ${t.prestige}`);
  714. w.chat.send("Top Prestige — " + lines.join(" | "));
  715. break;
  716. }
  717.  
  718. // ----- INVENTORY & ITEMS -----
  719. case "items": {
  720. const list = ITEM_POOL.map(it => `${it.id} — ${it.name}: ${it.description}`).join(" | ");
  721. w.chat.send(`Available items: ${list}`);
  722. break;
  723. }
  724. case "inventory": {
  725. const inv = player.inventory;
  726. if (!inv || Object.keys(inv).length === 0) { w.chat.send(`${nick}, your inventory is empty.`); break; }
  727. const parts = Object.entries(inv).map(([id,c]) => {
  728. const def = getItemDefById(id);
  729. return `${def ? def.name : id} x${c}`;
  730. });
  731. w.chat.send(`${nick}'s Inventory: ${parts.join(" — ")}`);
  732. break;
  733. }
  734. case "use": {
  735. const itemId = args[1];
  736. if (!itemId) { w.chat.send(`${nick}, usage: !use <itemId>`); break; }
  737. const def = getItemDefById(itemId);
  738. if (!def) { w.chat.send(`${nick}, unknown item ${itemId}.`); break; }
  739. if (!player.inventory[itemId] || player.inventory[itemId] <= 0) { w.chat.send(`${nick}, you don't have ${def.name}.`); break; }
  740. removeItemFromInventory(player, itemId, 1);
  741. switch (itemId) {
  742. case "lucky_charm":
  743. player.luckyCharmRolls += LUCKY_CHARM_ROLLS;
  744. w.chat.send(`${nick} used Lucky Charm — +${(LUCKY_CHARM_CRIT_BONUS*100).toFixed(0)}% crit for ${LUCKY_CHARM_ROLLS} rolls.`);
  745. break;
  746. case "iron_dice":
  747. player.ironDiceRolls += IRON_DICE_ROLLS;
  748. w.chat.send(`${nick} used Iron Dice — +${IRON_DICE_BONUS} dice bonus for ${IRON_DICE_ROLLS} rolls.`);
  749. break;
  750. case "wind_token":
  751. player.lastRoll = 0;
  752. w.chat.send(`${nick} used Wind Token — roll cooldown reset. Use !roll now.`);
  753. break;
  754. case "golden_sphere":
  755. player.goldenSpheres = (player.goldenSpheres || 0) + 1;
  756. w.chat.send(`${nick} used Golden Sphere — permanent +1 multiplier acquired.`);
  757. break;
  758. default:
  759. w.chat.send(`${nick} used ${def.name}.`);
  760. }
  761. break;
  762. }
  763.  
  764. // ----- STATS -----
  765. case "stats": {
  766. const prestigeMult = (1 + player.prestige * PRESTIGE_BONUS + (player.goldenSpheres||0)).toFixed(2);
  767. const critPct = (getCritChance(player) * 100).toFixed(2);
  768. w.chat.send(`${nick}'s Stats — Points:${player.points}, Mult:x${player.multiplier}+${player.permMult}, Dice:+${player.diceBonus}+${player.permDice}, Prestige:${player.prestige} (x${prestigeMult}), PP:${player.pp||0}, Rebirths:${player.rebirths||0}, Crit:${critPct}%`);
  769. break;
  770. }
  771.  
  772. // ----- SAVE / LOAD / EXPORT (UPDATED) -----
  773. case "save": {
  774. if (!isAdmin(nick)) {
  775. w.chat.send(`${nick}, you don't have permission to save the game.`);
  776. break;
  777. }
  778. const ok = savePlayersToDisk();
  779. if (ok) { w.chat.send("Save successful."); }
  780. else if (!fs && typeof localStorage === "undefined") { w.chat.send("Save not available in this environment."); }
  781. else { w.chat.send("Save failed (check server logs/console)."); }
  782. break;
  783. }
  784.  
  785. case "load": {
  786. if (!isAdmin(nick)) {
  787. w.chat.send(`${nick}, you don't have permission to load the game.`);
  788. break;
  789. }
  790. const ok = loadPlayersFromDisk();
  791. if (ok) { w.chat.send("Load successful."); }
  792. else if (!fs && typeof localStorage === "undefined") { w.chat.send("Load not available in this environment."); }
  793. else { w.chat.send("Load failed or no save found."); }
  794. break;
  795. }
  796. // ----- QUIT (permanent self-delete) -----
  797. case "quit": {
  798. const confirm = args[1];
  799. if (confirm !== "confirm") {
  800. w.chat.send(`${nick}, type "=quit confirm" to permanently delete your data.`);
  801. break;
  802. }
  803. if (!players[nick]) {
  804. w.chat.send(`${nick}, you are not in the game.`);
  805. break;
  806. }
  807.  
  808. // Remove from memory
  809. delete players[nick];
  810.  
  811. // Save updated players to disk / localStorage
  812. const ok = savePlayersToDisk();
  813. if (!ok) console.warn(`Failed to save after ${nick} quit.`);
  814.  
  815. w.chat.send(`${nick} has quit the game permanently. ${nick}'s data deleted.`);
  816. break;
  817. }
  818.  
  819. // ----- REMOVE PLAYER (admin-only) -----
  820. case "remove": {
  821. if (!isAdmin(nick)) {
  822. w.chat.send(`${nick}, you don't have permission to remove players.`);
  823. break;
  824. }
  825.  
  826. const targetNick = args[1];
  827. if (!targetNick) {
  828. w.chat.send(`${nick}, usage: =remove <playerNick>`);
  829. break;
  830. }
  831.  
  832. if (!players[targetNick]) {
  833. w.chat.send(`${nick}, player ${targetNick} not found.`);
  834. break;
  835. }
  836.  
  837. // Remove the target player from memory
  838. delete players[targetNick];
  839.  
  840. // Save updated players to disk / localStorage
  841. const ok = savePlayersToDisk();
  842. if (!ok) console.warn(`Failed to save after ${nick} removed ${targetNick}.`);
  843.  
  844. w.chat.send(`Player ${targetNick} has been permanently removed from the game by ${nick}.`);
  845. break;
  846. }
  847.  
  848. case "export": {
  849. const path = args[1] || SAVE_PATH;
  850. const result = exportPlayers(path);
  851.  
  852. if (typeof result === 'string') {
  853. // LocalStorage / Browser environment export
  854. w.chat.send(`Exported Player Data: Copy the following JSON string to save your game manually: ${result}`);
  855. } else if (result === true) {
  856. // FS environment export
  857. w.chat.send(`Exported save to ${path}`);
  858. } else {
  859. w.chat.send("Export failed.");
  860. }
  861. break;
  862. }
  863.  
  864. // ----- HELP -----
  865. case "help": {
  866. w.chat.send("Commands: =roll =stats =buymult =buydice =buy3rd =buycrit =risk =prestige =rebirth =shop =buy <id> =slots =items =inventory =use <id> **=steal <nick> =give <nick> <amt>** =toppoints =topprestige =save =load =export");
  867. break;
  868. }
  869.  
  870. default:
  871. // unknown command — ignore silently to match previous behavior
  872. break;
  873. }
  874. });
  875.  
  876. // -----------------------------
  877. // End of script
  878. // -----------------------------
Add Comment
Please, Sign In to add comment