Browse Source

存档功能更新

1.调整存档存储位置
2.导出存档加密
3.增加导入存档时装备强化等级校验
v1.0
许孟阳 2 weeks ago
parent
commit
659f818df0
  1. 1
      package.json
  2. 4
      src/config/base.ts
  3. 3
      src/config/equips/bean.ts
  4. 33
      src/store/action.ts
  5. 81
      src/tool/IndexedDB.ts
  6. 75
      src/tool/archive.ts
  7. 41
      src/tool/crypot.ts
  8. 3
      src/tool/index.ts
  9. 20
      src/views/archive.vue
  10. 2
      src/views/save-game.vue
  11. 5
      yarn.lock

1
package.json

@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
},
"dependencies": {
"core-js": "^3.8.3",
"crypto-js": "^4.2.0",
"js-base64": "^3.7.7",
"vue": "^3.2.13",
"vue-class-component": "^8.0.0-0",

4
src/config/base.ts

@ -1,3 +1,6 @@ @@ -1,3 +1,6 @@
import { Player, RebornPoints } from './beings';
import { Equip } from './equips';
export const backpack_num = 32;
export const reborn_arrts = ['hp', 'atk', 'crit', 'critDmg', 'def', 'bloc', 'recoverSpeed', 'moveSpeed', 'battleSpeed'];
export const reborn_point_coefficients = {
@ -13,4 +16,3 @@ export const reborn_point_coefficients = { @@ -13,4 +16,3 @@ export const reborn_point_coefficients = {
};
export const player_move_time = 3000;
export const player_battle_time = 1000;
export const archive_name = 'transmigration_game_archive';

3
src/config/equips/bean.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { quality_collor, extra_entry_num } from './constant';
import { extra_entry_num } from './constant';
export class Entry {
type: string;
@ -45,6 +45,7 @@ export class EquipBase { @@ -45,6 +45,7 @@ export class EquipBase {
}
export class Equip {
id?: string;
type: string;
lv: number;
locked: boolean = false;

33
src/store/action.ts

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
import { initialWeapon, initialArmor, initialNeck, initialRing, i18n, archive_name, audio_mp3 } from '@/config';
import { conisOfsell } from '@/tool';
import { Base64 } from 'js-base64';
import { initialWeapon, initialArmor, initialNeck, initialRing, i18n, audio_mp3 } from '@/config';
import { conisOfsell, saveArchive, getArchive, getFromStore } from '@/tool';
const backgound = new Audio(audio_mp3.background);
backgound.loop = true;
@ -68,28 +67,20 @@ export const useEquip = ({ state, commit }, index) => { @@ -68,28 +67,20 @@ export const useEquip = ({ state, commit }, index) => {
};
export const saveGame = ({ state, commit }) => {
const player = state.playerAttribute;
const data = {
equips: [player.weapon, player.armor, player.ring, player.neck, player.jewelry, player.pants, player.shoes, player.bracers],
lv: player.lv,
coins: player.coins,
backpack: state.grid,
autoSell: state.autoSell,
shop: state.shop,
reborn: state.rebornPoints,
};
var saveData = Base64.encode(Base64.encode(JSON.stringify(data)));
localStorage.setItem(archive_name, saveData);
saveArchive(state);
commit('set_sys_info', { msg: t('saveGame.2'), type: 'win' });
};
export const loadGame = ({ commit }, data?) => {
if (!data) {
try {
const archive = localStorage.getItem(archive_name) || '';
data = JSON.parse(Base64.decode(Base64.decode(archive)));
} catch (error) {}
if (data) {
loadArchive(commit, data);
return;
}
getArchive().then((rsp: any) => {
loadArchive(commit, rsp);
});
};
const loadArchive = (commit, data) => {
if (!data) {
commit('set_sys_info', { msg: t('loadEmpty'), type: 'warning' });
} else if (data == 'undfind') {
@ -98,7 +89,7 @@ export const loadGame = ({ commit }, data?) => { @@ -98,7 +89,7 @@ export const loadGame = ({ commit }, data?) => {
commit('set_player_equips', data.equips);
commit('set_player_lv', data.lv || 1);
commit('set_player_coins', data.coins || 0);
commit('set_backpack', data.backpack);
commit('set_backpack', data.grid);
commit('set_auto_sell', data.autoSell);
commit('set_shop', data.shop);
data.reborn && (data.reborn.recoverSpeed = 0);

81
src/tool/IndexedDB.ts

@ -0,0 +1,81 @@ @@ -0,0 +1,81 @@
import { deepCopy } from './caller';
const db_name = 'transmigration_game';
export const store_name_archive = 'archive';
export const key_name_archive = 'version';
const version = 1;
let db;
/**
*
* @param {object} dbName
* @param {string} version
* @return {object}
*/
function openDB() {
return new Promise((resolve, reject) => {
// 兼容浏览器
var indexedDB = window.indexedDB;
// 打开数据库,若没有则会创建
const request = indexedDB.open(db_name, version);
// 数据库打开成功回调
request.onsuccess = function (e) {
db = request.result; // 数据库对象
console.log('数据库打开成功');
resolve(db);
};
// 数据库打开失败的回调
request.onerror = function (e) {
console.log('数据库打开报错');
};
// 数据库有更新时候的回调
request.onupgradeneeded = function (e) {
// 数据库创建或升级的时候会触发
console.log('onupgradeneeded');
db = request.result; // 数据库对象
createArchiveStore();
};
});
}
const createArchiveStore = () => {
if (!db.objectStoreNames.contains(store_name_archive)) {
const objectStore = db.createObjectStore(store_name_archive, { keyPath: key_name_archive });
// 创建索引,在后面查询数据的时候可以根据索引查
// objectStore.createIndex(index_name_equip, index_name_equip, { unique: false });
}
};
await openDB();
export const insertToStore = (storeName, data) => {
data = deepCopy(data);
return new Promise<boolean>((resolve, reject) => {
const request = db
.transaction([storeName], 'readwrite') // 事务对象 指定表格名称和操作模式("只读"或"读写")
.objectStore(storeName) // 仓库对象
.put(data);
request.onsuccess = function (event) {
resolve(true);
};
request.onerror = function (event) {
resolve(false);
};
});
};
export const getFromStore = (storeName, id) => {
return new Promise((resolve, reject) => {
const store = db.transaction([storeName], 'readonly').objectStore(storeName);
const request = store.get(id);
request.onsuccess = (e) => {
resolve(request.result);
};
request.onerror = (e) => {
resolve([]);
};
});
};

75
src/tool/archive.ts

@ -0,0 +1,75 @@ @@ -0,0 +1,75 @@
import { Equip, Player, RebornPoints } from '@/config';
import { getFromStore, insertToStore, store_name_archive } from './IndexedDB';
import { uuid } from './random';
import { version } from 'vue';
const archive_version = '1.0';
const archive_version_strengthen = '1.0_flag';
export class GameArchive {
version: String;
equips: Equip[];
lv: number;
coins: number;
grid: any[];
autoSell: string[];
shop: any[];
reborn: RebornPoints;
constructor(player: Player, grid: any[], autoSell: any[], shop: any[], reboren: RebornPoints) {
this.version = archive_version;
this.equips = [player.weapon, player.armor, player.ring, player.neck, player.jewelry, player.pants, player.shoes, player.bracers];
this.lv = player.lv;
this.coins = player.coins;
this.grid = grid;
this.autoSell = autoSell;
this.shop = shop;
this.reborn = reboren;
}
}
export const saveArchive = (state) => {
const archive = new GameArchive(state.playerAttribute, state.grid, state.autoSell, state.shop, state.rebornPoints);
getFromStore(store_name_archive, archive_version_strengthen).then((flag: any) => {
const time = new Date().getTime();
if (!flag || !flag.time || flag.time + 10 * 60 * 1000 < time) {
flag = { version: archive_version_strengthen, time: time };
}
const equips = new Array();
Array.prototype.push.apply(equips, archive.equips);
Array.prototype.push.apply(equips, archive.grid);
equips.forEach((equip) => {
if (!equip) return;
if (equip.strengthenLv > 0 && !equip.id) {
equip.id = uuid();
}
if (equip.id) {
flag[equip.id] = equip.strengthenLv;
}
});
insertToStore(store_name_archive, archive);
insertToStore(store_name_archive, flag);
});
};
export const checkImportArchive = async (data): Promise<void> => {
getFromStore(store_name_archive, archive_version_strengthen).then((rsp: any) => {
if (!rsp) return;
const equips = new Array();
Array.prototype.push.apply(equips, data.equips);
Array.prototype.push.apply(equips, data.grid);
equips.forEach((equip) => {
if (!equip || !equip.id) return;
const slv = rsp[equip.id];
slv && (equip.strengthenLv = slv);
});
});
};
export const getArchive = () => {
return new Promise((resolve, reject) => {
getFromStore(store_name_archive, archive_version).then((rsp: any) => {
resolve(rsp);
});
});
};

41
src/tool/crypot.ts

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
import CryptoJS from 'crypto-js';
/**
* AES-256-CBC对称加密
* @param text {string}
* @param secretKey {string} 43
* @returns {string} Base64格式
*/
export const AES_CBC_ENCRYPT = (text, secretKey) => {
var keyHex = CryptoJS.enc.Base64.parse(secretKey);
var ivHex = keyHex.clone();
// 前16字节作为向量
ivHex.sigBytes = 16;
ivHex.words.splice(4);
var messageHex = CryptoJS.enc.Utf8.parse(text);
var encrypted = CryptoJS.AES.encrypt(messageHex, keyHex, {
iv: ivHex,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
});
return encrypted.toString();
};
/**
* AES-256-CBC对称解密
* @param textBase64 {string} Base64格式
* @param secretKey {string} 43
* @returns {string}
*/
export const AES_CBC_DECRYPT = (textBase64, secretKey) => {
var keyHex = CryptoJS.enc.Base64.parse(secretKey);
var ivHex = keyHex.clone();
// 前16字节作为向量
ivHex.sigBytes = 16;
ivHex.words.splice(4);
var decrypt = CryptoJS.AES.decrypt(textBase64, keyHex, {
iv: ivHex,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
});
return CryptoJS.enc.Utf8.stringify(decrypt);
};

3
src/tool/index.ts

@ -3,6 +3,9 @@ export * from './formatter'; @@ -3,6 +3,9 @@ export * from './formatter';
export * from './random';
export * from './mixins';
export * from './mobile';
export * from './IndexedDB';
export * from './crypot';
export * from './archive';
export const getArrayEmptyIdx = (array) => {
for (let i = 0; i < array.length; i++) {

20
src/views/archive.vue

@ -22,8 +22,9 @@ import { useStore } from "vuex"; @@ -22,8 +22,9 @@ import { useStore } from "vuex";
import { computed, onMounted, ref, onBeforeUnmount } from "vue";
import { useI18n } from "vue3-i18n";
import { Tooltip, Dialog } from "@/components"
import { menu_icons, archive_name } from "@/config";
import { Base64 } from "js-base64";
import { menu_icons } from "@/config";
import { getArchive, AES_CBC_ENCRYPT, AES_CBC_DECRYPT, checkImportArchive, saveArchive } from "@/tool";
const { t } = useI18n();
const { state, commit, dispatch } = useStore();
@ -31,13 +32,16 @@ const showArchive = computed(() => { @@ -31,13 +32,16 @@ const showArchive = computed(() => {
return state.curMenu == 'archive';
});
const archive = ref('');
const key = 'KUf4hM5rThssysJhcRFCfxLR8Imihjl0eMsyhh1M7Wk';
const showMenu = () => {
if (showArchive.value) {
state.curMenu = null;
} else {
state.curMenu = 'archive';
archive.value = localStorage.getItem(archive_name) || '';
getArchive().then((rsp: any) => {
archive.value = AES_CBC_ENCRYPT(JSON.stringify(rsp), key);
});
}
}
@ -59,16 +63,18 @@ const pasteArchive = () => { @@ -59,16 +63,18 @@ const pasteArchive = () => {
});
}
const importArchive = () => {
const importArchive = async () => {
try {
const data = JSON.parse(Base64.decode(Base64.decode(archive.value)));
const data = JSON.parse(AES_CBC_DECRYPT(archive.value, key));
if (!data || data == 'undfind') {
commit('set_sys_info', { msg: t('loadEmpty'), type: 'warning' });
}
dispatch('loadGame', data);
localStorage.setItem(archive_name, archive.value);
await checkImportArchive(data);
await dispatch('loadGame', data);
saveArchive(state);
showMenu();
} catch (error) {
console.log(error);
commit('set_sys_info', { msg: t('loadError'), type: 'warning' });
}
}

2
src/views/save-game.vue

@ -9,7 +9,7 @@ import { useStore } from "vuex"; @@ -9,7 +9,7 @@ import { useStore } from "vuex";
import { onBeforeUnmount, onMounted, ref } from "vue";
import { useI18n } from "vue3-i18n";
import { Tooltip } from "@/components"
import { menu_icons, archive_name } from "@/config";
import { menu_icons } from "@/config";
import { Base64 } from "js-base64";
const { t } = useI18n();

5
yarn.lock

@ -2448,6 +2448,11 @@ cross-spawn@^7.0.3: @@ -2448,6 +2448,11 @@ cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
crypto-js@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
css-declaration-sorter@^6.3.1:
version "6.4.1"
resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz#28beac7c20bad7f1775be3a7129d7eae409a3a71"

Loading…
Cancel
Save