Files
primal-rampage/js/save.js
T
Primal Rampage Bot 2024ea48b5 Primal Rampage — prehistoric survival wave shooter
Vanilla JS + Canvas 2D, zero dependencies.

Gameplay: campaign waves vs 12 dino species + 3 bosses, arena mode,
7 weapons, hero talents, pets, ult/bombs, card roguelite picks.
World: day/night cycle, weather fronts (rain+lightning), Blood Moon
weekends, 3 act biomes (jungle -> swamp -> ashlands with lava veins).
Meta: gems economy, upgrade tree, quests, bestiary, achievements,
shop (weapons/heroes/skins incl. premium), revive, ad-cap system,
endless scaling past wave 25, live-painted shop/bestiary icons,
pro main menu, auto-aim toggle (F). Headless smoke suite included.
2026-08-23 04:26:58 +00:00

206 lines
6.5 KiB
JavaScript

/* Primal Rampage — profile & persistence v2 (localStorage).
v2 adds: gems (hard currency), permanent upgrades, bestiary kills,
daily quests + login streak, achievements, lifetime stats, pet,
tutorial flag, rewarded-ad daily cap, shake amount + quality setting.
Server-backed leaderboards/accounts plug in later via US.net. */
(function () {
'use strict';
var KEY = 'neonrampage.profile.v1';
var US = (window.US = window.US || {});
var DEFAULTS = {
name: 'Rookie',
lang: 'en',
coins: 0,
bombs: 1,
weapons: ['pistol'],
heroes: ['recruit'],
skins: ['default'],
weapon: 'pistol',
hero: 'recruit',
skin: 'default',
bestScore: 0,
bestWave: 0,
// --- v2 ---
gems: 0, // hard currency "ngọn lửa"
upgrades: {}, // id -> level
bestiary: {}, // speciesId -> {kills, done}
quests: { date: '', items: [], lastDay: '', streak: 0 },
achievements: {}, // id -> true
stats: { totalKills: 0, totalAmber: 0, runs: 0 },
pet: null, // {name, color} once hatched
tut: false,
adDay: { date: '', count: 0 }, // rewarded ads watched today (cap 3)
settings: {
sfx: 0.8, music: 0.5, autofire: true, autoAim: false, onehit: false,
shake: true, shakeAmt: 1, quality: 'high'
},
board: [] // [{name,score,wave,ts}]
};
function clone(o) { return JSON.parse(JSON.stringify(o)); }
function isPlain(o) { return o && typeof o === 'object' && !(o instanceof Array); }
// deep-merge stored data over defaults so new fields appear after updates
function mergeDefaults(dst, def) {
Object.keys(def).forEach(function (k) {
if (!(k in dst)) dst[k] = clone(def[k]);
else if (isPlain(def[k]) && isPlain(dst[k])) mergeDefaults(dst[k], def[k]);
});
return dst;
}
var state = null;
function load() {
try {
var raw = localStorage.getItem(KEY);
state = raw ? mergeDefaults(JSON.parse(raw), DEFAULTS) : clone(DEFAULTS);
state.settings = Object.assign(clone(DEFAULTS.settings), state.settings || {});
state.stats = Object.assign(clone(DEFAULTS.stats), state.stats || {});
state.quests = Object.assign(clone(DEFAULTS.quests), state.quests || {});
state.adDay = Object.assign(clone(DEFAULTS.adDay), state.adDay || {});
if (!state.weapons.length) state.weapons = ['pistol'];
if (!state.heroes.length) state.heroes = ['recruit'];
if (!state.skins.length) state.skins = ['default'];
} catch (e) {
state = clone(DEFAULTS);
}
return state;
}
US.save = {
get: function () { return state || load(); },
save: function () {
try { localStorage.setItem(KEY, JSON.stringify(state)); } catch (e) { /* private mode */ }
},
reset: function () {
state = clone(DEFAULTS);
US.save.save();
},
addCoins: function (n) {
state.coins = Math.max(0, (state.coins | 0) + n);
US.save.save();
},
/* ---------- hard currency ---------- */
addGems: function (n) {
state.gems = Math.max(0, (state.gems | 0) + n);
US.save.save();
},
spendGems: function (n) {
if ((state.gems | 0) < n) return false;
state.gems -= n;
US.save.save();
return true;
},
ownWeapon: function (id) {
if (state.weapons.indexOf(id) < 0) { state.weapons.push(id); US.save.save(); }
},
ownHero: function (id) {
if (state.heroes.indexOf(id) < 0) { state.heroes.push(id); US.save.save(); }
},
ownSkin: function (id) {
if (state.skins.indexOf(id) < 0) { state.skins.push(id); US.save.save(); }
},
equip: function (slot, id) {
state[slot] = id;
US.save.save();
},
/* ---------- permanent upgrades ---------- */
upLevel: function (id) { return state.upgrades[id] | 0; },
buyUpgrade: function (id, cost) {
var lv = state.upgrades[id] | 0;
if ((state.coins | 0) < cost) return false;
state.coins -= cost;
state.upgrades[id] = lv + 1;
US.save.save();
return true;
},
/* ---------- bestiary ---------- */
bumpBestiary: function (speciesId, n) {
var e = state.bestiary[speciesId];
if (!e) e = state.bestiary[speciesId] = { kills: 0, done: false };
e.kills += (n || 1);
US.save.save();
return e;
},
bestiaryDone: function (speciesId, rewardFn) {
var e = state.bestiary[speciesId];
if (e && !e.done) {
e.done = true;
if (rewardFn) rewardFn();
US.save.save();
return true; // newly completed
}
return false;
},
/* ---------- lifetime stats ---------- */
stat: function (k, n) {
if (n === undefined) return state.stats[k] | 0;
state.stats[k] = (state.stats[k] | 0) + n;
US.save.save();
return state.stats[k];
},
/* ---------- achievements ---------- */
hasAch: function (id) { return !!state.achievements[id]; },
unlockAch: function (id) {
if (state.achievements[id]) return false;
state.achievements[id] = true;
US.save.save();
return true;
},
/* ---------- rewarded ads (stub; swap with real SDK later) ---------- */
ADS_DAILY_CAP: 3,
canWatchAd: function () {
var today = US.save.today();
if (state.adDay.date !== today) { state.adDay = { date: today, count: 0 }; }
return state.adDay.count < US.save.ADS_DAILY_CAP;
},
consumeAd: function () {
var today = US.save.today();
if (state.adDay.date !== today) state.adDay = { date: today, count: 0 };
state.adDay.count++;
US.save.save();
},
/* ---------- live-ops ---------- */
isWeekend: function () {
var d = new Date().getDay();
return d === 0 || d === 6;
},
/* ---------- misc ---------- */
today: function () {
var d = new Date();
return d.getFullYear() + '-' +
('0' + (d.getMonth() + 1)).slice(-2) + '-' +
('0' + d.getDate()).slice(-2);
},
submitRun: function (score, wave) {
var isRecord = score > state.bestScore;
if (isRecord) state.bestScore = Math.floor(score);
if (wave > state.bestWave) state.bestWave = wave;
state.board.push({ name: state.name || 'Rookie', score: Math.floor(score), wave: wave, ts: Date.now() });
state.board.sort(function (a, b) { return b.score - a.score; });
state.board = state.board.slice(0, 10);
US.save.save();
// Plug-in point for a real server leaderboard:
// if (US.net && US.net.submitRun) US.net.submitRun(state.board[0]);
return isRecord;
}
};
load();
})();