This commit is contained in:
crshqd/uiiuvdzfghvfd
2026-01-28 00:10:48 -05:00
commit e3f117a384
28 changed files with 2819 additions and 0 deletions
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1 @@
function createUnityInstance(t,n,d){function c(e,t){if(!c.aborted&&n.showBanner)return"error"==t&&(c.aborted=!0),n.showBanner(e,t);switch(t){case"error":console.error(e);break;case"warning":console.warn(e);break;default:console.log(e)}}function r(e){var t=e.reason||e.error,n=t?t.toString():e.message||e.reason||"",r=e.filename||t&&(t.fileName||t.sourceURL)||"",e=e.lineno||t&&(t.lineNumber||t.line)||0,t=t&&t.stack?t.stack.toString():r&&e?"at "+r+":"+e:"";""===n&&(n="An unspecified error occured."),S(n+="\n"+(t=t.startsWith(n)?t.substring(n.length):t).trim(),r,e)}function e(e,t,n){var r=e[t];void 0!==r&&r||(console.warn('Config option "'+t+'" is missing or empty. Falling back to default value: "'+n+'". Consider updating your WebGL template to include the missing config option.'),e[t]=n)}d=d||function(){};var o,m={canvas:t,webglContextAttributes:{preserveDrawingBuffer:!1,powerPreference:2},wasmFileSize:31117655,cacheControl:function(e){return e==m.dataUrl||e.match(/\.bundle/)?"must-revalidate":"no-store"},streamingAssetsUrl:"StreamingAssets",downloadProgress:{},deinitializers:[],intervals:{},setInterval:function(e,t){e=window.setInterval(e,t);return this.intervals[e]=!0,e},clearInterval:function(e){delete this.intervals[e],window.clearInterval(e)},preRun:[],postRun:[],print:function(e){console.log(e)},printErr:function(e){console.error(e),"string"==typeof e&&-1!=e.indexOf("wasm streaming compile failed")&&(-1!=e.toLowerCase().indexOf("mime")?c('HTTP Response Header "Content-Type" configured incorrectly on the server for file '+m.codeUrl+' , should be "application/wasm". Startup time performance will suffer.',"warning"):c('WebAssembly streaming compilation failed! This can happen for example if "Content-Encoding" HTTP header is incorrectly enabled on the server for file '+m.codeUrl+", but the file is not pre-compressed on disk (or vice versa). Check the Network tab in browser Devtools to debug server header configuration.","warning"))},locateFile:function(e){return"build.wasm"==e?this.codeUrl:e},disabledCanvasEvents:["contextmenu","dragstart"]};for(o in e(n,"companyName","Unity"),e(n,"productName","WebGL Player"),e(n,"productVersion","1.0"),n)m[o]=n[o];m.streamingAssetsUrl=new URL(m.streamingAssetsUrl,document.URL).href;var a=m.disabledCanvasEvents.slice();function i(e){e.preventDefault()}a.forEach(function(e){t.addEventListener(e,i)}),window.addEventListener("error",r),window.addEventListener("unhandledrejection",r);var s="",l="";function u(e){document.webkitCurrentFullScreenElement===t?t.style.width&&(s=t.style.width,l=t.style.height,t.style.width="100%",t.style.height="100%"):s&&(t.style.width=s,t.style.height=l,l=s="")}document.addEventListener("webkitfullscreenchange",u),m.deinitializers.push(function(){for(var e in m.disableAccessToMediaDevices(),a.forEach(function(e){t.removeEventListener(e,i)}),window.removeEventListener("error",r),window.removeEventListener("unhandledrejection",r),document.removeEventListener("webkitfullscreenchange",u),m.intervals)window.clearInterval(e);m.intervals={}}),m.QuitCleanup=function(){for(var e=0;e<m.deinitializers.length;e++)m.deinitializers[e]();m.deinitializers=[],"function"==typeof m.onQuit&&m.onQuit()};var h,f,p,g,b,y,w,v,C,P={Module:m,SetFullscreen:function(){if(m.SetFullscreen)return m.SetFullscreen.apply(m,arguments);m.print("Failed to set Fullscreen mode: Player not loaded yet.")},SendMessage:function(){if(m.SendMessage)return m.SendMessage.apply(m,arguments);m.print("Failed to execute SendMessage: Player not loaded yet.")},ConnectToProfiler:function(){if(m.ConnectToProfiler)return m.ConnectToProfiler.apply(m,arguments);m.print("Failed to execute ConnectToProfiler: Player not loaded yet.")},StopProfiling:function(){if(m.StopProfiling)return m.StopProfiling.apply(m,arguments);m.print("Failed to execute StopProfiling: Player not loaded yet.")},IsConnectedToProfiler:function(){if(m.IsConnectedToProfiler)return m.IsConnectedToProfiler.apply(m,arguments);m.print("Failed to execute IsConnectedToProfiler: Player not loaded yet.")},Quit:function(){return new Promise(function(e,t){m.shouldQuit=!0,m.onQuit=e})},GetMetricsInfo:function(){var e=Number(m._getMetricsInfo())>>>0,t=4+e,n=4+t,r=8+n,o=8+r,a=4+o,i=4+a,s=8+i,d=8+s,c=4+d,l=4+c,u=4+l;return{totalWASMHeapSize:m.HEAPU32[e>>2],usedWASMHeapSize:m.HEAPU32[t>>2],totalJSHeapSize:m.HEAPF64[n>>3],usedJSHeapSize:m.HEAPF64[r>>3],pageLoadTime:m.HEAPU32[o>>2],pageLoadTimeToFrame1:m.HEAPU32[a>>2],fps:m.HEAPF64[i>>3],movingAverageFps:m.HEAPF64[s>>3],assetLoadTime:m.HEAPU32[d>>2],webAssemblyStartupTime:m.HEAPU32[c>>2]-(m.webAssemblyTimeStart||0),codeDownloadTime:m.HEAPU32[l>>2],gameStartupTime:m.HEAPU32[u>>2],numJankedFrames:m.HEAPU32[4+u>>2]}}};function S(e,t,n){-1==e.indexOf("fullscreen error")&&(m.startupErrorHandler?m.startupErrorHandler(e,t,n):m.errorHandler&&m.errorHandler(e,t,n)||(console.log("Invoking error handler due to\n"+e),"function"==typeof dump&&dump("Invoking error handler due to\n"+e),S.didShowErrorMessage||(-1!=(e="An error occurred running the Unity content on this page. See your browser JavaScript console for more info. The error was:\n"+e).indexOf("DISABLE_EXCEPTION_CATCHING")?e="An exception has occurred, but exception handling has been disabled in this build. If you are the developer of this content, enable exceptions in your project WebGL player settings to be able to catch the exception or see the stack trace.":-1!=e.indexOf("Cannot enlarge memory arrays")?e="Out of memory. If you are the developer of this content, try allocating more memory to your WebGL build in the WebGL player settings.":-1==e.indexOf("Invalid array buffer length")&&-1==e.indexOf("Invalid typed array length")&&-1==e.indexOf("out of memory")&&-1==e.indexOf("could not allocate memory")||(e="The browser could not allocate enough memory for the WebGL content. If you are the developer of this content, try allocating less memory to your WebGL build in the WebGL player settings."),alert(e),S.didShowErrorMessage=!0)))}function T(e,t){if("symbolsUrl"!=e){var n=m.downloadProgress[e],r=(n=n||(m.downloadProgress[e]={started:!1,finished:!1,lengthComputable:!1,total:0,loaded:0}),"object"!=typeof t||"progress"!=t.type&&"load"!=t.type||(n.started||(n.started=!0,n.lengthComputable=t.lengthComputable),n.total=t.total,n.loaded=t.loaded,"load"==t.type&&(n.finished=!0)),0),o=0,a=0,i=0,s=0;for(e in m.downloadProgress){if(!(n=m.downloadProgress[e]).started)return;a++,n.lengthComputable?(r+=n.loaded,o+=n.total,i++):n.finished||s++}d(.9*(a?(a-s-(o?i*(o-r)/o:0))/a:0))}}function E(){var e=this;this.isConnected=this.connect().then(function(){return e.cleanUpCache()}),this.isConnected.catch(function(e){e="Error when initializing cache: "+e,console.log("[UnityCache] "+e)})}function U(e){console.log("[UnityCache] "+e)}function k(e){return k.link=k.link||document.createElement("a"),k.link.href=e,k.link.href}m.SystemInfo=function(){var e,t,n,r,o,a=navigator.userAgent+" ",i=[["Firefox","Firefox"],["OPR","Opera"],["Edg","Edge"],["SamsungBrowser","Samsung Browser"],["Trident","Internet Explorer"],["MSIE","Internet Explorer"],["Chrome","Chrome"],["CriOS","Chrome on iOS Safari"],["FxiOS","Firefox on iOS Safari"],["Safari","Safari"]];function s(e,t,n){return(e=RegExp(e,"i").exec(t))&&e[n]}for(var d=0;d<i.length;++d)if(t=s(i[d][0]+"[/ ](.*?)[ \\)]",a,1)){e=i[d][1];break}"Safari"==e&&(t=s("Version/(.*?) ",a,1)),"Internet Explorer"==e&&(t=s("rv:(.*?)\\)? ",a,1)||t);for(var c=[["Windows (.*?)[;)]","Windows"],["Android ([0-9_.]+)","Android"],["iPhone OS ([0-9_.]+)","iPhoneOS"],["iPad.*? OS ([0-9_.]+)","iPadOS"],["FreeBSD( )","FreeBSD"],["OpenBSD( )","OpenBSD"],["Linux|X11()","Linux"],["Mac OS X ([0-9_\\.]+)","MacOS"],["bot|google|baidu|bing|msn|teoma|slurp|yandex","Search Bot"]],l=0;l<c.length;++l)if(r=s(c[l][0],a,1)){n=c[l][1],r=r.replace(/_/g,".");break}var u;function h(){try{return window.WebAssembly?WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,5,3,1,0,1,10,13,1,11,0,65,0,65,0,65,1,252,11,0,11]))?WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,10,11,1,9,1,1,125,32,0,252,0,26,11]))?WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,10,10,1,8,1,1,126,32,0,194,26,11]))?WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,10,9,1,7,0,65,0,253,15,26,11]))?!!WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,10,10,1,8,0,6,64,1,25,1,11,11]))||"wasm-exceptions":"wasm-simd128":"sign-extend":"non-trapping fp-to-int":"bulk-memory":"WebAssembly"}catch(e){return"Exception: "+e}}r={"NT 5.0":"2000","NT 5.1":"XP","NT 5.2":"Server 2003","NT 6.0":"Vista","NT 6.1":"7","NT 6.2":"8","NT 6.3":"8.1","NT 10.0":"10"}[r]||r,webgpuVersion=0,(f=document.createElement("canvas"))&&(u=(p=f.getContext("webgl2"))?2:0,p||(p=f&&f.getContext("webgl"))&&(u=1),p&&(o=p.getExtension("WEBGL_debug_renderer_info")&&p.getParameter(37446)||p.getParameter(7937)));var f="undefined"!=typeof SharedArrayBuffer,p="object"==typeof WebAssembly&&"function"==typeof WebAssembly.compile,m=p&&!0===h();return{width:screen.width,height:screen.height,userAgent:a.trim(),browser:e||"Unknown browser",browserVersion:t||"Unknown version",mobile:/Mobile|Android|iP(ad|hone)/.test(navigator.appVersion),os:n||"Unknown OS",osVersion:r||"Unknown OS Version",gpu:o||"Unknown GPU",language:navigator.userLanguage||navigator.language,hasWebGL:u,hasWebGPU:webgpuVersion,hasCursorLock:!!document.body.requestPointerLock,hasFullscreen:!!document.body.requestFullscreen||!!document.body.webkitRequestFullscreen,hasThreads:f,hasWasm:p,hasWasm2023:m,missingWasm2023Feature:m?null:h(),hasWasmThreads:!1}}(),m.abortHandler=function(e){return S(e,"",0),!0},Error.stackTraceLimit=Math.max(Error.stackTraceLimit||0,50),m.readBodyWithProgress=function(a,i,s){var e=a.body?a.body.getReader():void 0,d=void 0!==a.headers.get("Content-Length"),c=function(e,t){if(!t)return 0;var t=e.headers.get("Content-Encoding"),n=parseInt(e.headers.get("Content-Length")),r=536870912;switch(t){case"br":return Math.min(Math.round(2*n),r);case"gzip":return Math.min(Math.round(1.6*n),r);default:return n}}(a,d),l=new Uint8Array(c),u=[],h=0,f=0;return d||console.warn("[UnityCache] Response is served without Content-Length header. Please reconfigure server to include valid Content-Length for better download performance."),function o(){return void 0===e?a.arrayBuffer().then(function(e){var t=new Uint8Array(e);return i({type:"progress",response:a,total:e.length,loaded:0,lengthComputable:d,chunk:s?t:null}),t}):e.read().then(function(e){if(e.done){if(h===c)return l;if(h<c)return l.slice(0,h);for(var t=new Uint8Array(h),n=(t.set(l,0),f),r=0;r<u.length;++r)t.set(u[r],n),n+=u[r].length;return t}return h+e.value.length<=l.length?(l.set(e.value,h),f=h+e.value.length):u.push(e.value),h+=e.value.length,i({type:"progress",response:a,total:Math.max(c,h),loaded:h,lengthComputable:d,chunk:s?e.value:null}),o()})}().then(function(e){return i({type:"load",response:a,total:e.length,loaded:e.length,lengthComputable:d,chunk:null}),a.parsedBody=e,a})},m.fetchWithProgress=function(e,t){var n=function(){};return t&&t.onProgress&&(n=t.onProgress),fetch(e,t).then(function(e){return m.readBodyWithProgress(e,n,t.enableStreamingDownload)})},m.UnityCache=(h={name:"UnityCache",version:4},f={name:"RequestMetaDataStore",version:1},p="RequestStore",g="WebAssembly",b=window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB,y=null,E.getInstance=function(){return y=y||new E},E.destroyInstance=function(){return y?y.close().then(function(){y=null}):Promise.resolve()},E.prototype.clearCache=function(){var r=this;return this.isConnected.then(function(){return r.execute(f.name,"clear",[])}).then(function(){return r.cache.keys()}).then(function e(t){var n;return 0===t.length?Promise.resolve():(n=t.pop(),r.cache.delete(n).then(function(){return e(t)}))})},E.UnityCacheDatabase=h,E.RequestMetaDataStore=f,E.MaximumCacheSize=1073741824,E.prototype.loadRequest=function(e){var t=this;return t.isConnected.then(function(){return Promise.all([t.cache.match(e),t.loadRequestMetaData(e)])}).then(function(e){if(void 0!==e[0]&&void 0!==e[1])return{response:e[0],metaData:e[1]}})},E.prototype.loadRequestMetaData=function(e){e="string"==typeof e?e:e.url;return this.execute(f.name,"get",[e])},E.prototype.updateRequestMetaData=function(e){return this.execute(f.name,"put",[e])},E.prototype.storeRequest=function(e,t){var n=this;return n.isConnected.then(function(){return n.cache.put(e,t)})},E.prototype.close=function(){return this.isConnected.then(function(){this.database&&(this.database.close(),this.database=null),this.cache&&(this.cache=null)}.bind(this))},E.prototype.connect=function(){var o=this;return void 0===b?Promise.reject(new Error("Could not connect to cache: IndexedDB is not supported.")):void 0===window.caches?Promise.reject(new Error("Could not connect to cache: Cache API is not supported.")):new Promise(function(t,n){try{function r(){o.openDBTimeout&&(clearTimeout(o.openDBTimeout),o.openDBTimeout=null)}o.openDBTimeout=setTimeout(function(){void 0===o.database&&n(new Error("Could not connect to cache: Database timeout."))},2e4);var e=b.open(h.name,h.version);e.onupgradeneeded=o.upgradeDatabase.bind(o),e.onsuccess=function(e){r(),o.database=e.target.result,t()},e.onerror=function(e){r(),o.database=null,n(new Error("Could not connect to database."))}}catch(e){r(),o.database=null,o.cache=null,n(new Error("Could not connect to cache: Could not connect to database."))}}).then(function(){var e=h.name+"_"+m.companyName+"_"+m.productName;return caches.open(e)}).then(function(e){o.cache=e})},E.prototype.upgradeDatabase=function(e){var t,e=e.target.result;e.objectStoreNames.contains(f.name)||(t=e.createObjectStore(f.name,{keyPath:"url"}),["accessedAt","updatedAt"].forEach(function(e){t.createIndex(e,e)})),e.objectStoreNames.contains(p)&&e.deleteObjectStore(p),e.objectStoreNames.contains(g)&&e.deleteObjectStore(g)},E.prototype.execute=function(a,i,s){return this.isConnected.then(function(){return new Promise(function(t,n){try{var e,r,o;null===this.database?n(new Error("indexedDB access denied")):(e=-1!=["put","delete","clear"].indexOf(i)?"readwrite":"readonly",r=this.database.transaction([a],e).objectStore(a),"openKeyCursor"==i&&(r=r.index(s[0]),s=s.slice(1)),(o=r[i].apply(r,s)).onsuccess=function(e){t(e.target.result)},o.onerror=function(e){n(e)})}catch(e){n(e)}}.bind(this))}.bind(this))},E.prototype.getMetaDataEntries=function(){var r=this,o=0,a=[];return new Promise(function(t,n){var e=r.database.transaction([f.name],"readonly").objectStore(f.name).openCursor();e.onsuccess=function(e){e=e.target.result;e?(o+=e.value.size,a.push(e.value),e.continue()):t({metaDataEntries:a,cacheSize:o})},e.onerror=function(e){n(e)}})},E.prototype.cleanUpCache=function(){var i=this;return this.getMetaDataEntries().then(function(e){for(var t=e.metaDataEntries,n=e.cacheSize,r=[],o=[],a=0;a<t.length;++a)t[a].version==m.productVersion?o.push(t[a]):(r.push(t[a]),n-=t[a].size);o.sort(function(e,t){return e.accessedAt-t.accessedAt});for(a=0;a<o.length&&!(n<E.MaximumCacheSize);++a)r.push(o[a]),n-=o[a].size;return function e(){var t;return 0===r.length?Promise.resolve():(t=r.pop(),i.cache.delete(t.url).then(function(e){if(e)return r=t.url,new Promise(function(e,t){var n=i.database.transaction([f.name],"readwrite");n.objectStore(f.name).delete(r),n.oncomplete=e,n.onerror=t});var r}).then(e))}()})},E),m.cachedFetch=(w=m.UnityCache,v=m.fetchWithProgress,C=m.readBodyWithProgress,function(o,a){var e,t,i=w.getInstance(),s=k("string"==typeof o?o:o.url),d={enabled:(e=s,(!(t=a)||!t.method||"GET"===t.method)&&((!t||-1!=["must-revalidate","immutable"].indexOf(t.control))&&!!e.match("^https?://")))};function c(n,r){return fetch(n,r).then(function(e){var t;return!d.enabled||d.revalidated?e:304===e.status?(d.revalidated=!0,i.updateRequestMetaData(d.metaData).then(function(){U("'"+d.metaData.url+"' successfully revalidated and served from the browser cache")}).catch(function(e){U("'"+d.metaData.url+"' successfully revalidated but not stored in the browser cache due to the error: "+e)}),C(d.response,r.onProgress,r.enableStreamingDownload)):200==e.status?(d.response=e,d.metaData.updatedAt=d.metaData.accessedAt,d.revalidated=!0,t=e.clone(),C(e,r.onProgress,r.enableStreamingDownload).then(function(e){return d.metaData.size=e.parsedBody.length,Promise.all([i.storeRequest(n,t),i.updateRequestMetaData(d.metaData)]).then(function(){U("'"+s+"' successfully downloaded and stored in the browser cache")}).catch(function(e){U("'"+s+"' successfully downloaded but not stored in the browser cache due to the error: "+e)}),e})):(U("'"+s+"' request failed with status: "+e.status+" "+e.statusText),C(e,r.onProgress,r.enableStreamingDownload))})}return a&&(d.control=a.control,d.companyName=a.companyName,d.productName=a.productName,d.productVersion=a.productVersion),d.revalidated=!1,d.metaData={url:s,accessedAt:Date.now(),version:d.productVersion},d.response=null,d.enabled?i.loadRequest(s).then(function(e){var n,r,t;return e?(n=e.response,r=e.metaData,d.response=n,d.metaData.size=r.size,d.metaData.updatedAt=r.updatedAt,"immutable"==d.control?(d.revalidated=!0,i.updateRequestMetaData(r).then(function(){U("'"+d.metaData.url+"' served from the browser cache without revalidation")}),C(n,a.onProgress,a.enableStreamingDownload)):(e=s,(t=window.location.href.match(/^[a-z]+:\/\/[^\/]+/))&&!e.lastIndexOf(t[0],0)||!n.headers.get("Last-Modified")&&!n.headers.get("ETag")?(e=(a=a||{}).headers||{},a.headers=e,n.headers.get("Last-Modified")?(e["If-Modified-Since"]=n.headers.get("Last-Modified"),e["Cache-Control"]="no-cache"):n.headers.get("ETag")&&(e["If-None-Match"]=n.headers.get("ETag"),e["Cache-Control"]="no-cache"),c(o,a)):fetch(s,{method:"HEAD"}).then(function(t){return d.revalidated=["Last-Modified","ETag"].every(function(e){return!n.headers.get(e)||n.headers.get(e)==t.headers.get(e)}),d.revalidated?(i.updateRequestMetaData(r).then(function(){U("'"+d.metaData.url+"' successfully revalidated and served from the browser cache")}),C(d.response,a.onProgress,a.enableStreamingDownload)):c(o,a)}))):c(o,a)}).catch(function(e){return U("Failed to load '"+d.metaData.url+"' from browser cache due to the error: "+e),v(o,a)}):v(o,a)});var A={gzip:{hasUnityMarker:function(e){var t=10,n="UnityWeb Compressed Content (gzip)";if(t>e.length||31!=e[0]||139!=e[1])return!1;var r=e[3];if(4&r){if(t+2>e.length)return!1;if((t+=2+e[t]+(e[t+1]<<8))>e.length)return!1}if(8&r){for(;t<e.length&&e[t];)t++;if(t+1>e.length)return!1;t++}return 16&r&&String.fromCharCode.apply(null,e.subarray(t,t+n.length+1))==n+"\0"}},br:{hasUnityMarker:function(e){var t="UnityWeb Compressed Content (brotli)";if(!e.length)return!1;var n=1&e[0]?14&e[0]?4:7:1,r=e[0]&(1<<n)-1,o=1+(Math.log(t.length-1)/Math.log(2)>>3);if(commentOffset=1+n+2+1+2+(o<<3)+7>>3,17==r||commentOffset>e.length)return!1;for(var a=r+(6+(o<<4)+(t.length-1<<6)<<n),i=0;i<commentOffset;i++,a>>>=8)if(e[i]!=(255&a))return!1;return String.fromCharCode.apply(null,e.subarray(commentOffset,commentOffset+t.length))==t}}};function D(){var t,e,n,s,r,o=performance.now(),p=(new Promise(function(a,e){var i=document.createElement("script");i.src=m.frameworkUrl,i.onload=function(){if("undefined"==typeof unityFramework||!unityFramework){var e,t=[["br","br"],["gz","gzip"]];for(e in t){var n,r=t[e];if(m.frameworkUrl.endsWith("."+r[0]))return n="Unable to parse "+m.frameworkUrl+"!","file:"==location.protocol?void c(n+" Loading pre-compressed (brotli or gzip) content via a file:// URL without a web server is not supported by this browser. Please use a local development web server to host compressed Unity content, or use the Unity Build and Run option.","error"):(n+=' This can happen if build compression was enabled but web server hosting the content was misconfigured to not serve the file with HTTP Response Header "Content-Encoding: '+r[1]+'" present. Check browser Console and Devtools Network tab to debug.',"br"==r[0]&&"http:"==location.protocol&&(r=-1!=["localhost","127.0.0.1"].indexOf(location.hostname)?"":"Migrate your server to use HTTPS.",n=/Firefox/.test(navigator.userAgent)?"Unable to parse "+m.frameworkUrl+'!<br>If using custom web server, verify that web server is sending .br files with HTTP Response Header "Content-Encoding: br". Brotli compression may not be supported in Firefox over HTTP connections. '+r+' See <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1670675">https://bugzilla.mozilla.org/show_bug.cgi?id=1670675</a> for more information.':"Unable to parse "+m.frameworkUrl+'!<br>If using custom web server, verify that web server is sending .br files with HTTP Response Header "Content-Encoding: br". Brotli compression may not be supported over HTTP connections. Migrate your server to use HTTPS.'),void c(n,"error"))}c("Unable to parse "+m.frameworkUrl+"! The file is corrupt, or compression was misconfigured? (check Content-Encoding HTTP Response Header on web server)","error")}var o=unityFramework;unityFramework=null,i.onload=null,a(o)},i.onerror=function(e){c("Unable to load file "+m.frameworkUrl+"! Check that the file exists on the remote server. (also check browser Console and Devtools Network tab to debug)","error")},document.body.appendChild(i),m.deinitializers.push(function(){document.body.removeChild(i)})}).then(function(e){m.webAssemblyTimeStart=performance.now(),e(m),m.codeDownloadTimeEnd=performance.now()-o}),performance.now()),a=(T(t="dataUrl"),e=m.cacheControl(m[t]),n=m.companyName&&m.productName?m.cachedFetch:m.fetchWithProgress,s=m[t],r=/file:\/\//.exec(s)?"same-origin":void 0,n(m[t],{method:"GET",companyName:m.companyName,productName:m.productName,productVersion:m.productVersion,control:e,mode:r,onProgress:function(e){T(t,e)}}).then(function(e){var t,n,r,o,a,i;return A.gzip.hasUnityMarker(e.parsedBody)&&(t=["gzip","gzip"]),(t=A.br.hasUnityMarker(e.parsedBody)?["brotli","br"]:t)&&(n=e.headers.get("Content-Type"),r=e.headers.get("Content-Encoding"),a=0<(o=e.headers.get("Content-Length"))&&e.parsedBody.length!=o,i=0<o&&e.parsedBody.length==o,r!=t[1]?c("Failed to parse binary data file "+s+' (with "Content-Type: '+n+'"), because it is still '+t[0]+'-compressed. It should have been uncompressed by the browser, but it was unable to do so since the web server provided the compressed content without specifying the HTTP Response Header "Content-Encoding: '+t[1]+'" that would have informed the browser that decompression is needed. Please verify your web server hosting configuration to add the missing "Content-Encoding: '+t[1]+'" HTTP Response Header.',"error"):c(a?"Web server configuration error: it looks like the web server has been misconfigured to double-compress the data file "+s+"! That is, it looks like the web browser has decompressed the file, but it is still in compressed form, suggesting that an already compressed file was compressed a second time. (Content-Length: "+o+", obtained length: "+e.parsedBody.length+")":i?/^((?!chrome|android).)*safari/i.test(navigator.userAgent)&&"gzip"==r&&"application/octet-stream"==n?"Unable to load content due to Apple Safari bug https://bugs.webkit.org/show_bug.cgi?id=247421 . To work around this issue, please reconfigure your web server to serve "+s+" with Content-Type: application/gzip instead of Content-Type: application/octet-stream":"Malformed binary data? Received compressed data file "+s+', with "Content-Type: '+n+'", "Content-Encoding: '+t[1]+'", "Content-Length: '+o+'", which the web browser should have decompressed, but it seemingly did not (received file size is the same as compressed file size was). Double check that the integrity of the file is intact.':"Malformed binary data URL "+s+'. No "Content-Length" HTTP Response header present. Check browser console for more information.',"error"),console.error("Malformed data? Downloaded binary data file "+s+" (ArrayBuffer size: "+e.parsedBody.length+") and browser should have decompressed it, but it might have not. Dumping raw HTTP Response Headers if it might help debug:"),e.headers.forEach(function(e,t){console.error(t+": "+e)})),e.parsedBody}).catch(function(e){var t="Failed to download file "+s;"file:"==location.protocol?c(t+". Loading web pages via a file:// URL without a web server is not supported by this browser. Please use a local development web server to host Unity content, or use the Unity Build and Run option.","error"):console.error(t)}));m.preRun.push(function(){m.addRunDependency("dataUrl"),a.then(function(t){var e=new TextDecoder("utf-8"),n=0;function r(){var e=(t[n]|t[n+1]<<8|t[n+2]<<16|t[n+3]<<24)>>>0;return n+=4,e}function o(e){if(A.gzip.hasUnityMarker(t))throw e+'. Failed to parse binary data file, because it is still gzip-compressed and should have been uncompressed by the browser. Web server has likely provided gzip-compressed data without specifying the HTTP Response Header "Content-Encoding: gzip" with it to instruct the browser to decompress it. Please verify your web server hosting configuration.';if(A.br.hasUnityMarker(t))throw e+'. Failed to parse binary data file, because it is still brotli-compressed and should have been uncompressed by the browser. Web server has likely provided brotli-compressed data without specifying the HTTP Response Header "Content-Encoding: br" with it to instruct the browser to decompress it. Please verify your web server hosting configuration.';throw e}var a="UnityWebData1.0\0",i=e.decode(t.subarray(0,a.length)),s=(i!=a&&o('Unknown data format (id="'+i+'")'),n+=a.length,r());for(n+s>t.length&&o("Invalid binary data file header! (pos="+n+", headerSize="+s+", file length="+t.length+")");n<s;){var d=r(),c=r(),l=(d+c>t.length&&o("Invalid binary data file size! (offset="+d+", size="+c+", file length="+t.length+")"),r()),u=(n+l>t.length&&o("Invalid binary data file path name! (pos="+n+", length="+l+", file length="+t.length+")"),e.decode(t.subarray(n,n+l)));n+=l;for(var h=0,f=u.indexOf("/",h)+1;0<f;h=f,f=u.indexOf("/",h)+1)m.FS_createPath(u.substring(0,h),u.substring(h,f-1),!0,!0);m.FS_createDataFile(u,null,t.subarray(d,d+c),!0,!0,!0)}m.removeRunDependency("dataUrl"),m.dataUrlLoadEndTime=performance.now()-p})})}return new Promise(function(e,t){var n;m.SystemInfo.hasWebGL?1==m.SystemInfo.hasWebGL?(n='Your browser does not support graphics API "WebGL 2" which is required for this content.',"Safari"==m.SystemInfo.browser&&parseInt(m.SystemInfo.browserVersion)<15&&(m.SystemInfo.mobile||1<navigator.maxTouchPoints?n+="\nUpgrade to iOS 15 or later.":n+="\nUpgrade to Safari 15 or later."),t(n)):m.SystemInfo.hasWasm?(m.startupErrorHandler=t,d(0),m.postRun.push(function(){d(1),m.WebPlayer.WaitForInitialization().then(function(){delete m.startupErrorHandler,e(P),m.pageStartupTime=performance.now()})}),m.SystemInfo.hasWebGPU=!1,Promise.resolve(!1).then(function(){D()})):t("Your browser does not support WebAssembly."):t("Your browser does not support WebGL.")})}
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
{"m_buildTarget":"WebGL","m_SettingsHash":"275fa873ba2f8e4d6d29663529cb3656","m_CatalogLocations":[{"m_Keys":["AddressablesMainContentCatalog"],"m_InternalId":"{UnityEngine.AddressableAssets.Addressables.RuntimePath}/catalog.bin","m_Provider":"UnityEngine.AddressableAssets.ResourceProviders.ContentCatalogProvider","m_Dependencies":[],"m_ResourceType":{"m_AssemblyName":"Unity.Addressables, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null","m_ClassName":"UnityEngine.AddressableAssets.ResourceLocators.ContentCatalogData"},"SerializedData":[]}],"m_LogResourceManagerExceptions":true,"m_ExtraInitializationData":[],"m_DisableCatalogUpdateOnStart":false,"m_IsLocalCatalogInBundle":false,"m_CertificateHandlerType":{"m_AssemblyName":"","m_ClassName":""},"m_AddressablesVersion":"2.2.2","m_maxConcurrentWebRequests":3,"m_CatalogRequestsTimeout":0}
+208
View File
@@ -0,0 +1,208 @@
// AdinPlay Ad Provider Module
// This module handles all AdinPlay-specific ad functionality
(function() {
console.log("[adinplay-ads.js] Module loading...");
// AdinPlay tag mapping - can be customized per game
const ADINPLAY_TAG_MAPPING = {
"300x250": "2v2-io_300x250",
"728x90": "2v2-io_728x90",
"300x600": "2v2-io_300x600"
};
// Ensure global providers exist
window.videoAdProviders = window.videoAdProviders || {};
window.bannerAdProviders = window.bannerAdProviders || {};
// AdinPlay initialization
function initAdinPlay() {
console.log("[adinplay-ads.js] Initializing AdinPlay...");
window.aiptag = window.aiptag || { cmd: [] };
aiptag.cmd.display = aiptag.cmd.display || [];
aiptag.cmd.player = aiptag.cmd.player || [];
aiptag.cmp = {
show: true,
position: "bottom",
button: false,
buttonText: "Privacy settings",
buttonPosition: "bottom-left"
};
// aiptag.pageProtect = true;
aiptag.cmd.player.push(function () {
console.log("[adinplay-ads.js] Creating AdinPlay player...");
aiptag.adplayer = new aipPlayer({
AD_WIDTH: 960,
AD_HEIGHT: 540,
AD_DISPLAY: 'fullscreen',
LOADING_TEXT: 'Loading advertisement',
PREROLL_ELEM: function () {
const elem = document.getElementById('videoad');
console.log(`[adinplay-ads.js] Preroll element: ${elem ? 'found' : 'not found'}`);
return elem;
},
AIP_COMPLETE: function (state) {
console.log(`[adinplay-ads.js] Video Ad Completed: ${state}`);
const lowerCaseState = state.toLowerCase();
const isFailure = lowerCaseState.includes("adblock") ||
lowerCaseState.includes("failed") ||
lowerCaseState.includes("empty") ||
lowerCaseState.includes("error");
if (isFailure) {
console.log("[adinplay-ads.js] Ad failed");
if (window._adinplayTempFailure) {
window._adinplayTempFailure();
delete window._adinplayTempFailure;
delete window._adinplayTempSuccess;
} else if (typeof unityInstance !== 'undefined') {
unityInstance.SendMessage("SDKManager", "OnVideoAdEnded", "false");
}
} else {
console.log("[adinplay-ads.js] Ad succeeded");
if (window._adinplayTempSuccess) {
window._adinplayTempSuccess();
delete window._adinplayTempSuccess;
delete window._adinplayTempFailure;
} else if (typeof unityInstance !== 'undefined') {
unityInstance.SendMessage("SDKManager", "OnVideoAdEnded", "true");
}
}
}
});
console.log("[adinplay-ads.js] AdinPlay player created");
});
// Load the AdinPlay script
console.log("[adinplay-ads.js] Loading AdinPlay script...");
const adinplayScript = document.createElement('script');
adinplayScript.type = "text/javascript";
adinplayScript.src = '//api.adinplay.com/libs/aiptag/pub/LGP/2v2.io/tag.min.js';
adinplayScript.async = true;
adinplayScript.onload = function() {
console.log("[adinplay-ads.js] Script loaded successfully");
};
adinplayScript.onerror = function() {
console.error("[adinplay-ads.js] Failed to load script");
};
document.head.appendChild(adinplayScript);
}
// Video ad provider implementation
window.videoAdProviders.adinplay = {
showMidroll: function(onSuccess, onFailure) {
console.log(`[adinplay-ads.js] showMidroll called`);
if (typeof aiptag !== 'undefined' && typeof aiptag.adplayer !== 'undefined') {
window._adinplayTempSuccess = onSuccess;
window._adinplayTempFailure = onFailure;
aiptag.cmd.player.push(function () {
console.log(`[adinplay-ads.js] Starting preroll`);
aiptag.adplayer.startPreRoll();
});
} else {
console.log(`[adinplay-ads.js] API not available`);
onFailure();
}
},
showRewarded: function(onSuccess, onFailure) {
console.log(`[adinplay-ads.js] showRewarded called (delegates to showMidroll)`);
this.showMidroll(onSuccess, onFailure);
}
};
// Banner ad provider implementation
window.bannerAdProviders.adinplay = {
displayBanner: async function(adTag, container) {
console.log(`[adinplay-ads.js] displayBanner for tag: ${adTag}`);
// Map generic adTag to AdinPlay-specific tag
const adinplayTag = ADINPLAY_TAG_MAPPING[adTag];
if (!adinplayTag) {
console.error(`[adinplay-ads.js] No mapping found for adTag: ${adTag}`);
return false;
}
console.log(`[adinplay-ads.js] Using AdinPlay tag: ${adinplayTag}`);
// Ensure our container has a proper placeholder element for AdinPlay
try {
// Remove any existing placeholder with the same id elsewhere
const existing = document.getElementById(adinplayTag);
if (existing && existing.parentNode) {
existing.parentNode.removeChild(existing);
}
} catch (e) {
// ignore
}
// Clear and prepare container
container.innerHTML = "";
const placeholder = document.createElement('div');
placeholder.id = adinplayTag;
placeholder.style.width = '100%';
placeholder.style.height = '100%';
placeholder.style.position = 'relative';
container.appendChild(placeholder);
// Wait for AdinPlay to be ready
const isReady = await window.waitForAdinPlay();
if (!(isReady && typeof aiptag !== "undefined" && typeof aipDisplayTag !== "undefined")) {
console.log(`[adinplay-ads.js] Not available for banner`);
return false;
}
// Display and verify fill. Resolve true only if creative appears; else false to allow fallback
return await new Promise((resolve) => {
let settled = false;
const settle = (ok) => {
if (!settled) { settled = true; resolve(ok); }
};
// Observe for injected content
const observer = new MutationObserver(() => {
// Ad networks typically inject iframes or images
const hasCreative = placeholder.querySelector('iframe, img, ins, div');
if (hasCreative && placeholder.children.length > 0) {
observer.disconnect();
settle(true);
}
});
try {
observer.observe(placeholder, { childList: true, subtree: true });
} catch {}
// Safety timeout in case nothing is injected (e.g., adblock)
const timeoutMs = 3000;
const timeoutId = setTimeout(() => {
observer.disconnect();
// Consider it a failure if no children were added
const ok = placeholder.children.length > 0;
settle(ok);
}, timeoutMs);
// Request display
aiptag.cmd.display.push(function() {
try {
aipDisplayTag.display(adinplayTag);
} catch (e) {
clearTimeout(timeoutId);
observer.disconnect();
settle(false);
}
});
});
}
};
// Initialize AdinPlay
initAdinPlay();
console.log("[adinplay-ads.js] Module loaded successfully");
})();
+298
View File
@@ -0,0 +1,298 @@
// CPMStar Ad Provider Module
// This module handles all CPMStar-specific ad functionality
(function() {
console.log("[cpmstar-ads.js] Module loading...");
// Ensure global providers exist
window.videoAdProviders = window.videoAdProviders || {};
window.bannerAdProviders = window.bannerAdProviders || {};
// CPMStar configuration
const CPMSTAR_ZONE_FILE = '1137_54105_gameapi';
// CPMStar banner placement IDs (replace with your actual placement IDs)
window.cpmstarBannerMapping = {
0: '88824', // 300x250
1: '88823', // 728x90
2: '88827' // 300x600
};
// CPMStar state
let cpmstarRewardedVideo = null;
let cpmstarInitialized = false;
// Initialize CPMStar for video ads
function initCpmstarVideo() {
console.log("[cpmstar-ads.js] Initializing CPMStar video ads...");
(function (zonefile) {
var y = "cpmstarx";
var drutObj = window[y] = window[y] || {};
function failCpmstarAPI() {
var failFn = function (o) { o && typeof (o) === "object" && o.fail && o.fail(); };
drutObj && Array.isArray(drutObj.cmd) && drutObj.cmd.forEach(failFn) && (drutObj.cmd.length = 0);
window.cpmstarAPI = window["_" + zonefile] = failFn;
}
var rnd = Math.round(Math.random() * 999999);
var s = document.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.onerror = failCpmstarAPI;
s.onload = initCpmstarAPI;
var proto = document.location.protocol;
var host = (proto == "https:" || proto == "file:") ? "https://server" : "//cdn";
if (window.location.hash == "#cpmstarDev") host = "//dev.server";
if (window.location.hash == "#cpmstarStaging") host = "//staging.server";
s.src = host + ".cpmstar.com/cached/zonefiles/" + zonefile + ".js?rnd=" + rnd;
var s2 = document.getElementsByTagName('script')[0];
s2.parentNode.insertBefore(s, s2);
window.cpmstarAPI = function (o) { (drutObj.cmd = drutObj.cmd || []).push(o); }
})(CPMSTAR_ZONE_FILE);
}
function initCpmstarAPI() {
console.log("[cpmstar-ads.js] CPMStar API callback");
if (typeof cpmstarAPI !== 'undefined') {
cpmstarAPI(function(api) {
console.log("[cpmstar-ads.js] Setting up CPMStar...");
// Set target for game ads
api.game.setTarget(document.body);
// Initialize interstitial ad
cpmstarAPI({
kind: "game.createInterstitial",
onAdOpened: function () {
console.log("[cpmstar-ads.js] Interstitial opened");
},
onAdClosed: function () {
console.log("[cpmstar-ads.js] Interstitial closed");
},
fail: function () {
console.log("[cpmstar-ads.js] Interstitial failed");
window.adblocked = true;
}
});
// Initialize rewarded video
cpmstarRewardedVideo = new api.game.RewardedVideoView("rewardedvideo");
cpmstarRewardedVideo.addEventListener("ad_opened", function(e) {
console.log("[cpmstar-ads.js] Rewarded video opened");
});
cpmstarRewardedVideo.addEventListener("ad_closed", function(e) {
console.log("[cpmstar-ads.js] Rewarded video closed");
if (window._cpmstarTempSuccess) {
window._cpmstarTempSuccess();
delete window._cpmstarTempSuccess;
delete window._cpmstarTempFailure;
} else if (typeof unityInstance !== 'undefined') {
unityInstance.SendMessage("SDKManager", "OnVideoAdEnded", "true");
}
// Preload another ad
setTimeout(function() {
cpmstarRewardedVideo.load();
}, 700);
});
cpmstarRewardedVideo.addEventListener("loaded", function(e) {
console.log("[cpmstar-ads.js] Rewarded video loaded");
});
cpmstarRewardedVideo.addEventListener("load_failed", function(e) {
console.log("[cpmstar-ads.js] Rewarded video failed to load");
});
// Preload first rewarded video
cpmstarRewardedVideo.load();
cpmstarInitialized = true;
console.log("[cpmstar-ads.js] Initialization complete");
});
}
}
// Helper function to trigger CPMStar banner
function triggerCpmstarBanner(placementId, element) {
console.log(`[cpmstar-ads.js] Triggering banner for placement ID: ${placementId}`);
if (typeof window.cpmstarx === 'undefined') {
window.cpmstarx = {};
}
if (!window.cpmstarx.libcmd) {
window.cpmstarx.libcmd = [];
}
// Find all elements with this class and get the last one
const els = document.getElementsByClassName(`div-${placementId}`);
const pindex = els.length - 1;
const el = els[pindex];
console.log(`[cpmstar-ads.js] Elements found: ${els.length}, using index: ${pindex}`);
// Push the banner command
window.cpmstarx.libcmd.push({
kind: 'asynctagfetch',
el: el,
pid: placementId,
pindex: pindex
});
console.log(`[cpmstar-ads.js] Banner command queued`);
}
// Video ad provider implementation
window.videoAdProviders.cpmstar = {
showMidroll: function(onSuccess, onFailure) {
console.log(`[cpmstar-ads.js] showMidroll called`);
if (typeof cpmstarAPI !== 'undefined') {
cpmstarAPI({
kind: "game.displayInterstitial",
onAdOpened: function () {
console.log(`[cpmstar-ads.js] Midroll opened`);
},
onAdClosed: function () {
console.log(`[cpmstar-ads.js] Midroll closed`);
onSuccess();
},
fail: function () {
console.log(`[cpmstar-ads.js] Midroll failed`);
onFailure();
}
});
} else {
console.log(`[cpmstar-ads.js] API not available`);
onFailure();
}
},
showRewarded: function(onSuccess, onFailure) {
console.log(`[cpmstar-ads.js] showRewarded called`);
if (cpmstarRewardedVideo && cpmstarRewardedVideo.isLoaded()) {
window._cpmstarTempSuccess = onSuccess;
window._cpmstarTempFailure = onFailure;
cpmstarRewardedVideo.show();
} else if (cpmstarRewardedVideo) {
console.log(`[cpmstar-ads.js] Rewarded video not loaded`);
cpmstarRewardedVideo.load();
onFailure();
} else {
console.log(`[cpmstar-ads.js] Rewarded video not initialized`);
onFailure();
}
}
};
// Banner ad provider implementation
window.bannerAdProviders.cpmstar = {
displayBanner: async function(bannerType, container) {
const placementId = window.cpmstarBannerMapping[bannerType];
console.log(`[cpmstar-ads.js] displayBanner for type: ${bannerType}, placement ID: ${placementId}`);
if (!placementId) {
console.log(`[cpmstar-ads.js] No placement ID for banner type ${bannerType}`);
return false;
}
// Get dimensions from global bannerDimensions
const dims = window.bannerDimensions[bannerType];
if (!dims) {
console.log(`[cpmstar-ads.js] No dimensions for banner type ${bannerType}`);
return false;
}
// Create CPMStar banner container
const cpmstarDiv = document.createElement('div');
cpmstarDiv.className = `div-${placementId}`;
cpmstarDiv.style.width = dims.width;
cpmstarDiv.style.height = dims.height;
cpmstarDiv.style.position = 'relative';
cpmstarDiv.style.display = 'block';
// Clear existing content and add CPMStar div
container.innerHTML = '';
container.appendChild(cpmstarDiv);
// Observe for creative injection
const success = await new Promise((resolve) => {
let settled = false;
const settle = (ok) => { if (!settled) { settled = true; resolve(ok); } };
const observer = new MutationObserver(() => {
const hasCreative = cpmstarDiv.querySelector('iframe, img, ins, div');
if (hasCreative && cpmstarDiv.children.length > 0) {
observer.disconnect();
settle(true);
}
});
try {
observer.observe(cpmstarDiv, { childList: true, subtree: true });
} catch {}
const timeoutMs = 3000;
const timeoutId = setTimeout(() => {
observer.disconnect();
const ok = cpmstarDiv.children.length > 0;
settle(ok);
}, timeoutMs);
// Load CPMStar banner script if not already loaded
if (!window.cpmstarBannerScriptLoaded) {
console.log(`[cpmstar-ads.js] Loading banner script`);
const script = document.createElement('script');
script.async = true;
script.src = 'https://ssl.cdne.cpmstar.com/cached/js/lib.js';
script.onload = function() {
console.log(`[cpmstar-ads.js] Banner script loaded`);
window.cpmstarBannerScriptLoaded = true;
try {
triggerCpmstarBanner(placementId, cpmstarDiv);
} catch (e) {
clearTimeout(timeoutId);
observer.disconnect();
settle(false);
}
};
script.onerror = function() {
console.log(`[cpmstar-ads.js] Failed to load banner script`);
clearTimeout(timeoutId);
observer.disconnect();
settle(false);
};
document.head.appendChild(script);
} else {
try {
triggerCpmstarBanner(placementId, cpmstarDiv);
} catch (e) {
clearTimeout(timeoutId);
observer.disconnect();
settle(false);
}
}
});
return success;
}
};
// Initialize CPMStar video ads
initCpmstarVideo();
// Ensure CPMStar API is initialized when available
if (typeof cpmstarAPI !== 'undefined') {
initCpmstarAPI();
} else {
var checkInterval = setInterval(function () {
if (typeof cpmstarAPI !== 'undefined') {
clearInterval(checkInterval);
initCpmstarAPI();
}
}, 100);
}
console.log("[cpmstar-ads.js] Module loaded successfully");
})();
+100
View File
@@ -0,0 +1,100 @@
// Local Ads Fallback Provider Module
// This module provides local image fallbacks for ads
//
// To enable clickable banners, set URLs in LOCAL_BANNER_LINKS:
// - Set to a URL string to make the banner clickable
// - Set to null to disable click functionality
// - Example: 0: 'https://example.com', 1: null, 2: 'https://another-site.com'
(function() {
console.log("[local-ads.js] Module loading...");
// Ensure global providers exist
window.videoAdProviders = window.videoAdProviders || {};
window.bannerAdProviders = window.bannerAdProviders || {};
// Local banner image paths (adblock-safe names)
const LOCAL_BANNER_IMAGES = {
0: 'ads/local-ads-assets/a.png', // 300x250
1: 'ads/local-ads-assets/b.png', // 728x90
2: 'ads/local-ads-assets/c.png' // 300x600
};
// Optional banner links (set to null to disable click functionality)
const LOCAL_BANNER_LINKS = {
0: 'https://kour.io/?utm_source=2v2&utm_medium=banner&utm_campaign=crosspromo', // 300x250
1: 'https://veck.io/?utm_source=2v2&utm_medium=banner&utm_campaign=crosspromo', // 728x90
2: 'https://growden.io/?utm_source=2v2&utm_medium=banner&utm_campaign=crosspromo' // 300x600
};
// Video ad provider implementation (simulated)
window.videoAdProviders.local = {
showMidroll: function(onSuccess, onFailure) {
console.log(`[local-ads.js] showMidroll called - failing immediately`);
onFailure();
},
showRewarded: function(onSuccess, onFailure) {
console.log(`[local-ads.js] showRewarded called - failing immediately`);
onFailure();
}
};
// Banner ad provider implementation
window.bannerAdProviders.local = {
displayBanner: async function(bannerType, container) {
console.log(`[local-ads.js] displayBanner for type: ${bannerType}`);
const imagePath = LOCAL_BANNER_IMAGES[bannerType];
if (!imagePath) {
console.log(`[local-ads.js] No local image for banner type ${bannerType}`);
return false;
}
// Get dimensions from global bannerDimensions
const dims = window.bannerDimensions[bannerType];
if (!dims) {
console.log(`[local-ads.js] No dimensions for banner type ${bannerType}`);
return false;
}
// Preload image and resolve true on success, false on failure
return await new Promise((resolve) => {
const testImg = new Image();
testImg.onload = function() {
const img = document.createElement('img');
img.src = imagePath;
img.style.width = dims.width;
img.style.height = dims.height;
img.style.display = 'block';
img.style.cursor = 'pointer';
img.alt = `Local Banner ${dims.width}x${dims.height}`;
// Add click handler if link is configured
const bannerLink = LOCAL_BANNER_LINKS[bannerType];
if (bannerLink) {
img.addEventListener('click', function() {
console.log(`[local-ads.js] Banner clicked, opening: ${bannerLink}`);
window.open(bannerLink, '_blank', 'noopener,noreferrer');
});
} else {
// Still show pointer cursor but no click action
img.style.cursor = 'default';
}
container.innerHTML = '';
container.appendChild(img);
console.log(`[local-ads.js] Local banner displayed: ${imagePath}${bannerLink ? ` (clickable: ${bannerLink})` : ' (no link)'}`);
resolve(true);
};
testImg.onerror = function() {
console.log('[local-ads.js] Local image blocked/missing');
resolve(false);
};
testImg.src = imagePath;
});
}
};
console.log("[local-ads.js] Module loaded successfully");
})();
Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

+476
View File
@@ -0,0 +1,476 @@
<!DOCTYPE html><html lang="en-us"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><meta http-equiv="X-UA-Compatible" content="IE=edge"><base href="/"><script>window.ENABLE_ADS = true;</script><title>2v2.io | Building Simulator, Battle Royale Shooting Game</title><meta name="description" content="2v2.io is a building simulator and battle royale shooting game. Team up, build fast, and outgun rivals to be the last squad standing. Play free online now!"><meta name="keywords" content="2v2.io, FPS io game, 2v2 io, 2v2 io game, online FPS, io games, 2v2, 2v2 io online game, multiplayer FPS"><meta name="author" content="Legion Platforms"><meta name="robots" content="index, follow"><meta name="theme-color" content="#0a0a0a"><meta name="touch-action" content="manipulation"><link rel="dns-prefetch" href="https://files.2v2.io/"><link rel="canonical" href="https://2v2.io/"><link rel="icon" href="https://2v2.io/favicon/favicon.ico" type="image/x-icon"><meta property="og:type" content="website"><meta property="og:title" content="2v2.io | Building Simulator, Battle Royale Shooting Game"><meta property="og:description" content="2v2.io is a building simulator and battle royale shooting game. Team up, build fast, and outgun rivals to be the last squad standing. Play free online now!"><meta property="og:image" content="https://2v2.io/background-og.webp"><meta property="og:image:type" content="image/webp"><meta property="og:image:secure_url" content="https://2v2.io/background-og.webp"><meta property="og:image:width" content="1280"><meta property="og:image:height" content="720"><meta property="og:image:alt" content="2v2.io browser FPS game"><meta property="og:url" content="https://2v2.io"><meta property="og:site_name" content="2v2.io"><meta property="og:locale" content="en_US"><meta property="og:updated_time" content="2025-05-08T12:00:00Z"><meta property="og:determiner" content="the"><script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-TRWMG587');</script><meta name="twitter:card" content="summary_large_image"><meta name="twitter:title" content="2v2.io | Building Simulator, Battle Royale Shooting Game"><meta name="twitter:description" content="2v2.io is a building simulator and battle royale shooting game. Team up, build fast, and outgun rivals to be the last squad standing. Play free online now!"><meta name="twitter:image" content="https://2v2.io/background-og.webp"><meta name="twitter:image:alt" content="2v2.io browser FPS game"><meta name="twitter:site" content="@2v2"><script async src="https://www.googletagmanager.com/gtag/js?id=G-ECPGZRERZQ"></script><script>window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-ECPGZRERZQ');</script><meta property="og:video" content="https://www.youtube.com/embed/gnHtAXm_ssk"><meta property="og:video:type" content="text/html"><meta property="og:video:width" content="1280"><meta property="og:video:height" content="720"><meta property="og:video:secure_url" content="https://www.youtube.com/embed/zAq8leZVH5c"><meta property="og:video:tag" content="2v2.io, FPS io game, 2v2 io, 2v2 io game, online FPS, io games, 2v2, 2v2 io online game, multiplayer FPS"><meta content="#1d9ae7" data-react-helmet="true" name="theme-color"><meta name="rating" content="general"><meta name="distribution" content="global"><link rel="alternate" hreflang="en" href="https://2v2.io/"><link rel="alternate" hreflang="x-default" href="https://2v2.io/"><script type="application/ld+json">{
"@context": "https://schema.org",
"@type": "VideoGame",
"name": "2v2.io",
"description": "2v2.io is a building simulator and battle royale shooting game. Team up, build fast, and outgun rivals to be the last squad standing. Play free online now!",
"image": "https://2v2.io/background-og.webp",
"author": {
"@type": "Organization",
"name": "Legion Platforms"
},
"url": "https://2v2.io",
"genre": ["Action", "Multiplayer", "Minecraft", "Shooter", "Free", ".io", "FPS"],
"keywords": "2v2, 2v2.io, 2v2, Online Game, Free to Play, 2v2.io, FPS io game, 2v2 io, 2v2 io game, online FPS, io games, 2v2, 2v2 io online game, multiplayer FPS",
"publisher": {
"@type": "Organization",
"name": "Legion Platforms"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.8",
"reviewCount": "500000"
},
"sameAs": [
"https://www.facebook.com/2v2",
"https://twitter.com/2v2",
"https://www.instagram.com/2v2"
]
}</script><script type="application/ld+json" id="website-jsonld">{
"@context": "https://schema.org",
"@type": "WebSite",
"url": "https://2v2.io/",
"name": "2v2.io",
"potentialAction": {
"@type": "SearchAction",
"target": "https://2v2.io/search?q={search_term_string}",
"query-input": "required name=search_term_string"
}
}</script><script type="application/ld+json" id="org-jsonld">{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Legion Platforms",
"url": "https://2v2.io",
"logo": "https://2v2.io/favicon/android-chrome-512x512.png",
"sameAs": [
"https://www.facebook.com/2v2",
"https://twitter.com/2v2",
"https://www.instagram.com/2v2"
]
}</script><link rel="preload" href="main.css" as="style" onload="this.rel='stylesheet'"><noscript><link rel="stylesheet" href="main.css"></noscript><link rel="preload" href="logo.webp" as="image" fetchpriority="high"><style>@media only screen and (max-width: 768px) {
body {
overflow: hidden;
}
}</style><meta name="mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><meta name="apple-mobile-web-app-title" content="2v2.io"><meta name="application-name" content="2v2.io"><meta name="format-detection" content="telephone=no"><link rel="manifest" href="manifest.json"><link rel="apple-touch-startup-image" href="/splash/apple-splash-1125x2436.png" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3)"><link rel="dns-prefetch" href="//www.google-analytics.com"><link rel="preconnect" href="https://www.google-analytics.com" crossorigin><link rel="preconnect" href="https://2v2.io" crossorigin><link rel="preconnect" href="https://files.2v2.io" crossorigin><link rel="preconnect" href="https://www.googletagmanager.com" crossorigin><link rel="preconnect" href="https://www.youtube.com" crossorigin><meta http-equiv="Permissions-Policy" content="accelerometer=(), camera=(), microphone=(), geolocation=()"><link rel="sitemap" type="application/xml" title="Sitemap" href="https://2v2.io/sitemap.xml"><meta name="robots" content="index, follow"><meta name="googlebot" content="index, follow"><meta name="msvalidate.01" content="CA12CDD2C731B36A8DF91876E3CD78F3"><meta property="og:see_also" content="https://www.youtube.com/@2v2"><meta property="og:see_also" content="https://twitter.com/2v2"><meta property="og:see_also" content="https://www.instagram.com/2v2"><meta property="og:see_also" content="https://www.facebook.com/2v2"><meta http-equiv="Cache-Control" content="public, max-age=600"><meta name="msapplication-TileColor" content="#2d3030"><meta name="msapplication-config" content="https://2v2.io/browserconfig.xml"><link rel="icon" sizes="192x192" href="https://2v2.io/favicon/android-chrome-192x192.png"><link rel="icon" sizes="512x512" href="https://2v2.io/favicon/android-chrome-512x512.png"><link rel="apple-touch-icon" href="https://2v2.io/favicon/apple-touch-icon.png"><script>const url = window.location.href;
const urlParams = new URLSearchParams(window.location.search);
var unityLoadingIsSDKDependant = false;
let sdkScriptSrc = "sdk.js";
// Check query parameters first (more robust), then fallback to URL substring matching
if (url.includes("discord")) {
sdkScriptSrc = "discord_sdk.js";
unityLoadingIsSDKDependant = true;
} else if (urlParams.has("cg") || url.includes("crazygames")) {
window.ENABLE_ADS = false;
sdkScriptSrc = "cg_sdk.js";
// } else if (urlParams.has("poki") || url.includes("poki-gdn.com") || url.includes(".poki.")) {
// sdkScriptSrc = "poki_sdk.js";
} else if (urlParams.has("yandex") || url.includes("/yandex")) {
// Add Yandex SDK detection
sdkScriptSrc = "yandex_sdk.js";
} else if (urlParams.has("msn") || url.includes("/msn")) {
// Microsoft Start integration
sdkScriptSrc = "msn_sdk.js";
}
const sdkScript = document.createElement("script");
sdkScript.src = `${sdkScriptSrc}?v=8`; // update this when any sdk updates
document.head.appendChild(sdkScript);
function waitForSDKThenStart(callback) {
const maxWait = 10000; //ms
const interval = 10; //ms
let waited = 0;
if (!unityLoadingIsSDKDependant) {
callback();
return;
}
const check = () => {
if (window.SDK && typeof window.SDK.getBuildURL === "function") {
callback();
} else if (waited >= maxWait) {
console.warn("SDK not available in time, proceeding anyway.");
callback();
} else {
waited += interval;
setTimeout(check, interval);
}
};
check();
}</script><script>(function () {
function canonicalHrefFor(locationObj) {
return locationObj.origin + locationObj.pathname; // ignore query/fragment for canonical
}
function setCanonicalAndOgUrl() {
var href = canonicalHrefFor(window.location);
var canonical = document.querySelector('link[rel="canonical"]');
if (!canonical) {
canonical = document.createElement('link');
canonical.setAttribute('rel', 'canonical');
document.head.appendChild(canonical);
}
canonical.setAttribute('href', href);
var ogUrl = document.querySelector('meta[property="og:url"]');
if (ogUrl) ogUrl.setAttribute('content', href);
}
setCanonicalAndOgUrl();
var _ps = history.pushState;
history.pushState = function () { _ps.apply(history, arguments); setCanonicalAndOgUrl(); };
var _rs = history.replaceState;
history.replaceState = function () { _rs.apply(history, arguments); setCanonicalAndOgUrl(); };
window.addEventListener('popstate', setCanonicalAndOgUrl);
})();</script><script>(function () {
function toTitleCase(text) {
return text.replace(/[-_]+/g, ' ').replace(/\b\w/g, function (c) { return c.toUpperCase(); });
}
function injectBreadcrumbs() {
var path = window.location.pathname.replace(/\/$/, '');
if (path === '' || path === '/') {
var existingHome = document.getElementById('breadcrumbs-jsonld');
if (existingHome) existingHome.remove();
return;
}
var items = [ { name: 'Home', url: window.location.origin + '/' } ];
if (path === '/shop') {
items.push({ name: 'Shop', url: window.location.origin + '/shop' });
} else if (path === '/inventory') {
items.push({ name: 'Inventory', url: window.location.origin + '/inventory' });
} else if (path === '/classes') {
items.push({ name: 'Classes', url: window.location.origin + '/classes' });
} else if (path === '/play-with-friends') {
items.push({ name: 'Play With Friends', url: window.location.origin + '/play-with-friends' });
} else if (path === '/servers') {
items.push({ name: 'Servers', url: window.location.origin + '/servers' });
} else if (path === '/settings') {
items.push({ name: 'Settings', url: window.location.origin + '/settings' });
} else if (path === '/login') {
items.push({ name: 'Login', url: window.location.origin + '/login' });
} else if (path === '/free') {
items.push({ name: 'Free', url: window.location.origin + '/free' });
} else if (/^\/profile\//.test(path)) {
var username = path.split('/').pop();
items.push({ name: 'Profile', url: window.location.origin + '/profile/' });
items.push({ name: username, url: window.location.origin + path });
} else if (/^\/inspect_/.test(path)) {
items.push({ name: 'Inspect Item', url: window.location.origin + path });
} else if (/^\/bundle_/.test(path)) {
items.push({ name: 'Bundle', url: window.location.origin + path });
} else {
var seg = path.split('/').pop();
items.push({ name: toTitleCase(seg), url: window.location.origin + path });
}
var breadcrumbs = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
'itemListElement': items.map(function (item, index) {
return {
'@type': 'ListItem',
'position': index + 1,
'name': item.name,
'item': item.url
};
})
};
var existing = document.getElementById('breadcrumbs-jsonld');
if (existing) existing.remove();
var script = document.createElement('script');
script.type = 'application/ld+json';
script.id = 'breadcrumbs-jsonld';
script.text = JSON.stringify(breadcrumbs);
document.head.appendChild(script);
}
injectBreadcrumbs();
var _ps = history.pushState;
history.pushState = function () { _ps.apply(history, arguments); injectBreadcrumbs(); };
var _rs = history.replaceState;
history.replaceState = function () { _rs.apply(history, arguments); injectBreadcrumbs(); };
window.addEventListener('popstate', injectBreadcrumbs);
})();</script><script>(function () {
var routeToMeta = {
'/': {
title: '2v2.io | Building Simulator, Battle Royale Shooting Game',
description: '2v2.io is a building simulator and battle royale shooting game. Team up, build fast, and outgun rivals to be the last squad standing. Play free online now!'
},
'/shop': {
title: '2v2.io Shop | Items, Bundles & Cosmetics',
description: 'Browse the 2v2.io shop to unlock skins, bundles, and cosmetics. Earn, purchase, and equip items to stand out in battle.'
},
'/inventory': {
title: '2v2.io Inventory | Manage Your Skins & Items',
description: 'View and manage your unlocked items, skins, and cosmetics for 2v2.io. Equip your favorites and jump back into action.'
},
'/classes': {
title: '2v2.io Classes | Choose Your Playstyle',
description: 'Discover 2v2.io classes and tailor your loadout to your preferred playstyle.'
},
'/play-with-friends': {
title: '2v2.io | Play With Friends',
description: 'Create or join rooms and play 2v2.io with friends. Share your room code to squad up quickly.'
},
'/servers': {
title: '2v2.io Servers | Regions & Matchmaking',
description: 'Pick the best 2v2.io server region for lower ping and better matchmaking.'
},
'/settings': {
title: '2v2.io Settings | Controls & Graphics',
description: 'Tune your controls, graphics, and gameplay settings for the best 2v2.io experience.'
},
'/login': {
title: 'Login | 2v2.io',
description: 'Log in to your 2v2.io account to sync progress and items.'
},
'/free': {
title: 'Free Rewards | 2v2.io',
description: 'Claim your free rewards and bonuses in 2v2.io.'
}
};
function setMeta(meta) {
if (!meta) return;
if (meta.title) document.title = meta.title;
var desc = document.querySelector('meta[name="description"]');
if (desc && meta.description) desc.setAttribute('content', meta.description);
var ogTitle = document.querySelector('meta[property="og:title"]');
if (ogTitle && meta.title) ogTitle.setAttribute('content', meta.title);
var twTitle = document.querySelector('meta[name="twitter:title"]');
if (twTitle && meta.title) twTitle.setAttribute('content', meta.title);
var ogDesc = document.querySelector('meta[property="og:description"]');
if (ogDesc && meta.description) ogDesc.setAttribute('content', meta.description);
var twDesc = document.querySelector('meta[name="twitter:description"]');
if (twDesc && meta.description) twDesc.setAttribute('content', meta.description);
}
function updateRouteMeta() {
var path = window.location.pathname.replace(/\/$/, '') || '/';
var meta = routeToMeta[path];
// Pattern routes
if (!meta) {
if (/^\/inspect_/.test(path)) {
meta = {
title: 'Inspect Item | 2v2.io',
description: 'View item details and shareable links in 2v2.io.'
};
} else if (/^\/bundle_/.test(path)) {
meta = {
title: 'Bundle | 2v2.io',
description: 'Preview and purchase bundles in the 2v2.io shop.'
};
} else if (/^\/profile\//.test(path)) {
var username = path.split('/').pop();
meta = {
title: username ? (username + ' | Profile | 2v2.io') : 'Profile | 2v2.io',
description: 'View player profiles and stats in 2v2.io.'
};
}
}
setMeta(meta);
}
updateRouteMeta();
var _ps = history.pushState;
history.pushState = function () { _ps.apply(history, arguments); updateRouteMeta(); };
var _rs = history.replaceState;
history.replaceState = function () { _rs.apply(history, arguments); updateRouteMeta(); };
window.addEventListener('popstate', updateRouteMeta);
})();</script></head><body style="background-color: black; color: black;"><div id="videoad"></div><noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-TRWMG587" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript><div id="unity-container"><canvas id="unity-canvas"></canvas></div><div id="unity-loading-container"><div id="loading-percentage">1%</div><img src="logo.webp" class="logo" alt="2v2.io Logo" decoding="async" fetchpriority="high"><div id="unity-loading-bar"><div id="unity-loading-bar-inner"></div></div></div><script type="application/ld+json" id="faq-jsonld">{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Is 2v2.io a free FPS game?",
"acceptedAnswer": { "@type": "Answer", "text": "Yes. 2v2.io is free to play in your browser with no download required." }
},
{
"@type": "Question",
"name": "Can I play 2v2.io with friends?",
"acceptedAnswer": { "@type": "Answer", "text": "Yes. Create a room in Play With Friends and share the code for others to join instantly." }
},
{
"@type": "Question",
"name": "Does 2v2.io have items and skins?",
"acceptedAnswer": { "@type": "Answer", "text": "You can unlock and equip skins, items, and bundles in the Shop and Inventory." }
}
]
}</script><script>// Safe build URL
function getSafeBuildURL() {
try {
if (typeof window.SDK?.getBuildURL === "function") {
return window.SDK.getBuildURL();
}
} catch (e) {
console.warn("SDK.getBuildURL failed:", e);
}
return "Build"; // fallback
}
// Safe Streaming Assets URL
function getSafeStreamingAssetsUrl() {
try {
if (typeof window.SDK?.getStreamingAssetsUrl === "function") {
return window.SDK.getStreamingAssetsUrl();
}
} catch (e) {
console.warn("SDK.getStreamingAssetsUrl failed:", e);
}
return "/StreamingAssets"; // fallback
}
// Safe Static Resolution Scale Multi
function getSafeStaticResolutionScaleMulti() {
try {
if (typeof window.SDK?.getStaticResolutionScaleMulti === "function") {
return window.SDK.getStaticResolutionScaleMulti();
}
} catch (e) {
console.warn("SDK.getStaticResolutionScaleMulti failed:", e);
}
return 1.00; // fallback
}
waitForSDKThenStart(() => {
var buildUrl = getSafeBuildURL();
var loaderUrl = buildUrl + "/6f378362abf9416ee1d09b1b8bba5e6a.loader.js";
// Determine if we're on mobile
var isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
var safeStaticResolutionScaleMulti = getSafeStaticResolutionScaleMulti();
var config = {
dataUrl: buildUrl + "/71a45c7b34a597f90c357bb1c4bac865.data.br",
frameworkUrl: buildUrl + "/2369d1a903ef1992e469da7cedc792de.framework.js.br",
codeUrl: buildUrl + "/66321962c8ffc3be6d5594e349d2ebb4.wasm.br",
streamingAssetsUrl: getSafeStreamingAssetsUrl(),
companyName: "LEGiON Platforms",
productName: "2v2.io",
productVersion: "15z70982016",
autoSyncPersistentDataPath: true,
devicePixelRatio: 1.0 * safeStaticResolutionScaleMulti // default value, will be updated below
};
// Default DPR based on platform
var defaultDPR = safeStaticResolutionScaleMulti * (isMobile ? 1.3 : 1.0);
// Attempt to read the ResolutionScale from localStorage
var lsValue = localStorage.getItem("ResolutionScale");
var parsedValue = parseFloat(lsValue);
// If localStorage contains a valid number, use it; otherwise use the default.
var finalResolutionScaleValue = !isNaN(parsedValue) ? parsedValue : defaultDPR;
config.devicePixelRatio = finalResolutionScaleValue;
if (isMobile) {
document.addEventListener('touchmove', function(event) {
if (event.scale !== 1) {
event.preventDefault();
}
}, { passive: false });
}
var canvas = document.querySelector("#unity-canvas");
var loadingContainer = document.querySelector("#unity-loading-container");
var loadingBar = document.querySelector("#unity-loading-bar-inner");
var loadingPercentageElem = document.querySelector("#loading-percentage");
//progress simulation for better UX
const minProg = 51;
const maxProg = 99;
var targetProgress = minProg;
function updateProgress() {
targetProgress += 0.12;
targetProgress = Math.min(Math.max(targetProgress, minProg), maxProg);
loadingBar.style.width = targetProgress + "%";
loadingPercentageElem.textContent = Math.floor(targetProgress) + "%";
}
// Start the interval to update progress every 100ms
var progressInterval = setInterval(updateProgress, 100);
if (window.SDK && typeof window.SDK.loadingStart === 'function') {
window.SDK.loadingStart();
}
function startUnity() {
createUnityInstance(canvas, config, function(progress) {
var actualProgress = Math.min(Math.max(progress * 100, minProg), maxProg);
if (actualProgress > targetProgress) {
targetProgress = actualProgress;
}
}).then(function(unityInstance) {
window.unityInstance = unityInstance;
if (window.SDK && typeof window.SDK.loadingEnd === 'function') {
window.SDK.loadingEnd();
}
// Ensure the loading bar fills to 100% smoothly
targetProgress = 100;
loadingBar.style.width = "100%";
loadingPercentageElem.textContent = "100%";
clearInterval(progressInterval);
loadingContainer?.remove();
}).catch(function(message) {
clearInterval(progressInterval);
console.error(message);
});
}
function loadUnityLoaderWithRetry(url, attempt, maxAttempts) {
var script = document.createElement("script");
var cacheBust = (url.indexOf('?') === -1 ? '?' : '&') + 'retry=' + attempt + '&t=' + Date.now();
script.src = url + cacheBust;
script.addEventListener('load', startUnity, { once: true });
script.addEventListener('error', function () {
console.warn("Unity loader failed (attempt", attempt, "of", maxAttempts, ")", url);
if (attempt < maxAttempts) {
var nextAttempt = attempt + 1;
var retryDelayMs = Math.min(1000 * attempt, 3000);
loadingPercentageElem.textContent = 'Retrying… (' + nextAttempt + '/' + maxAttempts + ')';
setTimeout(function () {
loadUnityLoaderWithRetry(url, nextAttempt, maxAttempts);
}, retryDelayMs);
} else {
clearInterval(progressInterval);
loadingBar.style.width = "100%";
loadingPercentageElem.textContent = 'Load failed. Please refresh.';
console.error("Unity loader failed after", maxAttempts, "attempts:", url);
}
}, { once: true });
document.body.appendChild(script);
}
if (typeof window.createUnityInstance === 'function') {
startUnity();
} else {
var existingLoader = document.querySelector('script[src="' + loaderUrl + '"]');
if (existingLoader) {
existingLoader.addEventListener('load', startUnity, { once: true });
} else {
loadUnityLoaderWithRetry(loaderUrl, 1, 3);
}
}
});</script><noscript>Please enable JavaScript to run this game! Learn how <a href="https://support.google.com/adsense/answer/12654" target="_blank" rel="noopener noreferrer">here</a>.</noscript><script src="s/cursor.js" defer="defer"></script><script src="s/pwa.js" defer="defer"></script><script src="s/utils.js" defer="defer"></script><script src="s/analytics.js" defer="defer"></script><script src="s/pinger.js" defer="defer"></script><script src="s/exit-intent.js" defer="defer"></script><script>if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('s/sw.js');
});
}</script><script defer src="https://static.cloudflareinsights.com/beacon.min.js/vcd15cbe7772f49c399c6a5babf22c1241717689176015" integrity="sha512-ZpsOmlRQV6y907TI0dKBHq9Md29nnaEIPlkf84rnaERnq6zvWvPUqr2ft8M1aS28oN72PdrCzSjY4U6VaAw1EQ==" data-cf-beacon='{"version":"2024.11.0","token":"1bfb393b72344e7f9d5c2908cabe8ce9","r":1,"server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body></html>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+133
View File
@@ -0,0 +1,133 @@
* {
padding: 0;
margin: 0;
}
html {
/* fix mobile viewport menu bar on iOS */
height: -webkit-fill-available;
}
body {
height: auto; /* allow root scroll on iOS */
/* fix mobile viewport menu bar on iOS */
min-height: -webkit-fill-available;
width: 100%;
text-align: center;
}
/* Use dynamic viewport height on mobile browsers that hide URL bar */
:root {
/* set via JS: --vh = 1% of innerHeight */
}
#unity-container,
#unity-canvas {
height: calc(var(--vh, 1vh) * 100);
}
#unity-container {
position: fixed; /* fixed so root can scroll under it on iOS */
left: 0px;
top: 0px;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: #29404d;
touch-action: pan-y; /* allow vertical pan so Safari can collapse bars */
}
#unity-canvas {
width: 100%;
height: 100%;
background-color: #fff;
}
#unity-loading-container {
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
background-color: #292c2f;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 10px;
opacity: 1;
visibility: visible;
background-image: url('bg.webp');
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
}
#unity-loading-container.finished {
opacity: 0;
visibility: collapse;
}
.logo {
user-select: none;
width: 50%;
max-width: 800px;
position: absolute;
bottom: 9%;
left: 0.5%;
transform-origin: bottom left;
}
#unity-loading-bar {
position: absolute;
height: 36px;
background: #00000014;
background-size: 200% 100%;
border: 5px solid #ffffff24;
border-radius: 14px;
overflow: hidden;
animation: shimmer 2s infinite linear;
bottom: 20px;
right: 120px;
left: 20px;
}
#loading-percentage {
position: absolute;
right: 16px;
bottom: 18px;
z-index: 20;
font-family: system-ui, sans-serif;
font-size: 40px;
font-weight: 900;
color: #fff;
user-select: none;
}
#unity-loading-bar-inner {
position: absolute;
left: 0%;
top: 0%;
width: 1%;
height: 100%;
background: linear-gradient(226deg, #1180ff, #0066cc); /* light to dark blue */
box-shadow: 0 0 27px #3cc6ff;
}
@keyframes shimmer {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
+21
View File
@@ -0,0 +1,21 @@
// Analytics helper functions for Unity WebGL
// Report scene playtime to GA4
// sceneName: string - The name of the scene (e.g., "map_city")
// gameMode: string - The game mode (e.g., "deathmatch")
// duration: number - Duration in seconds
window.reportScenePlaytime = function (sceneName, gameMode, duration) {
if (typeof gtag === 'function') {
gtag('event', 'scene_playtime', {
'scene_name': sceneName,
'game_mode': gameMode,
'duration': duration,
'value': duration // Useful for "average" metric calculations
});
// console.log(`[Analytics] Reported scene playtime: ${sceneName} (${gameMode}) - ${duration}s`);
} else {
console.warn("[Analytics] gtag not found, cannot report playtime.");
}
};
+213
View File
@@ -0,0 +1,213 @@
//v9
document.addEventListener('DOMContentLoaded', () => {
const canvas = document.getElementById('unity-canvas');
if (!canvas) {
console.error("Canvas element with id='unity-canvas' not found!");
return;
}
// Track lock state
window.isPointerLocked = false;
// Track if unadjustedMovement is supported (assume yes until proven otherwise)
let unadjustedMovementSupported = true;
// Utility: Check if pointer lock API is available
function hasPointerLockAPI() {
return !!(canvas.requestPointerLock ||
canvas.webkitRequestPointerLock ||
canvas.mozRequestPointerLock);
}
// Safari detection
function isSafari() {
// This UA check excludes Chrome on iOS
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
}
// Called when pointer lock state changes
function onPointerLockChange() {
const doc = document;
const locked = (
doc.pointerLockElement === canvas ||
doc.webkitPointerLockElement === canvas ||
doc.mozPointerLockElement === canvas
);
window.isPointerLocked = locked;
if (locked) {
console.log("----cursor locking----");
canvas.style.cursor = "none";
} else {
console.log("----cursor unlocking----");
canvas.style.cursor = "default";
}
sendLockStateToUnity(locked);
}
// Called when pointer lock errors out
function onPointerLockError(event) {
console.error("----pointer lock error----", event);
sendLockStateToUnity(false);
// For macOS Safari: add a one-time mousedown fallback if pointer isn't locked.
if (isSafari() && !window.isPointerLocked) {
console.log("----Safari fallback: adding one-time mousedown listener for pointer lock----");
addOneTimeMouseDownListener();
}
}
// Helper to add a one-time mousedown listener for fallback pointer lock
function addOneTimeMouseDownListener() {
// Use a named function so we can remove it if needed
function fallbackMouseDown(e) {
console.log("----Fallback mousedown triggered, retrying pointer lock----");
canvas.removeEventListener('mousedown', fallbackMouseDown);
lockPointerNow();
}
canvas.addEventListener('mousedown', fallbackMouseDown, false);
}
// Listen for pointer lock changes/errors (all vendor prefixes)
document.addEventListener('pointerlockchange', onPointerLockChange, false);
document.addEventListener('webkitpointerlockchange', onPointerLockChange, false);
document.addEventListener('mozpointerlockchange', onPointerLockChange, false);
document.addEventListener('pointerlockerror', onPointerLockError, false);
document.addEventListener('webkitpointerlockerror', onPointerLockError, false);
document.addEventListener('mozpointerlockerror', onPointerLockError, false);
// Send pointer lock state back to Unity
function sendLockStateToUnity(isLocked) {
if (typeof unityInstance !== 'undefined' && unityInstance && unityInstance.SendMessage) {
try {
unityInstance.SendMessage('CursorController', 'OnLockChange', isLocked ? 'true' : 'false');
} catch (e) {
console.warn("----Failed to send lock state to Unity----", e);
}
}
}
// Request pointer lock without options (basic fallback)
function requestPointerLockBasic() {
if (canvas.requestPointerLock) {
canvas.requestPointerLock();
} else if (canvas.webkitRequestPointerLock) {
canvas.webkitRequestPointerLock();
} else if (canvas.mozRequestPointerLock) {
canvas.mozRequestPointerLock();
}
}
// Request pointer lock with unadjustedMovement option
async function requestPointerLockWithOptions() {
if (canvas.requestPointerLock) {
return canvas.requestPointerLock({ unadjustedMovement: true });
} else if (canvas.webkitRequestPointerLock) {
return canvas.webkitRequestPointerLock({ unadjustedMovement: true });
} else if (canvas.mozRequestPointerLock) {
return canvas.mozRequestPointerLock({ unadjustedMovement: true });
}
}
// Attempt to lock pointer
async function lockPointerNow() {
console.log("----attempting pointer lock----");
if (!hasPointerLockAPI()) {
console.warn("----Pointer lock API not supported in this browser----");
sendLockStateToUnity(false);
return;
}
// If Safari, skip the options param entirely
if (isSafari()) {
console.log("----Safari detected; skipping unadjustedMovement param----");
try {
requestPointerLockBasic();
} catch (err) {
console.error("----Safari pointer lock request failed----", err);
sendLockStateToUnity(false);
}
return;
}
// Non-Safari browsers
// Check permissions if available (non-blocking, just for logging)
if (navigator.permissions && navigator.permissions.query) {
try {
const permStatus = await navigator.permissions.query({ name: 'pointer-lock' });
console.log("----pointer lock permission state: " + permStatus.state + "----");
} catch (permError) {
// Permissions API may not support pointer-lock query - that's fine
console.log("----Permissions API query not supported for pointer-lock----");
}
}
// Try with unadjustedMovement if supported, otherwise use basic request
if (unadjustedMovementSupported) {
try {
await requestPointerLockWithOptions();
console.log("----pointer lock request initiated with unadjustedMovement----");
} catch (err) {
// Check if error is due to unadjustedMovement not being supported
if (err && (err.name === 'NotSupportedError' || err.name === 'TypeError')) {
console.warn("----unadjustedMovement not supported, falling back to basic pointer lock----");
unadjustedMovementSupported = false;
// Retry immediately with basic request
try {
requestPointerLockBasic();
console.log("----pointer lock request initiated (basic fallback)----");
} catch (fallbackErr) {
console.error("----pointer lock request failed (basic fallback)----", fallbackErr);
sendLockStateToUnity(false);
}
} else {
console.error("----pointer lock request failed (non-Safari)----", err);
sendLockStateToUnity(false);
}
}
} else {
// We already know unadjustedMovement isn't supported, use basic request
try {
requestPointerLockBasic();
console.log("----pointer lock request initiated (basic, cached)----");
} catch (err) {
console.error("----pointer lock request failed (basic)----", err);
sendLockStateToUnity(false);
}
}
}
// Attempt to unlock pointer
function unlockPointerNow() {
console.log("----attempting pointer unlock----");
const doc = document;
if (doc.pointerLockElement === canvas ||
doc.webkitPointerLockElement === canvas ||
doc.mozpointerLockElement === canvas) {
if (doc.exitPointerLock) {
doc.exitPointerLock();
} else if (doc.webkitExitPointerLock) {
doc.webkitExitPointerLock();
} else if (doc.mozExitPointerLock) {
doc.mozExitPointerLock();
}
console.log("----pointer unlock request sent----");
} else {
console.warn("----pointer is not locked to the canvas----");
sendLockStateToUnity(false);
}
}
// Expose lock/unlock globally for Unity ExternalCall usage
window.lockPointerNow = lockPointerNow;
window.unlockPointerNow = unlockPointerNow;
// Expose a function to change cursor style
window.setCursor = function setCursor(cursorStyle) {
if (canvas) {
canvas.style.cursor = cursorStyle;
}
};
});
+139
View File
@@ -0,0 +1,139 @@
// Exit intent detection based on mouse movement patterns
let lastMousePosition = { x: 0, y: 0 };
let lastMouseMoveTime = 0;
let lastActivityTime = Date.now();
let exitIntentDetected = false;
let cornerDetectionTimeout = null;
let afkTimeout = null;
// Configuration
const CORNER_THRESHOLD = 50; // pixels from corner to consider it a corner
const MOVEMENT_THRESHOLD = 3; // minimum pixels to consider it a movement
const TIME_THRESHOLD = 3000; // 3 seconds to detect slow movement
const SLOW_MOVEMENT_THRESHOLD = 100; // pixels per second to consider it slow movement
const AFK_THRESHOLD = 240000; // 4 minutes in milliseconds
// Check if device is a touch-only mobile device
function isTouchOnlyDevice() {
// Check if device has touch capability
const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
// Check if device is a mobile device (excluding tablets and laptops with touch)
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
// Check if device has a keyboard (laptops/desktops)
const hasKeyboard = navigator.keyboard !== undefined;
// Device is touch-only if it has touch, is mobile, and doesn't have a keyboard
return hasTouch && isMobile && !hasKeyboard;
}
function sendExitIntentToUnity() {
if (typeof unityInstance !== 'undefined') {
unityInstance.SendMessage('WebUtils', 'OnExitIntent');
}
}
function initExitIntentDetection() {
// Only initialize if not a touch-only device
if (isTouchOnlyDevice()) {
return;
}
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseleave', handleMouseLeave);
document.addEventListener('keydown', handleActivity);
document.addEventListener('click', handleActivity);
// Start AFK timer
resetAFKTimer();
}
function handleActivity() {
lastActivityTime = Date.now();
resetAFKTimer();
}
function resetAFKTimer() {
// Clear existing AFK timeout if any
if (afkTimeout) {
clearTimeout(afkTimeout);
}
// Set new AFK timeout
afkTimeout = setTimeout(() => {
if (!exitIntentDetected) {
exitIntentDetected = true;
sendExitIntentToUnity();
}
}, AFK_THRESHOLD);
}
function handleMouseMove(e) {
const currentTime = Date.now();
const timeDiff = currentTime - lastMouseMoveTime;
// Update last activity time
lastActivityTime = currentTime;
resetAFKTimer();
// Calculate movement speed
const distance = Math.sqrt(
Math.pow(e.clientX - lastMousePosition.x, 2) +
Math.pow(e.clientY - lastMousePosition.y, 2)
);
const speed = distance / (timeDiff / 1000); // pixels per second
// Check if mouse is in a corner and moving slowly
const isInCorner = isMouseInCorner(e.clientX, e.clientY);
const isMovingSlowly = speed < SLOW_MOVEMENT_THRESHOLD && distance > MOVEMENT_THRESHOLD;
if (isInCorner && isMovingSlowly && !exitIntentDetected) {
if (!cornerDetectionTimeout) {
cornerDetectionTimeout = setTimeout(() => {
if (isMouseInCorner(e.clientX, e.clientY)) {
exitIntentDetected = true;
sendExitIntentToUnity();
}
}, TIME_THRESHOLD);
}
} else if (!isInCorner || !isMovingSlowly) {
// Reset detection if mouse moves out of corner or moves too fast
if (cornerDetectionTimeout) {
clearTimeout(cornerDetectionTimeout);
cornerDetectionTimeout = null;
}
exitIntentDetected = false;
}
// Update last position and time
lastMousePosition = { x: e.clientX, y: e.clientY };
lastMouseMoveTime = currentTime;
}
function handleMouseLeave(e) {
// If mouse leaves through the top, consider it an exit intent
if (e.clientY <= 0) {
sendExitIntentToUnity();
}
}
function isMouseInCorner(x, y) {
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
// Check top-left corner
if (x <= CORNER_THRESHOLD && y <= CORNER_THRESHOLD) {
return true;
}
// Check top-right corner
if (x >= windowWidth - CORNER_THRESHOLD && y <= CORNER_THRESHOLD) {
return true;
}
return false;
}
// Initialize when the script loads
initExitIntentDetection();
+43
View File
@@ -0,0 +1,43 @@
const servers = [
'fra-1.2v2.io',
'usa-1.2v2.io'
];
function getRandomServer() {
return servers[Math.floor(Math.random() * servers.length)];
}
async function fetchBestRegion() {
const maxAttempts = 3;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const randomServer = getRandomServer();
try {
const response = await fetch(`https://${randomServer}/api/init`);
if (response.ok) {
const data = await response.json();
const bestRegion = data.region; // use the "region" from the response JSON
console.log(`Attempt ${attempt}: Best region from server ${randomServer}: ${bestRegion}`);
return bestRegion;
} else {
console.error(`Attempt ${attempt}: Server ${randomServer} responded with status: ${response.status}`);
}
} catch (error) {
console.error(`Attempt ${attempt}: Error fetching best region from ${randomServer}:`, error);
}
}
console.error('All attempts failed. Falling back to default: USA');
return 'USA';
}
const cachedBestServer = localStorage.getItem('bestServer');
if (!cachedBestServer) {
fetchBestRegion().then(bestRegion => {
console.log('Best region to connect (Saving to localStorage):', bestRegion);
localStorage.setItem('bestServer', bestRegion);
}).catch(error => {
console.error('Error during region fetching:', error);
});
} else {
console.log('Using cached best server:', cachedBestServer);
}
+131
View File
@@ -0,0 +1,131 @@
window.deferredInstall = null;
window.addEventListener('beforeinstallprompt', (e) => {
window.deferredInstall = e;
console.log('PWA install prompt has been saved.');
if (typeof unityInstance !== 'undefined' && unityInstance != null) {
unityInstance.SendMessage('PWAManager', 'OnPwaPromptAvailabilityChanged', window.deferredInstall ? "1" : "0");
}
});
// Mobile fullscreen and viewport handling
(function () {
const ua = navigator.userAgent || '';
const isIOS = /iP(ad|hone|od)/.test(ua) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
const isAndroid = /Android/i.test(ua);
function enterFullscreen() {
const canvas = document.getElementById('unity-canvas') || document.documentElement;
// Prefer Unity's API when available
if (typeof window.unityInstance !== 'undefined' && window.unityInstance && typeof window.unityInstance.SetFullscreen === 'function') {
try { window.unityInstance.SetFullscreen(1); return; } catch (e) { /* fallback below */ }
}
const req = canvas.requestFullscreen || canvas.webkitRequestFullscreen || canvas.msRequestFullscreen || canvas.mozRequestFullScreen;
if (req) {
try { req.call(canvas); } catch (e) { console.warn('requestFullscreen failed:', e); }
}
}
function setupAndroidAutoFullscreenOnGesture() {
if (!isAndroid) return;
// Create a transparent full-screen overlay button to guarantee a user gesture
const overlayId = 'android-fs-overlay';
if (document.getElementById(overlayId)) return;
const overlay = document.createElement('button');
overlay.id = overlayId;
overlay.type = 'button';
overlay.setAttribute('aria-label', 'Enter Fullscreen');
overlay.style.cssText = 'position:fixed;inset:0;z-index:2147483647;background:transparent;border:0;margin:0;padding:0;width:100%;height:100%;opacity:0;touch-action:manipulation;';
const onTap = () => {
enterFullscreen();
if (screen.orientation && screen.orientation.lock) {
try { screen.orientation.lock('landscape'); } catch (_) {}
}
// Remove overlay after attempting fullscreen
requestAnimationFrame(() => {
overlay.remove();
});
};
overlay.addEventListener('pointerdown', onTap, { once: true });
overlay.addEventListener('click', onTap, { once: true });
overlay.addEventListener('touchend', onTap, { once: true });
document.addEventListener('DOMContentLoaded', () => {
document.body.appendChild(overlay);
});
}
function setupIOSDynamicViewportAndBarCollapse() {
if (!isIOS) return;
// Allow root scroll so Safari can collapse the URL bar.
// Do not block touchmove anywhere.
document.documentElement.style.overflowY = 'scroll';
document.documentElement.style.webkitOverflowScrolling = 'touch';
document.body.style.overflowY = 'visible';
// Dynamic 1% viewport unit to handle iOS chrome changes
const setVh = () => {
const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', vh + 'px');
};
setVh();
window.addEventListener('resize', setVh);
if (window.visualViewport && typeof window.visualViewport.addEventListener === 'function') {
window.visualViewport.addEventListener('resize', setVh);
}
document.addEventListener('visibilitychange', setVh);
// Ensure extra scroll area exists at the bottom
const ensureFiller = () => {
let filler = document.getElementById('ios-filler');
if (!filler) {
filler = document.createElement('div');
filler.id = 'ios-filler';
filler.style.cssText = 'height:140px;width:1px;opacity:0;pointer-events:none;';
document.body.appendChild(filler);
}
};
ensureFiller();
// Nudge scroll on load and retries to encourage bar collapse
let nudgeCount = 0;
const tryNudge = () => {
ensureFiller();
try { window.scrollTo(0, 1); } catch (e) {}
nudgeCount++;
if (nudgeCount < 5) setTimeout(tryNudge, 300);
};
window.addEventListener('load', () => setTimeout(tryNudge, 400));
window.addEventListener('orientationchange', () => setTimeout(tryNudge, 400));
window.addEventListener('resize', () => setTimeout(tryNudge, 400));
}
function applySizing() {
const container = document.getElementById('unity-container');
const canvas = document.getElementById('unity-canvas');
if (!container || !canvas) return;
// Prefer visualViewport to avoid initial 50% height bug on iOS
if (window.visualViewport && typeof window.visualViewport.height === 'number') {
container.style.height = window.visualViewport.height + 'px';
canvas.style.height = '100%';
} else {
const vhVar = getComputedStyle(document.documentElement).getPropertyValue('--vh');
if (vhVar) {
const h = parseFloat(vhVar) * 100;
container.style.height = h + 'px';
canvas.style.height = '100%';
}
}
container.style.width = '100%';
}
window.addEventListener('resize', applySizing);
if (window.visualViewport && typeof window.visualViewport.addEventListener === 'function') {
window.visualViewport.addEventListener('resize', applySizing);
}
document.addEventListener('DOMContentLoaded', applySizing);
setupAndroidAutoFullscreenOnGesture();
setupIOSDynamicViewportAndBarCollapse();
})();
+57
View File
@@ -0,0 +1,57 @@
// sw.js
// Import Workbox from the CDN
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js');
// Turn off debug logging for production
workbox.setConfig({ debug: false });
// Versioning your cache
const CACHE_VERSION = 'v1';
const RUNTIME_CACHE = `runtime-cache-${CACHE_VERSION}`;
// Cache Unity build files with StaleWhileRevalidate strategy
workbox.routing.registerRoute(
({ url }) => url.href.includes('Build'),
new workbox.strategies.StaleWhileRevalidate({
cacheName: RUNTIME_CACHE,
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 50, // Adjust as needed
purgeOnQuotaError: true,
}),
],
})
);
// Cache other assets (images, CSS, JS) with StaleWhileRevalidate
workbox.routing.registerRoute(
({ request }) =>
request.destination === 'style' ||
request.destination === 'script' ||
request.destination === 'image',
new workbox.strategies.StaleWhileRevalidate({
cacheName: 'assets-cache',
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 100, // Adjust as needed
purgeOnQuotaError: true,
}),
],
})
);
// Use NetworkFirst for HTML files to ensure you get the latest version
workbox.routing.registerRoute(
({ request }) => request.mode === 'navigate',
new workbox.strategies.NetworkFirst({
cacheName: 'pages-cache',
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 20, // Adjust as needed
purgeOnQuotaError: true,
}),
],
})
);
+228
View File
@@ -0,0 +1,228 @@
//fingerprint
/*
window.deviceId = localStorage.getItem("device_id");
if (!window.deviceId) {
const script = document.createElement('script');
script.src = "https://openfpcdn.io/fingerprintjs/v3";
script.async = true;
script.onload = () => {
FingerprintJS.load()
.then(fp => fp.get())
.then(result => {
window.deviceId = result.visitorId;
localStorage.setItem("device_id", window.deviceId);
})
.catch(() => {
window.deviceId = crypto.randomUUID();
localStorage.setItem("device_id", window.deviceId);
});
};
script.onerror = () => {
window.deviceId = crypto.randomUUID();
localStorage.setItem("device_id", window.deviceId);
};
document.head.appendChild(script);
}
*/
//fingerprint-end
//iframe-detection
try {
window.iframed = window.self !== window.top;
} catch (e) {
window.iframed = true;
}
//iframe-detection-end
const useKeyboardApiOnFullscreen = true;
function setURLHash(param) {
var hash = String(param);
if (typeof history.replaceState === 'function') {
if (hash) {
history.replaceState(null, null, '#' + encodeURIComponent(hash));
} else {
history.replaceState(null, null, window.location.pathname + window.location.search);
}
} else {
if (hash) {
window.location.hash = encodeURIComponent(hash);
} else {
window.location.hash = '';
}
}
}
function getURLHash() {
var hash = window.location.hash ? window.location.hash.substring(1) : null;
return hash ? decodeURIComponent(hash) : null;
}
window.alert = function() {
console.log('Blocked alert');
};
window.confirm = function() {
console.log('Blocked confirm');
return false;
};
window.prompt = function() {
console.log('Blocked prompt');
return null;
};
window.toggleFullscreen = toggleFullscreen;
function toggleFullscreen() {
if (!document.fullscreenElement && // Standard browsers
!document.mozFullScreenElement && // Firefox
!document.webkitFullscreenElement && // Chrome, Safari, Opera
!document.msFullscreenElement) { // IE/Edge
// Enter fullscreen mode
enterFullScreenMode();
} else {
// Exit fullscreen mode
exitFullscreenMode();
}
}
window.enterFullScreenMode = enterFullScreenMode;
function enterFullScreenMode(){
var elem = document.documentElement;
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem.mozRequestFullScreen) {
elem.mozRequestFullScreen();
} else if (elem.webkitRequestFullscreen) {
elem.webkitRequestFullscreen();
} else if (elem.msRequestFullscreen) {
elem.msRequestFullscreen();
}
// Lock screen orientation
if (screen.orientation && screen.orientation.lock) {
screen.orientation.lock('landscape').catch(() => { /* Ignore errors */ });
}
// Use Keyboard Lock API if enabled and supported
if (useKeyboardApiOnFullscreen && 'keyboard' in navigator && 'lock' in navigator.keyboard) {
// Lock the ESC key immediately after entering fullscreen
navigator.keyboard.lock(['Escape']).then(() => {
console.log('ESC key locked.');
}).catch(err => {
console.error('Failed to lock keyboard:', err);
});
}
}
function exitFullscreenMode(){
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
// Unlock the keyboard
if (useKeyboardApiOnFullscreen && 'keyboard' in navigator && 'unlock' in navigator.keyboard) {
navigator.keyboard.unlock();
console.log('Keyboard unlocked.');
}
}
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
if (document.pointerLockElement) {
document.exitPointerLock();
console.log('Pointer unlocked due to ESC key press.');
}
}
});
window.addEventListener('beforeunload', function (e) {
//if (!window.isPointerLocked)
// return;
e.preventDefault();
e.returnValue = '';
return '';
});
function goToWindow(windowName) {
const currentPath = window.location.pathname;
const newPath = `/${windowName}`;
if (currentPath === newPath) {
return;
}
history.pushState({}, '', newPath);
}
window.addEventListener('popstate', function(event) {
sendPopState();
});
function sendPopState() {
unityInstance.SendMessage('BrowserHistoryManager', 'PopState', window.location.pathname);
}
async function setClipboard(stringToSetAsClipboard) {
try {
await navigator.clipboard.writeText(stringToSetAsClipboard);
console.log('Text copied to clipboard successfully!');
} catch (err) {
console.error('Failed to copy text: ', err);
}
}
function getClipboard() {
navigator.clipboard.readText()
.then((clipboardContent) => {
unityInstanceWrapper.sendMessage('MainManager', 'OnGotClipboard', clipboardContent);
})
.catch((error) => {
console.error('Clipboard access error:', error);
});
}
// Prevent default scrolling behavior
window.addEventListener("wheel", (event) => event.preventDefault(), {
passive: false,
});
// Prevent default actions for specific keys
window.addEventListener("keydown", (event) => {
const key = event.key;
const ctrlPressed = event.ctrlKey || event.metaKey; // For Mac compatibility
// Prevent default actions for ArrowUp, ArrowDown, Space, Escape, and Ctrl+W
if (
["ArrowUp", "ArrowDown"].includes(key) ||
key === "Escape" ||
(ctrlPressed && key.toLowerCase() === "w")
) {
event.preventDefault();
}
});
// Rare situation where the GPU resets
window.addEventListener("webglcontextlost", function (event) {
event.preventDefault();
console.warn("WebGL context lost. Reloading...");
// Prevent unload blocker
window.onbeforeunload = null;
setTimeout(() => location.reload(), 100);
});
+748
View File
@@ -0,0 +1,748 @@
// SDK Production Version - Uses hardcoded configuration
// Designed to work with index.html
// Production configuration
const installProviders = [
"adinplay",
"cpmstar",
"local"
];
const videoAdPriorities = [
"cpmstar",
"adinplay"
];
const bannerAdPriorities = [
"adinplay",
"cpmstar",
"local"
];
// Global configuration
const DEBUG_MODE = window.location.href.includes('test');
const IS_MOBILE = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
const MIN_REFRESH_INTERVAL = 24_000; // Minimum showtime before refresh
const BANNER_DEBOUNCE_TIME = 100;
const MAX_AIPTAG_WAIT_TIME = 1800;
const SHOWTIME_CHECK_INTERVAL = 1000; // Check showtime every 1.6 seconds
// Banner position names for logging
const POSITION_NAMES = {
0: 'Hidden', 1: 'TopCenter', 2: 'TopRight', 3: 'TopLeft',
4: 'BottomCenter', 5: 'BottomRight', 6: 'BottomLeft',
7: 'MiddleCenter', 8: 'MiddleLeft', 9: 'MiddleRight',
10: 'BelowTopLeft', 11: 'BelowTopRight'
};
// Banner mappings and dimensions
window.bannerMapping = {
0: '300x250',
1: '728x90',
2: '300x600'
};
window.bannerDimensions = {
0: {
width: '300px', height: '250px',
scale: 1.3,
enableForMobile: true,
ratioBoostStops: [
{ ratio: 1.0, boost: 1.8 },
{ ratio: 1.62, boost: 1.2 },
{ ratio: 1.78, boost: 1.1 }
]
},
1: {
width: '728px', height: '90px',
scale: 1.19,
enableForMobile: false,
ratioBoostStops: [
{ ratio: 0, boost: 1.0 },
{ ratio: Infinity, boost: 1.0 }
]
},
2: {
width: '300px', height: '600px',
scale: 1.06,
enableForMobile: false,
ratioBoostStops: [
{ ratio: 1.0, boost: 1.9 },
{ ratio: 1.62, boost: 1.15 },
{ ratio: 1.78, boost: 1.0 }
]
}
};
// Sort ratioBoostStops
Object.keys(window.bannerDimensions).forEach(key => {
window.bannerDimensions[key].ratioBoostStops.sort((a, b) => a.ratio - b.ratio);
});
// Helper functions
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// Create promise for AdinPlay availability
window.waitForAdinPlay = function () {
return new Promise((resolve) => {
if (typeof aipDisplayTag !== "undefined") {
resolve(true);
return;
}
const timeoutId = setTimeout(() => {
resolve(false);
}, MAX_AIPTAG_WAIT_TIME);
const checkInterval = setInterval(() => {
if (typeof aipDisplayTag !== "undefined") {
clearInterval(checkInterval);
clearTimeout(timeoutId);
resolve(true);
}
}, 100);
});
};
// Load provider scripts dynamically
function loadProviderScript(provider) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = `ads/adapters/${provider}-ads.js`;
script.onload = () => {
resolve();
};
script.onerror = () => {
reject();
};
document.head.appendChild(script);
});
}
// Load all configured providers
async function loadProviders() {
for (const provider of installProviders) {
try {
await loadProviderScript(provider);
} catch (e) {
console.error(`Failed to load ${provider}:`, e);
}
}
}
// Main SDK object
window.SDK = {
_isInitialized: false,
_pendingBannerQueue: [],
gameplayStart() {
if (DEBUG_MODE) console.log("[sdk.js] Gameplay started");
},
loadingStart() {
if (DEBUG_MODE) console.log("[sdk.js] Loading started");
onWindowResize();
},
loadingEnd() {
if (DEBUG_MODE) console.log("[sdk.js] Loading finished");
onWindowResize();
},
gameplayEnd() {
if (DEBUG_MODE) console.log("[sdk.js] Gameplay ended");
},
showMidroll() {
if (window.ENABLE_ADS) {
showAd('midroll');
} else {
if (typeof unityInstance !== 'undefined') {
unityInstance.SendMessage("SDKManager", "OnVideoAdEnded", "true");
}
}
},
showRewarded() {
if (window.ENABLE_ADS) {
showAd('rewarded');
} else {
if (typeof unityInstance !== 'undefined') {
unityInstance.SendMessage("SDKManager", "OnVideoAdEnded", "true");
}
}
},
_bannerAds: {},
_occupiedPositions: {},
_lastRefreshed: {},
_pendingBannerOps: {},
_bannerShowtime: {}, // Tracks accumulated visible time per banner
_bannerVisibleSince: {}, // Tracks when banner became visible
_showtimeInterval: null, // Interval for checking showtime
_startTrackingShowtime(adTag) {
const now = Date.now();
if (!this._bannerVisibleSince[adTag]) {
this._bannerVisibleSince[adTag] = now;
if (DEBUG_MODE) console.log(`[sdk.js] Started tracking showtime for ${adTag}`);
}
},
_stopTrackingShowtime(adTag) {
if (this._bannerVisibleSince[adTag]) {
const now = Date.now();
const visibleDuration = now - this._bannerVisibleSince[adTag];
this._bannerShowtime[adTag] = (this._bannerShowtime[adTag] || 0) + visibleDuration;
delete this._bannerVisibleSince[adTag];
if (DEBUG_MODE) console.log(`[sdk.js] Stopped tracking showtime for ${adTag}. Total showtime: ${Math.floor(this._bannerShowtime[adTag] / 1000)}s`);
}
},
_resetShowtime(adTag) {
this._bannerShowtime[adTag] = 0;
delete this._bannerVisibleSince[adTag];
if (DEBUG_MODE) console.log(`[sdk.js] Reset showtime for ${adTag}`);
},
_getCurrentShowtime(adTag) {
let totalShowtime = this._bannerShowtime[adTag] || 0;
if (this._bannerVisibleSince[adTag]) {
const now = Date.now();
totalShowtime += (now - this._bannerVisibleSince[adTag]);
}
return totalShowtime;
},
_isBannerVisible(banner) {
return banner &&
banner.position !== 0 &&
banner.container &&
banner.container.style.display !== 'none' &&
banner.container.offsetParent !== null &&
!document.hidden;
},
async _originalSetBanner(bannerType, bannerPosition) {
const adTag = window.bannerMapping[bannerType];
const dims = window.bannerDimensions[bannerType];
const now = Date.now();
if (!adTag || !dims) {
if (DEBUG_MODE) console.log(`[sdk.js] SetBanner: Invalid banner type ${bannerType}`);
return;
}
if (IS_MOBILE && !dims.enableForMobile) {
if (DEBUG_MODE) console.log(`[sdk.js] SetBanner: Banner ${adTag} disabled on mobile`);
return;
}
// Get position name for logging
const posName = POSITION_NAMES[bannerPosition] || `Position${bannerPosition}`;
if (DEBUG_MODE) {
console.log(`[sdk.js] SetBanner called: ${adTag} at ${posName}`);
}
this._pendingBannerOps[bannerType] = {
bannerType: bannerType,
bannerPosition: bannerPosition,
timestamp: now
};
let existingInstance = this._bannerAds[adTag];
// Handle hiding
if (bannerPosition === 0) {
if (DEBUG_MODE) console.log(`[sdk.js] Hiding banner ${adTag}`);
if (existingInstance && existingInstance.container) {
// Stop tracking showtime when hiding
this._stopTrackingShowtime(adTag);
existingInstance.container.style.display = "none";
let oldPosKey = existingInstance.position.toString();
if (this._occupiedPositions[oldPosKey] === adTag) {
delete this._occupiedPositions[oldPosKey];
}
existingInstance.position = 0;
// Remove from pending operations since it's hidden
delete this._pendingBannerOps[bannerType];
}
return;
}
// Handle existing banner
if (existingInstance) {
if (existingInstance.position === bannerPosition) {
return;
}
// Move banner
let oldPosKey = existingInstance.position.toString();
if (this._occupiedPositions[oldPosKey] === adTag) {
delete this._occupiedPositions[oldPosKey];
}
let newPosKey = bannerPosition.toString();
if (this._occupiedPositions[newPosKey] && this._occupiedPositions[newPosKey] !== adTag) {
let conflictAdTag = this._occupiedPositions[newPosKey];
let conflictInstance = this._bannerAds[conflictAdTag];
if (conflictInstance && conflictInstance.container) {
conflictInstance.container.style.display = "none";
}
delete this._occupiedPositions[newPosKey];
}
existingInstance.container.style.display = "block";
updateContainerPosition(existingInstance.container, bannerPosition);
existingInstance.position = bannerPosition;
this._occupiedPositions[newPosKey] = adTag;
// Start tracking showtime when showing
this._startTrackingShowtime(adTag);
// Check if we should refresh based on accumulated showtime
const currentShowtime = this._getCurrentShowtime(adTag);
const shouldRefresh = currentShowtime >= MIN_REFRESH_INTERVAL;
if (shouldRefresh) {
if (this._pendingBannerOps[bannerType]?.timestamp > now) {
return;
}
// Reset showtime counter before refresh
this._resetShowtime(adTag);
this._startTrackingShowtime(adTag);
this._displayBannerWithProviders(bannerType, adTag, existingInstance.container, now);
}
} else {
// Create new banner
let posKey = bannerPosition.toString();
if (this._occupiedPositions[posKey]) {
let conflictAdTag = this._occupiedPositions[posKey];
let conflictInstance = this._bannerAds[conflictAdTag];
if (conflictInstance && conflictInstance.container) {
conflictInstance.container.style.display = "none";
}
delete this._occupiedPositions[posKey];
}
const container = document.createElement('div');
container.className = 'banner-container';
container.id = 'banner_' + adTag;
container.style.position = 'absolute';
container.style.zIndex = 1000;
container.style.userSelect = 'none';
container.style.pointerEvents = 'all';
container.style.width = dims.width;
container.style.height = dims.height;
container.style.overflow = 'hidden';
if (DEBUG_MODE) {
container.style.backgroundColor = 'rgba(255, 0, 0, 0.66)';
} else {
container.style.backgroundColor = 'rgba(0, 0, 0, 0.035)';
}
const bannerDiv = document.createElement('div');
bannerDiv.id = adTag;
bannerDiv.style.width = dims.width;
bannerDiv.style.height = dims.height;
container.appendChild(bannerDiv);
updateContainerPosition(container, bannerPosition);
document.body.appendChild(container);
this._bannerAds[adTag] = {
bannerType: bannerType,
adTag: adTag,
container: container,
position: bannerPosition
};
this._occupiedPositions[posKey] = adTag;
// Start tracking showtime for new banner
this._startTrackingShowtime(adTag);
if (this._pendingBannerOps[bannerType]?.timestamp > now) {
return;
}
this._displayBannerWithProviders(bannerType, adTag, container, now);
}
onWindowResize();
},
async _displayBannerWithProviders(bannerType, adTag, container, now) {
let providerIndex = 0;
const priorities = bannerAdPriorities;
// Get position name for logging
const bannerInstance = this._bannerAds[adTag];
const posName = bannerInstance ? (POSITION_NAMES[bannerInstance.position] || `Position${bannerInstance.position}`) : 'Unknown';
const tryNextProvider = async () => {
if (providerIndex >= priorities.length) {
if (DEBUG_MODE) console.log(`[sdk.js] All providers failed for banner ${adTag} at ${posName}`);
return false;
}
const providerName = priorities[providerIndex];
const provider = window.bannerAdProviders?.[providerName];
if (!provider) {
if (DEBUG_MODE) console.log(`[sdk.js] Provider ${providerName} not available for banner ${adTag}`);
providerIndex++;
return tryNextProvider();
}
if (DEBUG_MODE) console.log(`[sdk.js] Trying to show banner ${adTag} at ${posName} with provider: ${providerName}`);
try {
// Pass bannerType for CPMStar/Nitro/Local, adTag for AdinPlay
const param = providerName === 'adinplay' ? adTag : bannerType;
const success = await provider.displayBanner(param, container);
if (success) {
if (DEBUG_MODE) console.log(`[sdk.js] Successfully showing banner ${adTag} at ${posName} with ${providerName}`);
this._lastRefreshed[adTag] = now;
// Reset showtime after successful refresh
this._resetShowtime(adTag);
this._startTrackingShowtime(adTag);
return true;
} else {
if (DEBUG_MODE) console.log(`[sdk.js] Showing banner ${adTag} at ${posName} with ${providerName} FAILED, trying next...`);
providerIndex++;
return tryNextProvider();
}
} catch (error) {
if (DEBUG_MODE) console.log(`[sdk.js] Error showing banner ${adTag} with ${providerName}:`, error.message || error);
providerIndex++;
return tryNextProvider();
}
};
return tryNextProvider();
}
};
// Debounced SetBanner with initialization queue
window.SDK.SetBanner = function (bannerType, bannerPosition) {
// If not initialized and trying to show banner (not hide), queue it
if (!window.SDK._isInitialized && bannerPosition !== 0) {
if (DEBUG_MODE) console.log(`[sdk.js] SDK not initialized yet, queueing banner ${window.bannerMapping[bannerType] || bannerType} for position ${bannerPosition}`);
// Check if we already have this banner type in queue
const existingIndex = window.SDK._pendingBannerQueue.findIndex(item => item.bannerType === bannerType);
if (existingIndex >= 0) {
// Update existing entry with new position
window.SDK._pendingBannerQueue[existingIndex].bannerPosition = bannerPosition;
} else {
// Add new entry
window.SDK._pendingBannerQueue.push({ bannerType, bannerPosition });
}
return;
}
// If hiding banner (position 0), process immediately even if not initialized
// This ensures we can remove queued banners
if (bannerPosition === 0) {
// Remove from queue if present
window.SDK._pendingBannerQueue = window.SDK._pendingBannerQueue.filter(
item => item.bannerType !== bannerType
);
// If initialized, hide the banner
if (window.SDK._isInitialized) {
window.SDK._originalSetBanner.call(window.SDK, bannerType, bannerPosition);
}
} else {
// Normal debounced call for showing banners when initialized
if (!window.SDK._debouncedSetBannerPerType) {
window.SDK._debouncedSetBannerPerType = {};
}
if (!window.SDK._debouncedSetBannerPerType[bannerType]) {
window.SDK._debouncedSetBannerPerType[bannerType] = debounce(
(bannerType, bannerPosition) => {
window.SDK._originalSetBanner.call(window.SDK, bannerType, bannerPosition);
},
BANNER_DEBOUNCE_TIME
);
}
window.SDK._debouncedSetBannerPerType[bannerType](bannerType, bannerPosition);
}
};
// Container positioning helper
function updateContainerPosition(container, bannerPosition) {
container.style.top = "";
container.style.right = "";
container.style.bottom = "";
container.style.left = "";
container.style.transformOrigin = "";
switch (parseInt(bannerPosition)) {
case 0:
container.style.display = "none";
break;
case 1: // TopCenter
container.style.display = "block";
container.style.top = "1%";
container.style.left = "0";
container.style.right = "0";
container.style.marginLeft = "auto";
container.style.marginRight = "auto";
container.style.transformOrigin = "top center";
break;
case 2: // TopRight
container.style.display = "block";
container.style.top = "1%";
container.style.right = "1%";
container.style.transformOrigin = "top right";
break;
case 3: // TopLeft
container.style.display = "block";
container.style.top = "1%";
container.style.left = "1%";
container.style.transformOrigin = "top left";
break;
case 4: // BottomCenter
container.style.display = "block";
container.style.bottom = "0.5%";
container.style.left = "0";
container.style.right = "0";
container.style.marginLeft = "auto";
container.style.marginRight = "auto";
container.style.transformOrigin = "bottom center";
break;
case 5: // BottomRight
container.style.display = "block";
container.style.bottom = "1%";
container.style.right = "1%";
container.style.transformOrigin = "bottom right";
break;
case 6: // BottomLeft
container.style.display = "block";
container.style.bottom = "1%";
container.style.left = "1%";
container.style.transformOrigin = "bottom left";
break;
case 7: // MiddleCenter
container.style.display = "block";
container.style.position = "absolute";
container.style.top = "0";
container.style.bottom = "0";
container.style.left = "0";
container.style.right = "0";
container.style.margin = "auto";
break;
case 8: // MiddleLeft
container.style.display = "block";
container.style.left = "1%";
container.style.top = "0";
container.style.bottom = "0";
container.style.margin = "auto";
container.style.transformOrigin = "center left";
break;
case 9: // MiddleRight
container.style.display = "block";
container.style.right = "1%";
container.style.top = "0";
container.style.bottom = "0";
container.style.margin = "auto";
container.style.transformOrigin = "center right";
break;
case 10: // Below TopLeft
container.style.display = "block";
container.style.position = "absolute";
container.style.left = "1%";
container.style.top = "14%";
container.style.transformOrigin = "top left";
break;
case 11: // Below TopRight
container.style.display = "block";
container.style.position = "absolute";
container.style.top = "10%";
container.style.right = "1%";
container.style.transformOrigin = "top right";
break;
}
}
// Video ad system
function showAd(adType) {
let providerIndex = 0;
const priorities = videoAdPriorities;
function tryNextProvider() {
if (providerIndex >= priorities.length) {
if (typeof unityInstance !== 'undefined') {
unityInstance.SendMessage("SDKManager", "OnVideoAdEnded", "false");
}
return;
}
const providerName = priorities[providerIndex];
const provider = window.videoAdProviders?.[providerName];
if (!provider) {
providerIndex++;
tryNextProvider();
return;
}
const methodName = adType === 'rewarded' ? 'showRewarded' : 'showMidroll';
provider[methodName](
function () {
if (typeof unityInstance !== 'undefined') {
unityInstance.SendMessage("SDKManager", "OnVideoAdEnded", "true");
}
},
function () {
providerIndex++;
tryNextProvider();
}
);
}
tryNextProvider();
}
// Window resize handling
function getRatioBoost(stops, aspect) {
stops.sort((a, b) => a.ratio - b.ratio);
if (aspect <= stops[0].ratio) return stops[0].boost;
if (aspect >= stops.at(-1).ratio) return stops.at(-1).boost;
for (let i = 0; i < stops.length - 1; i++) {
const a = stops[i], b = stops[i + 1];
if (aspect >= a.ratio && aspect <= b.ratio) {
const t = (aspect - a.ratio) / (b.ratio - a.ratio);
return a.boost + (b.boost - a.boost) * t;
}
}
return 1;
}
function onWindowResize() {
const w = window.innerWidth, h = window.innerHeight;
const aspect = w / h;
const baseScale = Math.min(w / 1920, h / 960);
Object.keys(window.bannerMapping).forEach(key => {
const tag = window.bannerMapping[key];
const dims = window.bannerDimensions[key];
const ctr = document.getElementById('banner_' + tag);
if (!ctr || (IS_MOBILE && !dims.enableForMobile)) return;
const boost = getRatioBoost(dims.ratioBoostStops, aspect);
const finalScale = baseScale * dims.scale * boost;
ctr.style.transform = `scale(${finalScale})`;
});
}
window.addEventListener("resize", onWindowResize);
// Showtime-based refresh system
window.SDK._showtimeInterval = setInterval(async function () {
if (!window.ENABLE_ADS) return;
const now = Date.now();
// Update showtime for all visible banners
Object.keys(window.SDK._bannerAds).forEach(adTag => {
const banner = window.SDK._bannerAds[adTag];
if (window.SDK._isBannerVisible(banner)) {
// Banner is visible - ensure we're tracking it
if (!window.SDK._bannerVisibleSince[adTag]) {
window.SDK._startTrackingShowtime(adTag);
}
// Check if it's time to refresh based on showtime
const currentShowtime = window.SDK._getCurrentShowtime(adTag);
if (currentShowtime >= MIN_REFRESH_INTERVAL) {
if (DEBUG_MODE) console.log(`[sdk.js] Banner ${adTag} reached ${Math.floor(currentShowtime / 1000)}s showtime, refreshing...`);
const bannerType = Object.keys(window.bannerMapping).find(key => window.bannerMapping[key] === adTag);
if (bannerType !== undefined) {
// Reset showtime before refresh
window.SDK._resetShowtime(adTag);
window.SDK._startTrackingShowtime(adTag);
window.SDK._displayBannerWithProviders(parseInt(bannerType), adTag, banner.container, now);
}
}
} else {
// Banner is not visible - stop tracking if we were
if (window.SDK._bannerVisibleSince[adTag]) {
window.SDK._stopTrackingShowtime(adTag);
}
}
});
}, SHOWTIME_CHECK_INTERVAL);
// Handle page visibility changes for showtime tracking
document.addEventListener('visibilitychange', function () {
if (document.hidden) {
// Page is hidden - stop tracking all visible banners
Object.keys(window.SDK._bannerAds).forEach(adTag => {
if (window.SDK._bannerVisibleSince[adTag]) {
window.SDK._stopTrackingShowtime(adTag);
}
});
} else if (window.ENABLE_ADS) {
// Page is visible again - restart tracking for visible banners
Object.keys(window.SDK._bannerAds).forEach(adTag => {
const banner = window.SDK._bannerAds[adTag];
if (window.SDK._isBannerVisible(banner)) {
window.SDK._startTrackingShowtime(adTag);
}
});
}
});
// Process queued banners after initialization
window.SDK._processQueuedBanners = function () {
if (DEBUG_MODE && window.SDK._pendingBannerQueue.length > 0) {
console.log(`[sdk.js] Processing ${window.SDK._pendingBannerQueue.length} queued banner(s)`);
}
// Process each queued banner
const queue = [...window.SDK._pendingBannerQueue];
window.SDK._pendingBannerQueue = [];
queue.forEach(({ bannerType, bannerPosition }) => {
if (DEBUG_MODE) {
const adTag = window.bannerMapping[bannerType] || bannerType;
console.log(`[sdk.js] Processing queued banner: ${adTag} at position ${bannerPosition}`);
}
// Use SetBanner to ensure debouncing still applies
window.SDK.SetBanner(bannerType, bannerPosition);
});
};
// Initialize
(async function () {
if (DEBUG_MODE) console.log("[sdk.js] Starting SDK initialization...");
await loadProviders();
// Mark as initialized
window.SDK._isInitialized = true;
if (DEBUG_MODE) console.log("[sdk.js] SDK initialized, providers loaded");
// Process any queued banners
window.SDK._processQueuedBanners();
window.SDK.loadingStart();
})();