react-incremental-library
Version:
A React library for incremental, idle, and clicker games. Build high-performance, engaging experiences with minimal setup.
3 lines (2 loc) • 15.2 kB
JavaScript
import R,{useState as b,useRef as v,useCallback as y,useEffect as k,useMemo as I}from"react";function K(e){const{tickRate:s=1e3,startPaused:t=!1,maxTickTime:o=16,catchupThreshold:r=10,onTick:n,debugMode:c=!1}=e||{},[i,d]=b(0),[a,u]=b(0),[l,m]=b(t),[g,f]=b(0),p=v(null),S=v(null),P=v(0),h=y(T=>{if(S.current!==null){const D=1e3/(T-S.current);f(D)}if(S.current=T,l){P.current=requestAnimationFrame(h);return}p.current===null&&(p.current=T);let C=T-p.current;if(C>=s){const D=Math.min(Math.floor(C/s),r);for(let A=0;A<D;A++){const L=Math.min(s,o);n&&n(L),u(L),d(J=>J+1),c&&console.log(`[GameLoop Debug] Tick ${i+A+1} processed with delta: ${L}ms`)}p.current=p.current+D*s}P.current=requestAnimationFrame(h)},[r,l,o,n,s,c,i]);k(()=>(S.current=performance.now(),P.current=requestAnimationFrame(h),()=>{cancelAnimationFrame(P.current),p.current=null,S.current=null}),[h]);const M=y(()=>{m(!0),c&&console.log("[GameLoop Debug] Paused")},[c]),N=y(()=>{m(!1),p.current=performance.now(),c&&console.log("[GameLoop Debug] Resumed")},[c]),q=y(()=>{m(T=>{const C=!T;return c&&console.log(`[GameLoop Debug] Toggled pause. New state: ${C?"Paused":"Running"}`),C||(p.current=performance.now()),C})},[c]),$=y(()=>{d(0),c&&console.log("[GameLoop Debug] Tick counter reset")},[c]);return{tick:i,deltaTime:a,isPaused:l,fps:g,togglePause:q,pause:M,resume:N,resetTick:$}}const H=({interval:e,callback:s,enabled:t=!0})=>{const o=v(s);k(()=>{o.current=s},[s]),k(()=>{if(!t)return;const r=setInterval(()=>{o.current()},e);return()=>clearInterval(r)},[e,t])},F=e=>{let s;const t=new Set,o=(a,u)=>{const l=typeof a=="function"?a(s):a;if(!Object.is(l,s)){const m=s;s=u??(typeof l!="object"||l===null)?l:Object.assign({},s,l),t.forEach(g=>g(s,m))}},r=()=>s,i={setState:o,getState:r,getInitialState:()=>d,subscribe:a=>(t.add(a),()=>t.delete(a))},d=s=e(o,r,i);return i},z=e=>e?F(e):F,Q=e=>e;function W(e,s=Q){const t=R.useSyncExternalStore(e.subscribe,()=>s(e.getState()),()=>s(e.getInitialState()));return R.useDebugValue(t),t}const X=e=>{const s=z(e),t=o=>W(s,o);return Object.assign(t,s),t},O=e=>X,Y=(e,s={})=>{const{enabled:t=!0,logLevel:o="debug",logPrefix:r="store update",filter:n=()=>!0}=s;return(c,i,d)=>e((...a)=>{if(!t){c(...a);return}const u=i();c(...a);const l=i();let m="unknown";const g=a[0];if(typeof g=="function"?m=g.name||"anonymous":g&&typeof g=="object"&&(m=Object.keys(g).join(", ")||"state update"),!n(m))return;const f=`${r}: ${m}`;o==="debug"?(console.group(f),console.debug("prev state:",u),console.debug("next state:",l),console.debug("changed state:",g),console.groupEnd()):o==="info"?console.info(f,{prev:u,next:l,changed:g}):o==="warn"?console.warn(f,{prev:u,next:l,changed:g}):o==="error"&&console.error(f,{prev:u,next:l,changed:g})},i,d)},Z=(e,s={})=>{const{enabled:t=!0,logPrefix:o="performance"}=s;return(r,n,c)=>e((...i)=>{if(!t){r(...i);return}const d=window.performance.now();r(...i);const a=window.performance.now()-d;a>5&&console.warn(`${o}: State update took ${a.toFixed(2)}ms`)},n,c)},E=(e={})=>s=>{let t=s;return e.performance&&(t=Z(t,e.performance)),e.logger&&(t=Y(t,e.logger)),t},x="achievements",_=e=>{const s=E(e);return O()(s((t,o)=>({achievements:(()=>{try{const r=JSON.parse(localStorage.getItem(x)||"{}"),n={};return Object.entries(r).forEach(([c,i])=>{typeof i=="boolean"?n[c]={unlocked:i,progress:i?1:0}:n[c]=i}),n}catch{return{}}})(),register:(r,n={})=>t(c=>{if(r in c.achievements)return c;const i={...c.achievements,[r]:{unlocked:!1,progress:0,metadata:n}};return localStorage.setItem(x,JSON.stringify(i)),{achievements:i}}),updateProgress:(r,n)=>t(c=>{const i=Math.min(1,Math.max(0,n)),d=c.achievements[r];if(i<=d.progress||d.unlocked)return c;const a={...c.achievements,[r]:{...d,progress:i,unlocked:i>=1?!0:d.unlocked}};return localStorage.setItem(x,JSON.stringify(a)),{achievements:a}}),unlock:r=>t(n=>{if(!(r in n.achievements))return o().register(r),{achievements:o().achievements};if(n.achievements[r].unlocked)return n;const c={...n.achievements,[r]:{...n.achievements[r],unlocked:!0,progress:1}};return localStorage.setItem(x,JSON.stringify(c)),{achievements:c}}),isUnlocked:r=>{const n=o().achievements[r];return n?n.unlocked:!1},getProgress:r=>{const n=o().achievements[r];return n?n.progress:0},updateMetadata:(r,n)=>t(c=>{if(!(r in c.achievements))return o().register(r,n),{achievements:o().achievements};const i={...c.achievements,[r]:{...c.achievements[r],metadata:{...c.achievements[r].metadata,...n}}};return localStorage.setItem(x,JSON.stringify(i)),{achievements:i}}),reset:()=>{localStorage.removeItem(x),t({achievements:{}})}})))},U=_(),ee=e=>{const s=E(e);return O()(s((t,o)=>({currency:0,currencyName:"",maxValue:void 0,minValue:void 0,precision:2,format:void 0,setCurrencyName:r=>t({currencyName:r}),setMaxValue:r=>t({maxValue:r}),setMinValue:r=>t({minValue:r}),setPrecision:r=>t({precision:Math.max(0,Math.floor(r))}),setFormat:r=>t({format:r}),increase:r=>t(n=>{const c=Number((n.currency+r).toFixed(n.precision));return{currency:n.maxValue!==void 0?Math.min(c,n.maxValue):c}}),decrease:r=>t(n=>{const c=Number((n.currency-r).toFixed(n.precision));return{currency:n.minValue!==void 0?Math.max(c,n.minValue):c}}),reset:()=>t({currency:0}),getValue:()=>o().currency,getFormatted:()=>{const r=o();return r.format?r.format(r.currency):r.currency.toFixed(r.precision)}})))},w=ee(),G=O()((e,s)=>({prestigePoints:0,prestigeMultiplier:1,prestigeThreshold:1e3,canPrestige:!1,prestigeResetCallbacks:[],addPrestigePoints:t=>e(o=>({prestigePoints:o.prestigePoints+t,prestigeMultiplier:o.prestigeMultiplier+t*.1})),setPrestigeMultiplier:t=>e({prestigeMultiplier:t}),setPrestigeThreshold:t=>e({prestigeThreshold:t}),registerResetCallback:t=>e(o=>({prestigeResetCallbacks:[...o.prestigeResetCallbacks,t]})),prestige:()=>{const t=s(),o=w.getState();if(o.currency>=t.prestigeThreshold){const r=Math.floor(Math.sqrt(o.currency/t.prestigeThreshold));t.addPrestigePoints(r),t.prestigeResetCallbacks.forEach(n=>n()),o.reset()}},reset:()=>e({prestigePoints:0,prestigeMultiplier:1,prestigeThreshold:1e3,canPrestige:!1,prestigeResetCallbacks:[]})})),V=e=>e.baseCost*Math.pow(e.costScaling,e.count),j=(e,s)=>{if(!e.dependencies||e.dependencies.length===0)return e.baseProduction*e.count;const t=e.dependencies.reduce((o,r)=>{const n=s[r.producerId];return!n||n.count<r.requiredCount?0:o*(n.count/r.requiredCount)},1);return e.baseProduction*e.count*t},te=e=>{const s=E(e);return O()(s((t,o)=>({producers:{},lastUpdate:Date.now(),addProducer:r=>t(n=>({producers:{...n.producers,[r.id]:{...r,count:0}}})),buyProducer:r=>{const n=o().producers[r],c=w.getState();if(!n)return!1;const i=V(n);return c.currency<i?!1:(c.decrease(i),t(d=>({producers:{...d.producers,[r]:{...n,count:n.count+1}}})),!0)},updateProduction:r=>{const n=o(),c=w.getState();let i=0;Object.values(n.producers).forEach(a=>{i+=j(a,n.producers)});const d=i*r/1e3;c.increase(d),t({lastUpdate:Date.now()})},getProducerCost:r=>{const n=o().producers[r];return n?V(n):0},getProducerProduction:r=>{const n=o(),c=n.producers[r];return c?j(c,n.producers):0},canBuyProducer:r=>{const n=o().producers[r];return n?w.getState().currency>=V(n):!1},reset:()=>t({producers:{},lastUpdate:Date.now()})})))},re=te(),ne=({interval:e,increaseBy:s,onPause:t,onResume:o,onAutoIncrement:r,options:n})=>{const{increase:c}=w(),i=v(null),d=v(0),a=v(e),u=v(!1),[l,m]=b(n?.startPaused??!1),g=y(()=>{i.current!==null&&(clearTimeout(i.current),i.current=null)},[]),f=y(()=>{d.current=Date.now(),i.current=window.setTimeout(()=>{c(s),a.current=e,f(),r&&r(s)},a.current)},[c,s,e]),p=y(()=>{l&&(m(!1),!u.current&&o&&(o(),u.current=!0),d.current=Date.now(),i.current=window.setTimeout(()=>{c(s),a.current=e,f()},a.current))},[l,c,s,e,o,f]),S=y(()=>{if(l||i.current===null)return!1;g();const P=Date.now()-d.current;return a.current=Math.max(e-P,0),m(!0),u.current=!1,t&&t(),!0},[l,e,t,g]);return k(()=>(n?.startPaused||f(),()=>{g()}),[n?.startPaused,f,g]),{pause:S,resume:p,isPaused:l}},oe=({resourcePerSecond:e})=>{const[s,t]=b(()=>Date.now()),[o,r]=b(0);return k(()=>{const n=localStorage.getItem("lastSession");if(n){const c=(Date.now()-Number(n))/1e3;r(c*e)}t(Date.now())},[]),k(()=>{localStorage.setItem("lastSession",String(s))},[s]),{idleGain:o}},se=O()((e,s)=>({lastOnlineTime:Date.now(),maxOfflineTime:24*60*60*1e3,offlineMultiplier:.5,offlineCallbacks:[],setLastOnlineTime:t=>e({lastOnlineTime:t}),setMaxOfflineTime:t=>e({maxOfflineTime:t}),setOfflineMultiplier:t=>e({offlineMultiplier:t}),registerOfflineCallback:t=>e(o=>({offlineCallbacks:[...o.offlineCallbacks,t]})),calculateOfflineProgress:()=>{const t=s(),o=Date.now(),r=o-t.lastOnlineTime,n=Math.min(r,t.maxOfflineTime)*t.offlineMultiplier;t.offlineCallbacks.forEach(c=>c(n)),t.setLastOnlineTime(o)}})),ce=()=>{const e=se(),s=v(!1);return k(()=>{s.current||(e.calculateOfflineProgress(),s.current=!0);const t=()=>{document.visibilityState==="hidden"?e.setLastOnlineTime(Date.now()):e.calculateOfflineProgress()},o=()=>{e.setLastOnlineTime(Date.now())};return document.addEventListener("visibilitychange",t),window.addEventListener("beforeunload",o),()=>{document.removeEventListener("visibilitychange",t),window.removeEventListener("beforeunload",o)}},[]),{maxOfflineTime:e.maxOfflineTime,offlineMultiplier:e.offlineMultiplier,setMaxOfflineTime:e.setMaxOfflineTime,setOfflineMultiplier:e.setOfflineMultiplier,registerOfflineCallback:e.registerOfflineCallback}},B=({name:e,condition:s,onUnlock:t,progress:o,progressCurrent:r,progressTarget:n,category:c="General",description:i="",icon:d,hidden:a=!1})=>{const{isUnlocked:u,unlock:l}=U(),m=I(()=>o!==void 0?o:r!==void 0&&n!==void 0?Math.min(1,Math.max(0,r/n)):u(e)?1:0,[o,r,n,u,e]);return k(()=>{!u(e)&&s&&(l(e),t&&t())},[s,u,l,e,t]),{unlocked:u(e),progress:m,category:c,description:i,icon:d,hidden:a}},ae=(e,s)=>{const{achievements:t,register:o}=U();k(()=>{e.forEach(({name:a})=>{a in t||o(a)})},[e,t,o]),e.forEach(({name:a,condition:u,onUnlock:l})=>{B({name:a,condition:u,onUnlock:()=>{l&&l(),s?.onAchievementUnlocked&&s.onAchievementUnlocked(a)}})});const r=I(()=>Object.entries(t).map(([a,u])=>{const l=e.find(g=>g.name===a);let m=l?.progress;return l?.progressCurrent!==void 0&&l?.progressTarget!==void 0&&(m=Math.min(1,Math.max(0,l.progressCurrent/l.progressTarget))),{name:a,unlocked:u.unlocked,category:l?.category||"General",description:l?.description||"",icon:l?.icon,hidden:l?.hidden||!1,progress:m||(u.unlocked?1:u.progress)}}),[t,e]),n=a=>{const u=t[a],l=u?.unlocked??!1,m=e.find(f=>f.name===a);let g=m?.progress;return m?.progressCurrent!==void 0&&m?.progressTarget!==void 0&&(g=Math.min(1,Math.max(0,m.progressCurrent/m.progressTarget))),{name:a,unlocked:l,category:m?.category||"General",description:m?.description||"",icon:m?.icon,hidden:m?.hidden||!1,progress:g||(l?1:u?.progress||0)}},c=a=>r.filter(u=>u.category===a),i=I(()=>{const a=new Set;return r.forEach(u=>{u.category&&a.add(u.category)}),Array.from(a)},[r]),d=I(()=>{const a=r.length;return a===0?0:r.filter(u=>u.unlocked).length/a*100},[r]);return{allAchievements:r,getAchievement:n,getAchievementsByCategory:c,categories:i,completionPercentage:d}},ie=()=>{const e=G(),s=w();return s.currency>=e.prestigeThreshold!==e.canPrestige&&G.setState({canPrestige:s.currency>=e.prestigeThreshold}),{prestigePoints:e.prestigePoints,prestigeMultiplier:e.prestigeMultiplier,prestigeThreshold:e.prestigeThreshold,canPrestige:e.canPrestige,prestige:e.prestige,setPrestigeThreshold:e.setPrestigeThreshold,registerResetCallback:e.registerResetCallback,reset:e.reset}},ue=e=>{const[s,t]=b(0),{currency:o,increase:r,decrease:n}=w(),{cost:c}=e,i=a=>{o<Math.floor(c*a)||(n(c*a),t(s+a))},d=a=>{s<a||a!==0&&(r(c*a),t(s-a))};return{handleUpgrade:()=>{e.onUpgrade()},handleBuy:()=>{o<c||(n(c),e.onBuy())},handleBuyAll:()=>{const a=Math.floor(o/c);a!==0&&(i(a),e.onBuyAll())},handleBuyCustom:a=>{i(a),e.onBuyCustom(a)},handleSell:()=>{e.isSellable&&(d(1),e.onSell())},handleSellAll:()=>{e.isSellable&&(d(s),e.onSellAll())},handleSellCustom:a=>{e.isSellable&&(d(a),e.onSellCustom(a))},owned:s}},le=({duration:e,autoReset:s=!0})=>{const[t,o]=b(!1),[r,n]=b(0),c=v(null),i=v(0),d=y(()=>{t||(o(!0),n(e),i.current=Date.now(),c.current=setInterval(()=>{const u=Date.now()-i.current,l=Math.max(e-u,0);n(l),l===0&&(clearInterval(c.current),o(!1))},100))},[t,e]),a=y(()=>{c.current&&clearInterval(c.current),o(!1),n(0)},[]);return k(()=>()=>{c.current&&clearInterval(c.current)},[]),{isCoolingDown:t,remainingTime:r,startCooldown:d,resetCooldown:a}},de=e=>{const{currency:s,increase:t,decrease:o,currencyName:r,reset:n,setCurrencyName:c,setPrecision:i,setFormat:d,getFormatted:a,getValue:u,setMaxValue:l,setMinValue:m}=w();return e&&(e.formatter&&d(e.formatter),e.precision!==void 0&&i(e.precision),e.initialName&&c(e.initialName)),{currency:s,increase:t,decrease:o,currencyName:r,reset:n,setCurrencyName:c,setPrecision:i,setFormat:d,getFormatted:a,getValue:u,setMaxValue:l,setMinValue:m}},me=()=>({...re()}),ge=({initialValue:e,max:s})=>{const[t,o]=b(e);return{resource:t,addResource:r=>{o(n=>Math.min(n+r,s))},removeResource:r=>{o(n=>Math.max(n-r,0))}}},pe=({baseValue:e,initialMultiplier:s=1})=>{const[t,o]=b(s),r=y(()=>e*t,[e,t]),n=y(i=>{o(d=>d+i)},[]),c=y(()=>{o(s)},[s]);return{multiplier:t,applyMultiplier:r,increaseMultiplier:n,resetMultiplier:c}},fe=({initialValue:e=0,onClick:s})=>{const[t,o]=b(e);return{count:t,handleClick:()=>{const r=t+1;o(r),s&&s(r)}}},he=({keys:e,callback:s,eventType:t="keydown",preventDefault:o=!1,stopPropagation:r=!1,disabled:n=!1,debounceMs:c=0,requireExactOrder:i=!1,allowRepeat:d=!0})=>{const a=v(new Set),u=v([]),l=v(0),m=v(),g=p=>["control","alt","shift","meta"].includes(p.toLowerCase()),f=y(p=>{const S=Date.now();c>0&&S-l.current<c||!d&&p.repeat||(o&&p.preventDefault(),r&&p.stopPropagation(),s(p),l.current=S)},[s,c,o,r,d]);return k(()=>{if(n)return;const p=(Array.isArray(e)?e:[e]).map(h=>h.toLowerCase()),S=()=>{if(i){const h=u.current;return p.every((M,N)=>h[h.length-p.length+N]===M)}return p.every(h=>a.current.has(h))},P=h=>{const M=h.key.toLowerCase();h.type==="keydown"?(a.current.add(M),u.current.push(M),u.current.length>p.length*2&&(u.current=u.current.slice(-p.length)),S()&&f(h)):h.type==="keyup"&&(a.current.delete(M),g(M)||(u.current=[]))};return document.addEventListener(t,P),document.addEventListener("keyup",P),()=>{document.removeEventListener(t,P),document.removeEventListener("keyup",P),a.current.clear(),u.current=[],m.current&&clearTimeout(m.current)}},[e,f,t,n,i]),null},ve=(e,s,t="en-US")=>{const o=typeof e=="string"?Number(e):e;return isNaN(o)?e:new Intl.NumberFormat(t,{notation:s}).format(o)},ye=()=>{const e=localStorage.getItem("data");return e?JSON.parse(e):{}},be=e=>{const s=window.localStorage,{data:t,onSave:o,onDiscard:r}=e;return k(()=>(s.setItem("data",JSON.stringify(t)),o&&o(),()=>{r&&r()}),[t,r,o]),{data:t}};export{B as useAchievement,ae as useAchievements,ne as useAutoIncrement,fe as useClicker,le as useCooldown,de as useCurrency,K as useGameLoop,he as useHotKey,oe as useIdleProgress,ye as useLoadSaveGame,pe as useMultiplier,ve as useNotation,ce as useOfflineProgress,ie as usePrestige,me as useProduction,ge as useResourceCap,be as useSaveGame,H as useTick,ue as useUpgrade};
//# sourceMappingURL=index.js.map