h
This commit is contained in:
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.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
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}
|
||||
@@ -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");
|
||||
})();
|
||||
@@ -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");
|
||||
})();
|
||||
@@ -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 |
+476
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
})();
|
||||
@@ -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
@@ -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);
|
||||
});
|
||||
@@ -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();
|
||||
})();
|
||||
Reference in New Issue
Block a user