mirror of
				https://github.com/unanmed/HumanBreak.git
				synced 2025-10-31 20:32:58 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			1520 lines
		
	
	
		
			45 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1520 lines
		
	
	
		
			45 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| ///<reference path="../../src/types/declaration/core.d.ts" />
 | ||
| 
 | ||
| /*
 | ||
| utils.js 工具类
 | ||
| 
 | ||
|  */
 | ||
| 
 | ||
| 'use strict';
 | ||
| 
 | ||
| function utils() {
 | ||
|     this._init();
 | ||
|     this.scan = {
 | ||
|         up: { x: 0, y: -1 },
 | ||
|         left: { x: -1, y: 0 },
 | ||
|         down: { x: 0, y: 1 },
 | ||
|         right: { x: 1, y: 0 }
 | ||
|     };
 | ||
|     this.scan2 = {
 | ||
|         up: { x: 0, y: -1 },
 | ||
|         left: { x: -1, y: 0 },
 | ||
|         down: { x: 0, y: 1 },
 | ||
|         right: { x: 1, y: 0 },
 | ||
|         leftup: { x: -1, y: -1 },
 | ||
|         leftdown: { x: -1, y: 1 },
 | ||
|         rightup: { x: 1, y: -1 },
 | ||
|         rightdown: { x: 1, y: 1 }
 | ||
|     };
 | ||
| }
 | ||
| 
 | ||
| utils.prototype._init = function () {
 | ||
|     //
 | ||
| };
 | ||
| 
 | ||
| ////// 将文字中的${和}(表达式)进行替换 //////
 | ||
| utils.prototype.replaceText = function (text, prefix) {
 | ||
|     if (typeof text !== 'string') return text;
 | ||
|     const length = text.length;
 | ||
|     let pointer = -1;
 | ||
|     let res = '';
 | ||
|     let expression = '';
 | ||
|     let blockDepth = 0;
 | ||
|     let inExpression = false;
 | ||
| 
 | ||
|     while (++pointer < length) {
 | ||
|         const char = text[pointer];
 | ||
| 
 | ||
|         if (inExpression) {
 | ||
|             if (char === '}') {
 | ||
|                 blockDepth--;
 | ||
|             } else if (char === '{') {
 | ||
|                 blockDepth++;
 | ||
|             }
 | ||
|             if (blockDepth === 0) {
 | ||
|                 inExpression = false;
 | ||
|                 res += String(core.utils.calValue(expression, prefix));
 | ||
|                 expression = '';
 | ||
|             } else {
 | ||
|                 expression += char;
 | ||
|             }
 | ||
|             continue;
 | ||
|         }
 | ||
| 
 | ||
|         if (char === '$' && text[pointer + 1] === '{') {
 | ||
|             pointer++;
 | ||
|             inExpression = true;
 | ||
|             blockDepth++;
 | ||
|             continue;
 | ||
|         }
 | ||
| 
 | ||
|         res += char;
 | ||
|     }
 | ||
| 
 | ||
|     return res;
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.replaceValue = function (value) {
 | ||
|     if (typeof value !== 'string') return value;
 | ||
|     if (
 | ||
|         value.includes(':') ||
 | ||
|         value.includes('flag:') ||
 | ||
|         value.includes('global:')
 | ||
|     ) {
 | ||
|         if (value.includes('status:'))
 | ||
|             value = value.replace(
 | ||
|                 /status:([a-zA-Z0-9_]+)/g,
 | ||
|                 "core.getStatus('$1')"
 | ||
|             );
 | ||
|         if (value.includes('buff:'))
 | ||
|             value = value.replace(
 | ||
|                 /buff:([a-zA-Z0-9_]+)/g,
 | ||
|                 "core.getBuff('$1')"
 | ||
|             );
 | ||
|         if (value.includes('item:'))
 | ||
|             value = value.replace(
 | ||
|                 /item:([a-zA-Z0-9_]+)/g,
 | ||
|                 "core.itemCount('$1')"
 | ||
|             );
 | ||
|         if (value.includes('flag:') || value.includes('flag:'))
 | ||
|             value = value.replace(
 | ||
|                 /flag[::]([a-zA-Z0-9_\u4E00-\u9FCC\u3040-\u30FF\u2160-\u216B\u0391-\u03C9]+)/g,
 | ||
|                 "core.getFlag('$1', 0)"
 | ||
|             );
 | ||
|         if (value.includes('global:') || value.includes('global:'))
 | ||
|             value = value.replace(
 | ||
|                 /global[::]([a-zA-Z0-9_\u4E00-\u9FCC\u3040-\u30FF\u2160-\u216B\u0391-\u03C9]+)/g,
 | ||
|                 "core.getGlobal('$1', 0)"
 | ||
|             );
 | ||
|         if (value.includes('enemy:'))
 | ||
|             value = value.replace(
 | ||
|                 /enemy:([a-zA-Z0-9_]+)[\.:]([a-zA-Z0-9_]+)/g,
 | ||
|                 "core.material.enemys['$1'].$2"
 | ||
|             );
 | ||
|         if (value.includes('blockId:'))
 | ||
|             value = value.replace(
 | ||
|                 /blockId:(\d+),(\d+)/g,
 | ||
|                 'core.getBlockId($1, $2)'
 | ||
|             );
 | ||
|         if (value.includes('blockNumber:'))
 | ||
|             value = value.replace(
 | ||
|                 /blockNumber:(\d+),(\d+)/g,
 | ||
|                 'core.getBlockNumber($1, $2)'
 | ||
|             );
 | ||
|         if (value.includes('blockCls:'))
 | ||
|             value = value.replace(
 | ||
|                 /blockCls:(\d+),(\d+)/g,
 | ||
|                 'core.getBlockCls($1, $2)'
 | ||
|             );
 | ||
|         if (value.includes('equip:'))
 | ||
|             value = value.replace(/equip:(\d)/g, 'core.getEquip($1)');
 | ||
|         if (value.includes('temp:'))
 | ||
|             value = value.replace(
 | ||
|                 /temp:([a-zA-Z0-9_]+)/g,
 | ||
|                 "core.getFlag('@temp@$1', 0)"
 | ||
|             );
 | ||
|         // if (value.indexOf('switch:') >= 0)
 | ||
|         //     value = value.replace(
 | ||
|         //         /switch:([a-zA-Z0-9_]+)/g,
 | ||
|         //         "core.getFlag('" + (prefix || ':f@x@y') + "@$1', 0)"
 | ||
|         //     );
 | ||
|     }
 | ||
|     return value;
 | ||
| };
 | ||
| 
 | ||
| ////// 计算表达式的值 //////
 | ||
| utils.prototype.calValue = function (value, prefix) {
 | ||
|     if (!core.isset(value)) return null;
 | ||
|     if (typeof value === 'string') {
 | ||
|         value = this.replaceValue(value);
 | ||
|         return eval(value);
 | ||
|     }
 | ||
|     if (value instanceof Function) {
 | ||
|         return value();
 | ||
|     }
 | ||
|     return value;
 | ||
| };
 | ||
| 
 | ||
| ////// 向某个数组前插入另一个数组或元素 //////
 | ||
| utils.prototype.unshift = function (a, b) {
 | ||
|     if (!(a instanceof Array) || b == null) return;
 | ||
|     if (b instanceof Array) {
 | ||
|         a.unshift(...b);
 | ||
|     } else a.unshift(b);
 | ||
|     return a;
 | ||
| };
 | ||
| 
 | ||
| ////// 向某个数组后插入另一个数组或元素 //////
 | ||
| utils.prototype.push = function (a, b) {
 | ||
|     if (!(a instanceof Array) || b == null) return;
 | ||
|     if (b instanceof Array) {
 | ||
|         a.push(...b);
 | ||
|     } else a.push(b);
 | ||
|     return a;
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.decompress = function (value) {
 | ||
|     try {
 | ||
|         var output = lzw_decode(value);
 | ||
|         if (output) return JSON.parse(output);
 | ||
|     } catch (e) {}
 | ||
|     try {
 | ||
|         var output = LZString.decompress(value);
 | ||
|         if (output) return JSON.parse(output);
 | ||
|     } catch (e) {}
 | ||
|     try {
 | ||
|         return JSON.parse(value);
 | ||
|     } catch (e) {
 | ||
|         console.error(e);
 | ||
|     }
 | ||
|     return null;
 | ||
| };
 | ||
| 
 | ||
| ////// 设置本地存储 //////
 | ||
| utils.prototype.setLocalStorage = function (key, value) {
 | ||
|     try {
 | ||
|         if (value == null) {
 | ||
|             this.removeLocalStorage(key);
 | ||
|             return;
 | ||
|         }
 | ||
| 
 | ||
|         var str = JSON.stringify(value).replace(
 | ||
|             /[\u007F-\uFFFF]/g,
 | ||
|             function (chr) {
 | ||
|                 return (
 | ||
|                     '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).substr(-4)
 | ||
|                 );
 | ||
|             }
 | ||
|         );
 | ||
|         localStorage.setItem(core.firstData.name + '_' + key, str);
 | ||
| 
 | ||
|         if (key == 'autoSave') core.saves.ids[0] = true;
 | ||
|         else if (/^save\d+$/.test(key))
 | ||
|             core.saves.ids[parseInt(key.substring(4))] = true;
 | ||
| 
 | ||
|         return true;
 | ||
|     } catch (e) {
 | ||
|         console.error(e);
 | ||
|         return false;
 | ||
|     }
 | ||
| };
 | ||
| 
 | ||
| ////// 获得本地存储 //////
 | ||
| utils.prototype.getLocalStorage = function (key, defaultValue) {
 | ||
|     try {
 | ||
|         var value = JSON.parse(
 | ||
|             localStorage.getItem(core.firstData.name + '_' + key)
 | ||
|         );
 | ||
|         if (value == null) return defaultValue;
 | ||
|         return value;
 | ||
|     } catch (e) {
 | ||
|         return defaultValue;
 | ||
|     }
 | ||
| };
 | ||
| 
 | ||
| ////// 移除本地存储 //////
 | ||
| utils.prototype.removeLocalStorage = function (key) {
 | ||
|     localStorage.removeItem(core.firstData.name + '_' + key);
 | ||
|     if (key == 'autoSave') delete core.saves.ids[0];
 | ||
|     else if (/^save\d+$/.test(key))
 | ||
|         delete core.saves.ids[parseInt(key.substring(4))];
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.setLocalForage = function (
 | ||
|     key,
 | ||
|     value,
 | ||
|     successCallback,
 | ||
|     errorCallback
 | ||
| ) {
 | ||
|     if (value == null) {
 | ||
|         this.removeLocalForage(key);
 | ||
|         return;
 | ||
|     }
 | ||
| 
 | ||
|     var name = core.firstData.name + '_' + key;
 | ||
|     var str = JSON.stringify(value).replace(/[\u007F-\uFFFF]/g, function (chr) {
 | ||
|         return '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).substr(-4);
 | ||
|     });
 | ||
|     var callback = function (err) {
 | ||
|         if (err) {
 | ||
|             if (errorCallback) errorCallback(err);
 | ||
|         } else {
 | ||
|             if (key == 'autoSave') core.saves.ids[0] = true;
 | ||
|             else if (/^save\d+$/.test(key))
 | ||
|                 core.saves.ids[parseInt(key.substring(4))] = true;
 | ||
|             if (successCallback) successCallback();
 | ||
|         }
 | ||
|     };
 | ||
|     this._setLocalForage_set(name, str, callback);
 | ||
| };
 | ||
| 
 | ||
| utils.prototype._setLocalForage_set = function (name, str, callback) {
 | ||
|     if (window.jsinterface && window.jsinterface.setLocalForage) {
 | ||
|         var id = setTimeout(null);
 | ||
|         core['__callback' + id] = callback;
 | ||
|         core.saves.cache[name] = str;
 | ||
|         window.jsinterface.setLocalForage(id, name, str);
 | ||
|     } else {
 | ||
|         var compressed =
 | ||
|             str.length > 100000 ? LZString.compress(str) : lzw_encode(str);
 | ||
|         core.saves.cache[name] = compressed;
 | ||
|         localforage.setItem(name, compressed, callback);
 | ||
|     }
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.getLocalForage = function (
 | ||
|     key,
 | ||
|     defaultValue,
 | ||
|     successCallback,
 | ||
|     errorCallback
 | ||
| ) {
 | ||
|     var name = core.firstData.name + '_' + key;
 | ||
|     var callback = function (err, value) {
 | ||
|         if (err) {
 | ||
|             if (errorCallback) errorCallback(err);
 | ||
|         } else {
 | ||
|             core.saves.cache[name] = value;
 | ||
|             if (!successCallback) return;
 | ||
|             if (value != null) {
 | ||
|                 var res = core.utils.decompress(value);
 | ||
|                 successCallback(res == null ? defaultValue : res);
 | ||
|                 return;
 | ||
|             }
 | ||
|             successCallback(defaultValue);
 | ||
|         }
 | ||
|     };
 | ||
|     if (core.saves.cache[name] != null) {
 | ||
|         return callback(null, core.saves.cache[name]);
 | ||
|     }
 | ||
|     this._getLocalForage_get(name, callback);
 | ||
| };
 | ||
| 
 | ||
| utils.prototype._getLocalForage_get = function (name, callback) {
 | ||
|     if (window.jsinterface && window.jsinterface.getLocalForage) {
 | ||
|         var id = setTimeout(null);
 | ||
|         core['__callback' + id] = callback;
 | ||
|         window.jsinterface.getLocalForage(id, name);
 | ||
|     } else {
 | ||
|         localforage.getItem(name, callback);
 | ||
|     }
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.removeLocalForage = function (
 | ||
|     key,
 | ||
|     successCallback,
 | ||
|     errorCallback
 | ||
| ) {
 | ||
|     var name = core.firstData.name + '_' + key;
 | ||
|     var callback = function (err) {
 | ||
|         if (err) {
 | ||
|             if (errorCallback) errorCallback(err);
 | ||
|         } else {
 | ||
|             if (key == 'autoSave') delete core.saves.ids[0];
 | ||
|             else if (/^save\d+$/.test(key))
 | ||
|                 delete core.saves.ids[parseInt(key.substring(4))];
 | ||
|             if (successCallback) successCallback();
 | ||
|         }
 | ||
|     };
 | ||
|     delete core.saves.cache[name];
 | ||
|     this._removeLocalForage_remove(name, callback);
 | ||
| };
 | ||
| 
 | ||
| utils.prototype._removeLocalForage_remove = function (name, callback) {
 | ||
|     if (window.jsinterface && window.jsinterface.removeLocalForage) {
 | ||
|         var id = setTimeout(null);
 | ||
|         core['__callback' + id] = callback;
 | ||
|         window.jsinterface.removeLocalForage(id, name);
 | ||
|     } else {
 | ||
|         localforage.removeItem(name, callback);
 | ||
|     }
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.clearLocalForage = function (callback) {
 | ||
|     core.saves.cache = {};
 | ||
|     if (window.jsinterface && window.jsinterface.clearLocalForage) {
 | ||
|         var id = setTimeout(null);
 | ||
|         core['__callback' + id] = callback;
 | ||
|         window.jsinterface.clearLocalForage(id);
 | ||
|     } else {
 | ||
|         localforage.clear(callback);
 | ||
|     }
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.iterateLocalForage = function (iter, callback) {
 | ||
|     if (window.jsinterface && window.jsinterface.iterateLocalForage) {
 | ||
|         var id = setTimeout(null);
 | ||
|         core['__iter' + id] = iter;
 | ||
|         core['__callback' + id] = callback;
 | ||
|         window.jsinterface.iterateLocalForage(id);
 | ||
|     } else {
 | ||
|         localforage.iterate(iter, callback);
 | ||
|     }
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.keysLocalForage = function (callback) {
 | ||
|     if (window.jsinterface && window.jsinterface.keysLocalForage) {
 | ||
|         var id = setTimeout(null);
 | ||
|         core['__callback' + id] = callback;
 | ||
|         window.jsinterface.keysLocalForage(id);
 | ||
|     } else {
 | ||
|         localforage.keys(callback);
 | ||
|     }
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.lengthLocalForage = function (callback) {
 | ||
|     if (window.jsinterface && window.jsinterface.lengthLocalForage) {
 | ||
|         var id = setTimeout(null);
 | ||
|         core['__callback' + id] = callback;
 | ||
|         window.jsinterface.lengthLocalForage(id);
 | ||
|     } else {
 | ||
|         localforage.length(callback);
 | ||
|     }
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.setGlobal = function (key, value) {
 | ||
|     if (core.isReplaying()) return;
 | ||
|     core.setLocalStorage(key, value);
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.getGlobal = function (key, defaultValue) {
 | ||
|     var value;
 | ||
|     if (core.isReplaying()) {
 | ||
|         // 不考虑key不一致的情况
 | ||
|         var action = core.status.replay.toReplay.shift();
 | ||
|         if (action.indexOf('input2:') == 0) {
 | ||
|             value = JSON.parse(core.decodeBase64(action.substring(7)));
 | ||
|             core.setFlag('__global__' + key, value);
 | ||
|             core.status.route.push(
 | ||
|                 'input2:' + core.encodeBase64(JSON.stringify(value))
 | ||
|             );
 | ||
|         } else {
 | ||
|             // 录像兼容性:尝试从flag和localStorage获得
 | ||
|             // 注意这里不再二次记录 input2: 到录像
 | ||
|             core.status.replay.toReplay.unshift(action);
 | ||
|             value = core.getFlag(
 | ||
|                 '__global__' + key,
 | ||
|                 core.getLocalStorage(key, defaultValue)
 | ||
|             );
 | ||
|         }
 | ||
|     } else {
 | ||
|         value = core.getLocalStorage(key, defaultValue);
 | ||
|         core.setFlag('__global__' + key, value);
 | ||
|         core.status.route.push(
 | ||
|             'input2:' + core.encodeBase64(JSON.stringify(value))
 | ||
|         );
 | ||
|     }
 | ||
|     return value;
 | ||
| };
 | ||
| 
 | ||
| ////// 深拷贝一个对象 //////
 | ||
| utils.prototype.clone = function (data, filter, recursion) {
 | ||
|     if (!core.isset(data)) return null;
 | ||
|     // date
 | ||
|     if (data instanceof Date) {
 | ||
|         var copy = new Date();
 | ||
|         copy.setTime(data.getTime());
 | ||
|         return copy;
 | ||
|     }
 | ||
|     // array
 | ||
|     if (data instanceof Array) {
 | ||
|         return data.map((v, i) => {
 | ||
|             if (!filter || filter(String(i), v)) {
 | ||
|                 return this.clone(v, recursion ? filter : null, recursion);
 | ||
|             }
 | ||
|         });
 | ||
|     }
 | ||
|     // 函数
 | ||
|     if (data instanceof Function) {
 | ||
|         return data;
 | ||
|     }
 | ||
|     // object
 | ||
|     if (data instanceof Object) {
 | ||
|         var copy = {};
 | ||
|         for (const [key, value] of Object.entries(data)) {
 | ||
|             if (!filter || filter(key, value)) {
 | ||
|                 copy[key] = this.clone(
 | ||
|                     value,
 | ||
|                     recursion ? filter : null,
 | ||
|                     recursion
 | ||
|                 );
 | ||
|             }
 | ||
|         }
 | ||
|         return copy;
 | ||
|     }
 | ||
|     return data;
 | ||
| };
 | ||
| 
 | ||
| ////// 深拷贝1D/2D数组优化 //////
 | ||
| utils.prototype.cloneArray = function (data) {
 | ||
|     if (!(data instanceof Array)) return this.clone(data);
 | ||
|     if (data[0] instanceof Array) {
 | ||
|         return data.map(function (one) {
 | ||
|             return one.slice();
 | ||
|         });
 | ||
|     } else {
 | ||
|         return data.slice();
 | ||
|     }
 | ||
| };
 | ||
| 
 | ||
| ////// 裁剪图片 //////
 | ||
| utils.prototype.splitImage = function (image, width, height) {
 | ||
|     if (typeof image == 'string') {
 | ||
|         image = core.getMappedName(image);
 | ||
|         image = core.material.images.images[image];
 | ||
|     }
 | ||
|     if (!image) return [];
 | ||
|     width = width || 32;
 | ||
|     height = height || width;
 | ||
|     var canvas = document.createElement('canvas');
 | ||
|     var ctx = canvas.getContext('2d');
 | ||
|     var ans = [];
 | ||
|     for (var j = 0; j < image.height; j += height) {
 | ||
|         for (var i = 0; i < image.width; i += width) {
 | ||
|             var w = Math.min(width, image.width - i),
 | ||
|                 h = Math.min(height, image.height - j);
 | ||
|             canvas.width = w;
 | ||
|             canvas.height = h;
 | ||
|             core.drawImage(ctx, image, i, j, w, h, 0, 0, w, h);
 | ||
|             var img = new Image();
 | ||
|             img.src = canvas.toDataURL('image/png');
 | ||
|             ans.push(img);
 | ||
|         }
 | ||
|     }
 | ||
|     return ans;
 | ||
| };
 | ||
| 
 | ||
| ////// 格式化时间为字符串 //////
 | ||
| utils.prototype.formatDate = function (date) {
 | ||
|     if (!date) date = new Date();
 | ||
|     return (
 | ||
|         '' +
 | ||
|         date.getFullYear() +
 | ||
|         '-' +
 | ||
|         core.setTwoDigits(date.getMonth() + 1) +
 | ||
|         '-' +
 | ||
|         core.setTwoDigits(date.getDate()) +
 | ||
|         ' ' +
 | ||
|         core.setTwoDigits(date.getHours()) +
 | ||
|         ':' +
 | ||
|         core.setTwoDigits(date.getMinutes()) +
 | ||
|         ':' +
 | ||
|         core.setTwoDigits(date.getSeconds())
 | ||
|     );
 | ||
| };
 | ||
| 
 | ||
| ////// 格式化时间为最简字符串 //////
 | ||
| utils.prototype.formatDate2 = function (date) {
 | ||
|     if (!date) date = new Date();
 | ||
|     return (
 | ||
|         '' +
 | ||
|         date.getFullYear() +
 | ||
|         core.setTwoDigits(date.getMonth() + 1) +
 | ||
|         core.setTwoDigits(date.getDate()) +
 | ||
|         core.setTwoDigits(date.getHours()) +
 | ||
|         core.setTwoDigits(date.getMinutes()) +
 | ||
|         core.setTwoDigits(date.getSeconds())
 | ||
|     );
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.formatTime = function (time) {
 | ||
|     return (
 | ||
|         core.setTwoDigits(Math.floor(time / 3600000)) +
 | ||
|         ':' +
 | ||
|         core.setTwoDigits(Math.floor(time / 60000) % 60) +
 | ||
|         ':' +
 | ||
|         core.setTwoDigits(Math.floor(time / 1000) % 60)
 | ||
|     );
 | ||
| };
 | ||
| 
 | ||
| ////// 两位数显示 //////
 | ||
| utils.prototype.setTwoDigits = function (x) {
 | ||
|     const num = Number(x);
 | ||
|     return num < 10 && num >= 0 ? '0' + x : x;
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.formatSize = function (size) {
 | ||
|     if (size < 1024) return size + 'B';
 | ||
|     else if (size < 1024 * 1024) return (size / 1024).toFixed(2) + 'KB';
 | ||
|     else return (size / 1024 / 1024).toFixed(2) + 'MB';
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.formatBigNumber = function (x, digits) {
 | ||
|     if (typeof x === 'string') return x;
 | ||
|     if (digits === true) digits = 4; // 兼容旧版onMap参数
 | ||
|     digits ??= 6;
 | ||
|     x = Math.trunc(parseFloat(x)); // 尝试识别为小数,然后向0取整
 | ||
|     if (x == null || !Number.isFinite(x)) return '???'; // 无法识别的数或正负无穷大,显示'???'
 | ||
|     var units = [
 | ||
|         // 单位及其后缀字母,可自定义,如改成千进制下的K、M、G、T、P
 | ||
|         { val: 1e4, suffix: 'w' },
 | ||
|         { val: 1e8, suffix: 'e' },
 | ||
|         { val: 1e12, suffix: 'z' },
 | ||
|         { val: 1e16, suffix: 'j' },
 | ||
|         { val: 1e20, suffix: 'g' }
 | ||
|     ];
 | ||
|     if (Math.abs(x) > 1e20 * Math.pow(10, digits - 2))
 | ||
|         return x.toExponential(0); // 绝对值过大以致于失去精度的数,直接使用科学记数法,系数只保留整数
 | ||
|     var sign = x < 0 ? '-' : '';
 | ||
|     x = Math.abs(x);
 | ||
| 
 | ||
|     if (x < Math.pow(10, digits)) return sign + x;
 | ||
| 
 | ||
|     for (var i = 0; i < units.length; ++i) {
 | ||
|         var each = units[i];
 | ||
|         var u = (x / each.val)
 | ||
|             .toFixed(digits)
 | ||
|             .slice(0, sign === '-' ? digits : digits + 1);
 | ||
|         if (u.indexOf('.') < 0) continue;
 | ||
|         u = u.substring(
 | ||
|             0,
 | ||
|             u[u.length - 2] == '.' ? u.length - 2 : u.length - 1
 | ||
|         );
 | ||
|         return sign + u + each.suffix;
 | ||
|     }
 | ||
|     return sign + x.toExponential(0);
 | ||
| };
 | ||
| 
 | ||
| ////// 变速移动 //////
 | ||
| utils.prototype.applyEasing = function (name) {
 | ||
|     var list = {
 | ||
|         easeIn: function (t) {
 | ||
|             return Math.pow(t, 3);
 | ||
|         },
 | ||
|         easeOut: function (t) {
 | ||
|             return 1 - Math.pow(1 - t, 3);
 | ||
|         },
 | ||
|         easeInOut: function (t) {
 | ||
|             // easeInOut试了一下感觉二次方效果明显点
 | ||
|             if (t < 0.5) return Math.pow(t, 2) * 2;
 | ||
|             else return 1 - Math.pow(1 - t, 2) * 2;
 | ||
|         },
 | ||
|         linear: function (t) {
 | ||
|             return t;
 | ||
|         }
 | ||
|     };
 | ||
|     if (name == 'random') {
 | ||
|         var keys = Object.keys(list);
 | ||
|         name = keys[Math.floor(Math.random() * keys.length)];
 | ||
|     }
 | ||
|     return list[name] || list.linear;
 | ||
| };
 | ||
| 
 | ||
| ////// 数组转RGB //////
 | ||
| utils.prototype.arrayToRGB = function (color) {
 | ||
|     if (!(color instanceof Array)) return color;
 | ||
|     var nowR = this.clamp(parseInt(color[0]), 0, 255),
 | ||
|         nowG = this.clamp(parseInt(color[1]), 0, 255),
 | ||
|         nowB = this.clamp(parseInt(color[2]), 0, 255);
 | ||
|     return (
 | ||
|         '#' + ((nowR << 16) + (nowG << 8) + nowB).toString(16).padStart(6, '0')
 | ||
|     );
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.arrayToRGBA = function (color) {
 | ||
|     if (!(color instanceof Array)) return color;
 | ||
|     if (color[3] == null) color[3] = 1;
 | ||
|     var nowR = this.clamp(parseInt(color[0]), 0, 255),
 | ||
|         nowG = this.clamp(parseInt(color[1]), 0, 255),
 | ||
|         nowB = this.clamp(parseInt(color[2]), 0, 255),
 | ||
|         nowA = this.clamp(parseFloat(color[3]), 0, 1);
 | ||
|     return 'rgba(' + nowR + ',' + nowG + ',' + nowB + ',' + nowA + ')';
 | ||
| };
 | ||
| 
 | ||
| ////// 加密路线 //////
 | ||
| utils.prototype.encodeRoute = function (route, compress = true) {
 | ||
|     var ans = '',
 | ||
|         lastMove = '',
 | ||
|         cnt = 0;
 | ||
| 
 | ||
|     route.forEach(function (t) {
 | ||
|         if (t === 'up' || t === 'down' || t === 'left' || t === 'right') {
 | ||
|             if (t !== lastMove && cnt > 0) {
 | ||
|                 const char = lastMove[0];
 | ||
|                 if (char) {
 | ||
|                     ans += char.toUpperCase();
 | ||
|                 }
 | ||
|                 if (cnt > 1) ans += cnt;
 | ||
|                 cnt = 0;
 | ||
|             }
 | ||
|             lastMove = t;
 | ||
|             cnt++;
 | ||
|         } else {
 | ||
|             if (cnt > 0) {
 | ||
|                 const char = lastMove[0];
 | ||
|                 if (char) {
 | ||
|                     ans += char.toUpperCase();
 | ||
|                 }
 | ||
|                 if (cnt > 1) ans += cnt;
 | ||
|                 cnt = 0;
 | ||
|             }
 | ||
|             ans += core.utils._encodeRoute_encodeOne(t);
 | ||
|         }
 | ||
|     });
 | ||
|     if (cnt > 0) {
 | ||
|         ans += lastMove[0].toUpperCase();
 | ||
|         if (cnt > 1) ans += cnt;
 | ||
|     }
 | ||
|     if (!compress) {
 | ||
|         return '!!' + ans;
 | ||
|     } else {
 | ||
|         return LZString.compressToBase64(ans);
 | ||
|     }
 | ||
| };
 | ||
| 
 | ||
| utils.prototype._encodeRoute_id2number = function (id) {
 | ||
|     var number = core.maps.getNumberById(id);
 | ||
|     return number === 0 ? id : number;
 | ||
| };
 | ||
| 
 | ||
| utils.prototype._encodeRoute_encodeOne = function (t) {
 | ||
|     if (t.startsWith('item:'))
 | ||
|         return 'I' + this._encodeRoute_id2number(t.slice(5)) + ':';
 | ||
|     else if (t.startsWith('unEquip:')) return 'u' + t.slice(8);
 | ||
|     else if (t.startsWith('saveEquip:')) return 's' + t.slice(10);
 | ||
|     else if (t.startsWith('loadEquip:')) return 'l' + t.slice(10);
 | ||
|     else if (t.startsWith('fly:')) return 'F' + t.slice(4) + ':';
 | ||
|     else if (t === 'choices:none') return 'c';
 | ||
|     else if (t.startsWith('choices:')) return 'C' + t.slice(8);
 | ||
|     else if (t.startsWith('shop:')) return 'S' + t.slice(5) + ':';
 | ||
|     else if (t === 'turn') return 'T';
 | ||
|     else if (t.startsWith('turn:')) return 't' + t[5].toUpperCase() + ':';
 | ||
|     else if (t === 'getNext') return 'G';
 | ||
|     else if (t === 'input:none') return 'p';
 | ||
|     else if (t.startsWith('input:')) return 'P' + t.slice(6);
 | ||
|     else if (t.startsWith('input2:')) return 'Q' + t.slice(7) + ':';
 | ||
|     else if (t === 'no') return 'N';
 | ||
|     else if (t.startsWith('move:')) return 'M' + t.slice(5);
 | ||
|     else if (t.startsWith('key:')) return 'K' + t.slice(4);
 | ||
|     else if (t.startsWith('click:')) return 'k' + t.slice(6);
 | ||
|     else if (t.startsWith('random:')) return 'X' + t.slice(7);
 | ||
|     return '(' + t + ')';
 | ||
| };
 | ||
| 
 | ||
| ////// 解密路线 //////
 | ||
| utils.prototype.decodeRoute = function (route) {
 | ||
|     if (!route) return route;
 | ||
| 
 | ||
|     var decodeObj = { index: 0, ans: [] };
 | ||
|     if (route.startsWith('!!')) decodeObj.index = 2;
 | ||
|     else {
 | ||
|         var v = LZString.decompressFromBase64(route);
 | ||
|         if (v != null && /^[-_a-zA-Z0-9+\/=:()]*$/.test(v)) {
 | ||
|             if (v != '' || route.length < 8) route = v;
 | ||
|         }
 | ||
|     }
 | ||
|     decodeObj.route = route;
 | ||
| 
 | ||
|     while (decodeObj.index < decodeObj.route.length) {
 | ||
|         this._decodeRoute_decodeOne(
 | ||
|             decodeObj,
 | ||
|             decodeObj.route.charAt(decodeObj.index++)
 | ||
|         );
 | ||
|     }
 | ||
| 
 | ||
|     return decodeObj.ans;
 | ||
| };
 | ||
| 
 | ||
| utils.prototype._decodeRoute_getNumber = function (decodeObj, noparse) {
 | ||
|     var num = '';
 | ||
|     var first = true;
 | ||
|     while (true) {
 | ||
|         var ch = decodeObj.route.charAt(decodeObj.index);
 | ||
|         if (ch >= '0' && ch <= '9') num += ch;
 | ||
|         else if (ch == '-' && first) num += ch;
 | ||
|         else break;
 | ||
|         first = false;
 | ||
|         decodeObj.index++;
 | ||
|     }
 | ||
|     if (num.length == 0) num = '1';
 | ||
|     return noparse ? num : parseInt(num);
 | ||
| };
 | ||
| 
 | ||
| utils.prototype._decodeRoute_getString = function (decodeObj) {
 | ||
|     var str = '';
 | ||
|     while (
 | ||
|         decodeObj.index < decodeObj.route.length &&
 | ||
|         decodeObj.route.charAt(decodeObj.index) != ':'
 | ||
|     ) {
 | ||
|         str += decodeObj.route.charAt(decodeObj.index++);
 | ||
|     }
 | ||
|     decodeObj.index++;
 | ||
|     return str;
 | ||
| };
 | ||
| 
 | ||
| utils.prototype._decodeRoute_number2id = function (number) {
 | ||
|     if (/^\d+$/.test(number)) {
 | ||
|         var info = core.maps.blocksInfo[number];
 | ||
|         if (info) return info.id;
 | ||
|     }
 | ||
|     return number;
 | ||
| };
 | ||
| 
 | ||
| utils.prototype._decodeRoute_decodeOne = function (decodeObj, c) {
 | ||
|     // --- 特殊处理自定义项
 | ||
|     if (c == '(') {
 | ||
|         var idx = decodeObj.route.indexOf(')', decodeObj.index);
 | ||
|         if (idx >= 0) {
 | ||
|             decodeObj.ans.push(decodeObj.route.substring(decodeObj.index, idx));
 | ||
|             decodeObj.index = idx + 1;
 | ||
|             return;
 | ||
|         }
 | ||
|     }
 | ||
|     var nxt =
 | ||
|         c == 'I' || c == 'e' || c == 'F' || c == 'S' || c == 'Q' || c == 't'
 | ||
|             ? this._decodeRoute_getString(decodeObj)
 | ||
|             : this._decodeRoute_getNumber(decodeObj);
 | ||
| 
 | ||
|     var mp = { U: 'up', D: 'down', L: 'left', R: 'right' };
 | ||
| 
 | ||
|     switch (c) {
 | ||
|         case 'U':
 | ||
|         case 'D':
 | ||
|         case 'L':
 | ||
|         case 'R':
 | ||
|             for (var i = 0; i < nxt; i++) decodeObj.ans.push(mp[c]);
 | ||
|             break;
 | ||
|         case 'I':
 | ||
|             decodeObj.ans.push('item:' + this._decodeRoute_number2id(nxt));
 | ||
|             break;
 | ||
|         case 'u':
 | ||
|             decodeObj.ans.push('unEquip:' + nxt);
 | ||
|             break;
 | ||
|         case 'e':
 | ||
|             decodeObj.ans.push('equip:' + this._decodeRoute_number2id(nxt));
 | ||
|             break;
 | ||
|         case 's':
 | ||
|             decodeObj.ans.push('saveEquip:' + nxt);
 | ||
|             break;
 | ||
|         case 'l':
 | ||
|             decodeObj.ans.push('loadEquip:' + nxt);
 | ||
|             break;
 | ||
|         case 'F':
 | ||
|             decodeObj.ans.push('fly:' + nxt);
 | ||
|             break;
 | ||
|         case 'c':
 | ||
|             decodeObj.ans.push('choices:none');
 | ||
|             break;
 | ||
|         case 'C':
 | ||
|             decodeObj.ans.push('choices:' + nxt);
 | ||
|             break;
 | ||
|         case 'S':
 | ||
|             decodeObj.ans.push('shop:' + nxt);
 | ||
|             break;
 | ||
|         case 'T':
 | ||
|             decodeObj.ans.push('turn');
 | ||
|             break;
 | ||
|         case 't':
 | ||
|             decodeObj.ans.push('turn:' + mp[nxt]);
 | ||
|             break;
 | ||
|         case 'G':
 | ||
|             decodeObj.ans.push('getNext');
 | ||
|             break;
 | ||
|         case 'p':
 | ||
|             decodeObj.ans.push('input:none');
 | ||
|             break;
 | ||
|         case 'P':
 | ||
|             decodeObj.ans.push('input:' + nxt);
 | ||
|             break;
 | ||
|         case 'Q':
 | ||
|             decodeObj.ans.push('input2:' + nxt);
 | ||
|             break;
 | ||
|         case 'N':
 | ||
|             decodeObj.ans.push('no');
 | ||
|             break;
 | ||
|         case 'M':
 | ||
|             ++decodeObj.index;
 | ||
|             decodeObj.ans.push(
 | ||
|                 'move:' + nxt + ':' + this._decodeRoute_getNumber(decodeObj)
 | ||
|             );
 | ||
|             break;
 | ||
|         case 'K':
 | ||
|             decodeObj.ans.push('key:' + nxt);
 | ||
|             break;
 | ||
|         case 'k':
 | ||
|             ++decodeObj.index;
 | ||
|             var px = this._decodeRoute_getNumber(decodeObj);
 | ||
|             ++decodeObj.index;
 | ||
|             var py = this._decodeRoute_getNumber(decodeObj);
 | ||
|             decodeObj.ans.push('click:' + nxt + ':' + px + ':' + py);
 | ||
|             break;
 | ||
|         case 'X':
 | ||
|             decodeObj.ans.push('random:' + nxt);
 | ||
|             break;
 | ||
|     }
 | ||
| };
 | ||
| 
 | ||
| ////// 判断某对象是否不为null也不为NaN //////
 | ||
| utils.prototype.isset = function (val) {
 | ||
|     return val != null && !(typeof val == 'number' && isNaN(val));
 | ||
| };
 | ||
| 
 | ||
| ////// 获得子数组 //////
 | ||
| utils.prototype.subarray = function (a, b) {
 | ||
|     if (!(a instanceof Array) || !(b instanceof Array) || a.length < b.length)
 | ||
|         return null;
 | ||
|     for (var i = 0; i < b.length; ++i) {
 | ||
|         if (a[i] != b[i]) return null;
 | ||
|     }
 | ||
|     return a.slice(b.length);
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.inArray = function (array, element) {
 | ||
|     return array instanceof Array && array.includes(element);
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.clamp = function (x, a, b) {
 | ||
|     var min = Math.min(a, b),
 | ||
|         max = Math.max(a, b);
 | ||
|     return Math.min(Math.max(x || 0, min), max);
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.getCookie = function (name) {
 | ||
|     var match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
 | ||
|     return match ? match[2] : null;
 | ||
| };
 | ||
| 
 | ||
| ////// 设置statusBar的innerHTML,会自动斜体和放缩,也可以增加自定义css //////
 | ||
| utils.prototype.setStatusBarInnerHTML = function (name, value, css) {};
 | ||
| 
 | ||
| utils.prototype.strlen = function (str) {
 | ||
|     var count = 0;
 | ||
|     for (var i = 0, len = str.length; i < len; i++) {
 | ||
|         count += str.charCodeAt(i) < 256 ? 1 : 2;
 | ||
|     }
 | ||
|     return count;
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.turnDirection = function (turn, direction) {
 | ||
|     direction = direction || core.getHeroLoc('direction');
 | ||
|     var directionList = [
 | ||
|         'left',
 | ||
|         'leftup',
 | ||
|         'up',
 | ||
|         'rightup',
 | ||
|         'right',
 | ||
|         'rightdown',
 | ||
|         'down',
 | ||
|         'leftdown'
 | ||
|     ];
 | ||
|     if (directionList.indexOf(turn) >= 0) return turn;
 | ||
|     if (turn == ':hero') return core.getHeroLoc('direction');
 | ||
|     if (turn == ':backhero')
 | ||
|         return this.turnDirection(':back', core.getHeroLoc('direction'));
 | ||
|     if (typeof turn === 'number' && turn % 45 == 0) turn /= 45;
 | ||
|     else {
 | ||
|         switch (turn) {
 | ||
|             case ':left':
 | ||
|                 turn = 6;
 | ||
|                 break; // turn left
 | ||
|             case ':right':
 | ||
|                 turn = 2;
 | ||
|                 break; // turn right
 | ||
|             case ':back':
 | ||
|                 turn = 4;
 | ||
|                 break; // turn back
 | ||
|             default:
 | ||
|                 turn = 0;
 | ||
|                 break;
 | ||
|         }
 | ||
|     }
 | ||
|     var index = directionList.indexOf(direction);
 | ||
|     if (index < 0) return direction;
 | ||
|     return directionList[(index + (turn || 0)) % directionList.length];
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.matchWildcard = function (pattern, string) {
 | ||
|     try {
 | ||
|         return new RegExp(
 | ||
|             '^' +
 | ||
|                 pattern
 | ||
|                     .split(/\*+/)
 | ||
|                     .map(function (s) {
 | ||
|                         return s.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
 | ||
|                     })
 | ||
|                     .join('.*') +
 | ||
|                 '$'
 | ||
|         ).test(string);
 | ||
|     } catch (e) {
 | ||
|         return false;
 | ||
|     }
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.matchRegex = function (pattern, string) {
 | ||
|     try {
 | ||
|         if (pattern.startsWith('^')) pattern = pattern.substring(1);
 | ||
|         if (pattern.endsWith('$'))
 | ||
|             pattern = pattern.substring(0, pattern.length - 1);
 | ||
|         return new RegExp('^' + pattern + '$').test(string);
 | ||
|     } catch (e) {
 | ||
|         return false;
 | ||
|     }
 | ||
| };
 | ||
| 
 | ||
| ////// Base64加密 //////
 | ||
| utils.prototype.encodeBase64 = function (str) {
 | ||
|     return btoa(
 | ||
|         encodeURIComponent(str).replace(
 | ||
|             /%([0-9A-F]{2})/g,
 | ||
|             function (match, p1) {
 | ||
|                 return String.fromCharCode(parseInt(p1, 16));
 | ||
|             }
 | ||
|         )
 | ||
|     );
 | ||
| };
 | ||
| 
 | ||
| ////// Base64解密 //////
 | ||
| utils.prototype.decodeBase64 = function (str) {
 | ||
|     return decodeURIComponent(
 | ||
|         atob(str)
 | ||
|             .split('')
 | ||
|             .map(function (c) {
 | ||
|                 return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
 | ||
|             })
 | ||
|             .join('')
 | ||
|     );
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.rand = function (num) {
 | ||
|     var rand = core.getFlag('__rand__');
 | ||
|     rand = this.__next_rand(rand);
 | ||
|     core.setFlag('__rand__', rand);
 | ||
|     var ans = rand / 2147483647;
 | ||
|     if (num && num > 0) return Math.floor(ans * num);
 | ||
|     return ans;
 | ||
| };
 | ||
| 
 | ||
| ////// 生成随机数(录像方法) //////
 | ||
| utils.prototype.rand2 = function (num) {
 | ||
|     num = num || 2147483648;
 | ||
|     num = Math.abs(num);
 | ||
| 
 | ||
|     var value;
 | ||
|     if (core.isReplaying()) {
 | ||
|         var action = core.status.replay.toReplay.shift();
 | ||
|         if (action.indexOf('random:') == 0) {
 | ||
|             value = parseInt(action.substring(7));
 | ||
|             if (isNaN(value) || value >= num || value < 0) {
 | ||
|                 console.warn('错误!当前random:项超过范围。将重新随机生成!');
 | ||
|                 value = Math.floor(Math.random() * num);
 | ||
|             }
 | ||
|         } else {
 | ||
|             console.warn('错误!当前需要一个random:项。将重新随机生成!');
 | ||
|             value = Math.floor(Math.random() * num);
 | ||
|         }
 | ||
|     } else {
 | ||
|         value = Math.floor(Math.random() * num);
 | ||
|     }
 | ||
|     core.status.route.push('random:' + value);
 | ||
|     return value;
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.__init_seed = function () {
 | ||
|     var rand = (new Date().getTime() % 34834795) + 3534;
 | ||
|     rand = this.__next_rand(rand);
 | ||
|     rand = this.__next_rand(rand);
 | ||
|     rand = this.__next_rand(rand);
 | ||
|     core.setFlag('__seed__', rand);
 | ||
|     core.setFlag('__rand__', rand);
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.__next_rand = function (_rand) {
 | ||
|     _rand = (_rand % 127773) * 16807 - ~~(_rand / 127773) * 2836;
 | ||
|     _rand += _rand < 0 ? 2147483647 : 0;
 | ||
|     return _rand;
 | ||
| };
 | ||
| 
 | ||
| ////// 读取一个本地文件内容 //////
 | ||
| utils.prototype.readFile = function (success, error, accept, readType) {
 | ||
|     core.platform.successCallback = success;
 | ||
|     core.platform.errorCallback = error;
 | ||
| 
 | ||
|     if (window.jsinterface) {
 | ||
|         window.jsinterface.readFile();
 | ||
|         return;
 | ||
|     }
 | ||
| 
 | ||
|     // step 0: 不为http/https,直接不支持
 | ||
|     if (!core.platform.isOnline) {
 | ||
|         alert('离线状态下不支持文件读取!');
 | ||
|         if (error) error();
 | ||
|         return;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 1: 如果不支持FileReader,直接不支持
 | ||
|     if (core.platform.fileReader == null) {
 | ||
|         alert('当前浏览器不支持FileReader!');
 | ||
|         if (error) error();
 | ||
|         return;
 | ||
|     }
 | ||
| 
 | ||
|     if (core.platform.fileInput == null) {
 | ||
|         core.platform.fileInput = document.createElement('input');
 | ||
|         core.platform.fileInput.style.opacity = 0;
 | ||
|         core.platform.fileInput.type = 'file';
 | ||
|         core.platform.fileInput.onchange = function () {
 | ||
|             var files = core.platform.fileInput.files;
 | ||
|             if (files.length == 0) {
 | ||
|                 if (core.platform.errorCallback) core.platform.errorCallback();
 | ||
|                 return;
 | ||
|             }
 | ||
|             if (!readType)
 | ||
|                 core.platform.fileReader.readAsText(
 | ||
|                     core.platform.fileInput.files[0]
 | ||
|                 );
 | ||
|             else
 | ||
|                 core.platform.fileReader.readAsDataURL(
 | ||
|                     core.platform.fileInput.files[0]
 | ||
|                 );
 | ||
|             core.platform.fileInput.value = '';
 | ||
|         };
 | ||
|     }
 | ||
|     core.platform.fileInput.value = '';
 | ||
|     if (accept) core.platform.fileInput.accept = accept;
 | ||
| 
 | ||
|     core.platform.fileInput.click();
 | ||
| };
 | ||
| 
 | ||
| ////// 读取文件完毕 //////
 | ||
| utils.prototype.readFileContent = function (content) {
 | ||
|     var obj = null;
 | ||
|     if (content.slice(0, 4) === 'data') {
 | ||
|         if (core.platform.successCallback)
 | ||
|             core.platform.successCallback(content);
 | ||
|         return;
 | ||
|     }
 | ||
|     // 检查base64
 | ||
|     try {
 | ||
|         obj = JSON.parse(LZString.decompressFromBase64(content));
 | ||
|     } catch (e) {}
 | ||
|     if (!obj) {
 | ||
|         try {
 | ||
|             obj = JSON.parse(content);
 | ||
|         } catch (e) {
 | ||
|             console.error(e);
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     if (obj) {
 | ||
|         if (core.platform.successCallback) core.platform.successCallback(obj);
 | ||
|         return;
 | ||
|     }
 | ||
| 
 | ||
|     if (core.platform.errorCallback) core.platform.errorCallback();
 | ||
| };
 | ||
| 
 | ||
| ////// 下载文件到本地 //////
 | ||
| utils.prototype.download = function (filename, content) {
 | ||
|     if (window.jsinterface) {
 | ||
|         window.jsinterface.download(filename, content);
 | ||
|         return;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 0: 不为http/https,直接不支持
 | ||
|     if (!core.platform.isOnline) {
 | ||
|         alert('离线状态下不支持下载操作!');
 | ||
|         return;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 1: 如果是iOS平台,直接不支持
 | ||
|     if (core.platform.isIOS) {
 | ||
|         if (core.copy(content)) {
 | ||
|             alert(
 | ||
|                 'iOS平台下不支持直接下载文件!\n所有应下载内容已经复制到您的剪切板,请自行创建空白文件并粘贴。'
 | ||
|             );
 | ||
|         } else {
 | ||
|             alert('iOS平台下不支持下载操作!');
 | ||
|         }
 | ||
|         return;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 2: 如果不是PC平台(Android),则只支持chrome
 | ||
|     if (!core.platform.isPC) {
 | ||
|         if (
 | ||
|             !core.platform.isChrome ||
 | ||
|             core.platform.isQQ ||
 | ||
|             core.platform.isWeChat
 | ||
|         ) {
 | ||
|             // 检测chrome
 | ||
|             if (core.copy(content)) {
 | ||
|                 alert(
 | ||
|                     '移动端只有Chrome浏览器支持直接下载文件!\n所有应下载内容已经复制到您的剪切板,请自行创建空白文件并粘贴。'
 | ||
|                 );
 | ||
|             } else {
 | ||
|                 alert('该平台或浏览器暂不支持下载操作!');
 | ||
|             }
 | ||
|             return;
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     // Step 3: 如果是Safari浏览器,则提示并打开新窗口
 | ||
|     if (core.platform.isSafari) {
 | ||
|         alert(
 | ||
|             '你当前使用的是Safari浏览器,不支持直接下载文件。\n即将打开一个新窗口为应下载内容,请自行全选复制然后创建空白文件并粘贴。'
 | ||
|         );
 | ||
|         var blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
 | ||
|         var href = window.URL.createObjectURL(blob);
 | ||
|         var opened = window.open(href, '_blank');
 | ||
|         window.URL.revokeObjectURL(href);
 | ||
|         return;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 4: 下载
 | ||
|     var blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
 | ||
|     if (window.navigator.msSaveOrOpenBlob) {
 | ||
|         window.navigator.msSaveBlob(blob, filename);
 | ||
|     } else {
 | ||
|         var href = window.URL.createObjectURL(blob);
 | ||
|         var elem = window.document.createElement('a');
 | ||
|         elem.href = href;
 | ||
|         elem.download = filename;
 | ||
|         document.body.appendChild(elem);
 | ||
|         elem.click();
 | ||
|         document.body.removeChild(elem);
 | ||
|         window.URL.revokeObjectURL(href);
 | ||
|     }
 | ||
| };
 | ||
| 
 | ||
| ////// 复制一段内容到剪切板 //////
 | ||
| utils.prototype.copy = function (data) {
 | ||
|     if (window.jsinterface) {
 | ||
|         window.jsinterface.copy(data);
 | ||
|         return true;
 | ||
|     }
 | ||
| 
 | ||
|     if (!core.platform.supportCopy) return false;
 | ||
| 
 | ||
|     var textArea = document.createElement('textarea');
 | ||
|     textArea.style.position = 'fixed';
 | ||
|     textArea.style.top = 0;
 | ||
|     textArea.style.left = 0;
 | ||
|     textArea.style.width = '2em';
 | ||
|     textArea.style.height = '2em';
 | ||
|     textArea.style.padding = 0;
 | ||
|     textArea.style.border = 'none';
 | ||
|     textArea.style.outline = 'none';
 | ||
|     textArea.style.boxShadow = 'none';
 | ||
|     textArea.style.background = 'transparent';
 | ||
|     textArea.value = data;
 | ||
|     document.body.appendChild(textArea);
 | ||
|     textArea.focus();
 | ||
|     textArea.setSelectionRange(0, textArea.value.length);
 | ||
|     var successful = false;
 | ||
|     try {
 | ||
|         successful = document.execCommand('copy');
 | ||
|     } catch (err) {
 | ||
|         successful = false;
 | ||
|     }
 | ||
| 
 | ||
|     document.body.removeChild(textArea);
 | ||
|     return successful;
 | ||
| };
 | ||
| 
 | ||
| ////// 显示一段confirm //////
 | ||
| utils.prototype.myconfirm = function (hint, yesCallback, noCallback) {
 | ||
|     main.dom.inputDiv.style.display = 'block';
 | ||
|     main.dom.inputMessage.innerHTML = hint.replace(/\n/g, '<br/>');
 | ||
|     main.dom.inputBox.style.display = 'none';
 | ||
|     main.dom.inputYes.blur();
 | ||
|     main.dom.inputNo.blur();
 | ||
|     core.status.holdingKeys = [];
 | ||
| 
 | ||
|     core.platform.successCallback = yesCallback;
 | ||
|     core.platform.errorCallback = noCallback;
 | ||
| };
 | ||
| 
 | ||
| ////// 让用户输入一段文字 //////
 | ||
| utils.prototype.myprompt = function (hint, value, callback) {
 | ||
|     main.dom.inputDiv.style.display = 'block';
 | ||
|     main.dom.inputMessage.innerHTML = hint.replace(/\n/g, '<br/>');
 | ||
|     main.dom.inputBox.style.display = 'block';
 | ||
|     main.dom.inputBox.value = value == null ? '' : value;
 | ||
|     main.dom.inputYes.blur();
 | ||
|     main.dom.inputNo.blur();
 | ||
|     setTimeout(function () {
 | ||
|         main.dom.inputBox.focus();
 | ||
|     });
 | ||
|     core.status.holdingKeys = [];
 | ||
| 
 | ||
|     core.platform.successCallback = core.platform.errorCallback = callback;
 | ||
| };
 | ||
| 
 | ||
| ////// 动画显示某对象 //////
 | ||
| utils.prototype.showWithAnimate = function (obj, speed, callback) {
 | ||
|     obj.style.display = 'block';
 | ||
|     if (!speed || main.mode != 'play') {
 | ||
|         obj.style.opacity = 1;
 | ||
|         if (callback) callback();
 | ||
|         return;
 | ||
|     }
 | ||
|     obj.style.opacity = 0;
 | ||
|     var opacityVal = 0;
 | ||
|     var showAnimate = window.setInterval(function () {
 | ||
|         opacityVal += 0.03;
 | ||
|         obj.style.opacity = opacityVal;
 | ||
|         if (opacityVal > 1) {
 | ||
|             clearInterval(showAnimate);
 | ||
|             if (callback) callback();
 | ||
|         }
 | ||
|     }, speed);
 | ||
| };
 | ||
| 
 | ||
| ////// 动画使某对象消失 //////
 | ||
| utils.prototype.hideWithAnimate = function (obj, speed, callback) {
 | ||
|     if (!speed || main.mode != 'play') {
 | ||
|         obj.style.display = 'none';
 | ||
|         if (callback) callback();
 | ||
|         return;
 | ||
|     }
 | ||
|     obj.style.opacity = 1;
 | ||
|     var opacityVal = 1;
 | ||
|     var hideAnimate = window.setInterval(function () {
 | ||
|         opacityVal -= 0.03;
 | ||
|         obj.style.opacity = opacityVal;
 | ||
|         if (opacityVal < 0) {
 | ||
|             obj.style.display = 'none';
 | ||
|             clearInterval(hideAnimate);
 | ||
|             if (callback) callback();
 | ||
|         }
 | ||
|     }, speed);
 | ||
| };
 | ||
| 
 | ||
| ////// 生成浏览器唯一的 guid //////
 | ||
| utils.prototype.getGuid = function () {
 | ||
|     var guid = localStorage.getItem('guid');
 | ||
|     if (guid != null) return guid;
 | ||
|     guid = 'xxxxxxxx_xxxx_4xxx_yxxx_xxxxxxxxxxxx'.replace(
 | ||
|         /[xy]/g,
 | ||
|         function (c) {
 | ||
|             var r = (Math.random() * 16) | 0,
 | ||
|                 v = c == 'x' ? r : (r & 0x3) | 0x8;
 | ||
|             return v.toString(16);
 | ||
|         }
 | ||
|     );
 | ||
|     localStorage.setItem('guid', guid);
 | ||
|     return guid;
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.hashCode = function (obj) {
 | ||
|     if (typeof obj == 'string') {
 | ||
|         var hash = 0,
 | ||
|             i,
 | ||
|             chr;
 | ||
|         if (obj.length === 0) return hash;
 | ||
|         for (i = 0; i < obj.length; i++) {
 | ||
|             chr = obj.charCodeAt(i);
 | ||
|             hash = (hash << 5) - hash + chr;
 | ||
|             hash |= 0;
 | ||
|         }
 | ||
|         return hash;
 | ||
|     }
 | ||
|     return this.hashCode(JSON.stringify(obj).split('').sort().join(''));
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.same = function (a, b) {
 | ||
|     if (a === b) return true;
 | ||
|     if (a == null || b == null) return false;
 | ||
|     if (a instanceof Array && b instanceof Array) {
 | ||
|         if (a.length !== b.length) return false;
 | ||
|         for (var i = 0; i < a.length; i++) {
 | ||
|             if (!this.same(a[i], b[i])) return false;
 | ||
|         }
 | ||
|         return true;
 | ||
|     }
 | ||
|     if (a instanceof Object && b instanceof Object) {
 | ||
|         const toCompare = new Set([...Object.keys(a), ...Object.keys(b)]);
 | ||
|         for (const key of toCompare) {
 | ||
|             if (!this.same(a[key], b[key])) return false;
 | ||
|         }
 | ||
|         return true;
 | ||
|     }
 | ||
|     return false;
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.unzip = function (
 | ||
|     blobOrUrl,
 | ||
|     success,
 | ||
|     error,
 | ||
|     convertToText,
 | ||
|     onprogress
 | ||
| ) {
 | ||
|     var _error = function (msg) {
 | ||
|         console.error(msg);
 | ||
|         if (error) error(msg);
 | ||
|     };
 | ||
| 
 | ||
|     if (!window.zip) {
 | ||
|         return _error('zip.js not exists!');
 | ||
|     }
 | ||
| 
 | ||
|     if (typeof blobOrUrl == 'string') {
 | ||
|         return core.http(
 | ||
|             'GET',
 | ||
|             blobOrUrl,
 | ||
|             null,
 | ||
|             function (data) {
 | ||
|                 core.unzip(data, success, error, convertToText);
 | ||
|             },
 | ||
|             _error,
 | ||
|             null,
 | ||
|             'blob',
 | ||
|             onprogress
 | ||
|         );
 | ||
|     }
 | ||
| 
 | ||
|     if (!(blobOrUrl instanceof Blob)) {
 | ||
|         return _error('Should use Blob or URL as input');
 | ||
|     }
 | ||
| 
 | ||
|     zip.createReader(
 | ||
|         new zip.BlobReader(blobOrUrl),
 | ||
|         function (reader) {
 | ||
|             reader.getEntries(function (entries) {
 | ||
|                 core.utils._unzip_readEntries(
 | ||
|                     entries,
 | ||
|                     function (data) {
 | ||
|                         reader.close(function () {
 | ||
|                             if (success) success(data);
 | ||
|                         });
 | ||
|                     },
 | ||
|                     convertToText
 | ||
|                 );
 | ||
|             });
 | ||
|         },
 | ||
|         _error
 | ||
|     );
 | ||
| };
 | ||
| 
 | ||
| utils.prototype._unzip_readEntries = function (
 | ||
|     entries,
 | ||
|     success,
 | ||
|     convertToText
 | ||
| ) {
 | ||
|     var results = {};
 | ||
|     if (entries == null || entries.length == 0) {
 | ||
|         return success(results);
 | ||
|     }
 | ||
|     var length = entries.length;
 | ||
|     entries.forEach(function (entry) {
 | ||
|         entry.getData(
 | ||
|             convertToText ? new zip.TextWriter('utf8') : new zip.BlobWriter(),
 | ||
|             function (data) {
 | ||
|                 results[entry.filename] = data;
 | ||
|                 length--;
 | ||
|                 if (length == 0) {
 | ||
|                     success(results);
 | ||
|                 }
 | ||
|             }
 | ||
|         );
 | ||
|     });
 | ||
| };
 | ||
| 
 | ||
| utils.prototype.http = function (
 | ||
|     type,
 | ||
|     url,
 | ||
|     formData,
 | ||
|     success,
 | ||
|     error,
 | ||
|     mimeType,
 | ||
|     responseType,
 | ||
|     onprogress
 | ||
| ) {
 | ||
|     var xhr = new XMLHttpRequest();
 | ||
|     xhr.open(type, url, true);
 | ||
|     if (mimeType) xhr.overrideMimeType(mimeType);
 | ||
|     if (responseType) xhr.responseType = responseType;
 | ||
|     xhr.onload = function (e) {
 | ||
|         if (xhr.status == 200) {
 | ||
|             if (success) success(xhr.response);
 | ||
|         } else {
 | ||
|             if (error) error('HTTP ' + xhr.status);
 | ||
|         }
 | ||
|     };
 | ||
|     xhr.onprogress = function (e) {
 | ||
|         if (e.lengthComputable) {
 | ||
|             if (onprogress) onprogress(e.loaded, e.total);
 | ||
|         }
 | ||
|     };
 | ||
|     xhr.onabort = function () {
 | ||
|         if (error) error('Abort');
 | ||
|     };
 | ||
|     xhr.ontimeout = function () {
 | ||
|         if (error) error('Timeout');
 | ||
|     };
 | ||
|     xhr.onerror = function () {
 | ||
|         if (error) error('Error on Connection');
 | ||
|     };
 | ||
|     if (formData) xhr.send(formData);
 | ||
|     else xhr.send();
 | ||
| };
 | ||
| 
 | ||
| // LZW-compress
 | ||
| // https://gist.github.com/revolunet/843889
 | ||
| function lzw_encode(s) {
 | ||
|     var dict = {};
 | ||
|     var data = (s + '').split('');
 | ||
|     var out = [];
 | ||
|     var currChar;
 | ||
|     var phrase = data[0];
 | ||
|     var code = 256;
 | ||
|     for (var i = 1; i < data.length; i++) {
 | ||
|         currChar = data[i];
 | ||
|         if (dict[phrase + currChar] != null) {
 | ||
|             phrase += currChar;
 | ||
|         } else {
 | ||
|             out.push(phrase.length > 1 ? dict[phrase] : phrase.charCodeAt(0));
 | ||
|             dict[phrase + currChar] = code;
 | ||
|             code++;
 | ||
|             phrase = currChar;
 | ||
|         }
 | ||
|     }
 | ||
|     out.push(phrase.length > 1 ? dict[phrase] : phrase.charCodeAt(0));
 | ||
|     for (var i = 0; i < out.length; i++) {
 | ||
|         out[i] = String.fromCharCode(out[i]);
 | ||
|     }
 | ||
|     return out.join('');
 | ||
| }
 | ||
| 
 | ||
| // Decompress an LZW-encoded string
 | ||
| function lzw_decode(s) {
 | ||
|     var dict = {};
 | ||
|     var data = (s + '').split('');
 | ||
|     var currChar = data[0];
 | ||
|     var oldPhrase = currChar;
 | ||
|     var out = [currChar];
 | ||
|     var code = 256;
 | ||
|     var phrase;
 | ||
|     for (var i = 1; i < data.length; i++) {
 | ||
|         var currCode = data[i].charCodeAt(0);
 | ||
|         if (currCode < 256) {
 | ||
|             phrase = data[i];
 | ||
|         } else {
 | ||
|             phrase = dict[currCode] ? dict[currCode] : oldPhrase + currChar;
 | ||
|         }
 | ||
|         out.push(phrase);
 | ||
|         currChar = phrase.charAt(0);
 | ||
|         dict[code] = oldPhrase + currChar;
 | ||
|         code++;
 | ||
|         oldPhrase = phrase;
 | ||
|     }
 | ||
|     return out.join('');
 | ||
| }
 |