UNPKG

skynovel

Version:
661 lines (543 loc) 21.1 kB
/* ***** BEGIN LICENSE BLOCK ***** Copyright (c) 2018-2020 Famibee (famibee.blog38.fc2.com) This software is released under the MIT License. http://opensource.org/licenses/mit-license.php ** ***** END LICENSE BLOCK ***** */ import {CmnLib, uint, int, getDateStr} from './CmnLib'; import {HArg, IHTag, IVariable, ISetVal, typeProcVal, ISysBase, IData4Vari, IMark} from './CmnInterface'; import {Config} from './Config'; import {Areas} from './Areas'; import {PropParser} from './PropParser'; export class Variable implements IVariable { private hScope : any = {sys:{}, save:{}, tmp:{}, mp:{}}; private hSave : any = this.hScope.save; private hTmp : any = this.hScope.tmp; constructor(private readonly cfg: Config, hTag: IHTag) { // 変数操作 hTag.let = o=> this.let(o); // 変数代入・演算 hTag.let_abs = o=> this.let_abs(o); // 絶対値 hTag.let_char_at = o=> this.let_char_at(o); // 文字列から一字取りだし hTag.let_index_of = o=> this.let_index_of(o); // 文字列で検索 hTag.let_length = o=> this.let_length(o); // 文字列の長さ // let_mlはScriptIteratorにて定義 // インラインテキスト代入 hTag.let_replace = o=> this.let_replace(o); // 正規表現で置換 hTag.let_round = o=> this.let_round(o); // 四捨五入 hTag.let_search = o=> this.let_search(o); // 正規表現で検索 hTag.let_substr = o=> this.let_substr(o); // 文字列から抜きだし // デバッグ・その他 hTag.clearsysvar = ()=> this.clearsysvar(); // システム変数の全消去 hTag.clearvar = ()=> this.clearvar(); // ゲーム変数の全消去 hTag.dump_val = ()=> this.dump_val(); // 変数のダンプ // しおり hTag.copybookmark = o=> this.copybookmark(o); // しおりの複写 hTag.erasebookmark = o=> this.erasebookmark(o);// しおりの消去 //hTag.load // ScriptIterator.ts内で定義 // しおりの読込 //hTag.record_place // ScriptIterator.ts内で定義 // セーブポイント指定 //hTag.save // ScriptIterator.ts内で定義 // しおりの保存 // save: this.hSave['sn.userFnTail'] = ''; this.defTmp('const.sn.bookmark.json', ()=> { const a: object[] = []; Object.keys(this.data.mark).sort().forEach(k=> { const o = {...this.data.mark[k].json}; for (const key in o) { const v = o[key]; if (typeof v != 'string') continue; if (v.substr(0, 10) != 'userdata:/') continue; o[key] = cfg.searchPath(v); } o.place = k; a.push(o); }); return JSON.stringify(a); }); // tmp: this.hTmp['const.sn.isFirstBoot'] = true; this.hTmp['sn.tagL.enabled'] = true; // 頁末まで一気に読み進むか(l無視) this.hTmp['sn.skip.all'] = false; // falseなら既読のみをスキップ this.hTmp['sn.skip.enabled'] = false; // 次の選択肢(/未読)まで進むが有効か this.hTmp['sn.auto.enabled'] = false; // 自動読みすすみモードかどうか this.hTmp['const.sn.last_page_text'] = ''; //this.hTmp['const.sn.mouse.middle'] // ScriptIterator で定義 //this.hTmp['const.sn.vctCallStk.length'] // ScriptIterator で定義 /* this.hTmp['const.Stage.supportsOrientationChange'] = Stage.supportsOrientationChange; if (this.hTmp['const.Stage.supportsOrientationChange']) { this.hTmp['const.Stage.orientation'] = ()=> {return stage.orientation;}; this.hTmp['const.Stage.deviceOrientation'] = ()=> {return stage.deviceOrientation;}; const lenSO:uint = stage.supportedOrientations.length; for (let iSO:uint=0; iSO<lenSO; ++iSO) { this.hTmp['const.Stage.supportedOrientations.' + stage.supportedOrientations[iSO] ] = true; } } else { // import flash.display.StageOrientation; this.hTmp['const.Stage.orientation'] = this.hTmp['const.Stage.deviceOrientation'] = // StageOrientation.DEFAULT; 'default'; } */ this.hTmp['const.sn.displayState'] = false; // const.flash.display.Stage.displayState this.hTmp['const.Date.getTime'] = ()=> (new Date).getTime(); this.hTmp['const.Date.getDateStr'] = ()=> getDateStr(); this.hTmp['const.Stage.mouseX'] = ()=> { // return stage.mouseX; return 0; }; this.hTmp['const.Stage.mouseY'] = ()=> { // return stage.mouseY; return 0; }; this.hTmp['const.sn.platform'] = JSON.stringify(CmnLib.platform); this.clearsysvar(); this.clearvar(); // prj.json this.hTmp['const.sn.config.window.width'] = cfg.oCfg.window.width; this.hTmp['const.sn.config.window.height']= cfg.oCfg.window.height; this.hTmp['const.sn.config.book.title'] = cfg.oCfg.book.title; this.hTmp['const.sn.config.book.version'] = cfg.oCfg.book.version; this.hTmp['const.sn.Math.PI'] = Math.PI; if (typeof window == 'undefined') return; const win: any = window; const ac = win['AudioContext'] ?? win['webkitAudioContext']; this.hTmp['const.sn.needClick2Play'] = ()=> new ac().state == 'suspended'; // ダークモード切り替え検知 const dmmq = window.matchMedia('(prefers-color-scheme: dark)'); this.hTmp['const.sn.isDarkMode'] = CmnLib.isDarkMode = dmmq.matches; dmmq.addListener(e=> this.hTmp['const.sn.isDarkMode'] = CmnLib.isDarkMode = e.matches); } private data : IData4Vari = {sys:{}, mark:{}, kidoku:{}}; private hSys : any; private hAreaKidoku : {[name: string]: Areas} = {}; setSys(sys: ISysBase) { sys.initVal(this.data, this.hTmp, data=> { this.data = data; this.hSys = this.hScope.sys = this.data.sys; for (const fn in this.data.kidoku) { const areas = new Areas(); areas.hAreas = {...this.data.kidoku[fn]}; this.hAreaKidoku[fn] = areas; } sessionStorage.clear(); this.flush_ = (this.cfg.oCfg.debug.variable) ? ()=> { const oSys: any = {}; Object.keys(this.hSys).forEach(k=> { const v = this.hSys[k]; oSys['sys:'+ k] = (v instanceof Function) ?v(): v; }); sessionStorage[this.cfg.getNs() +'sys'] = JSON.stringify(oSys); const oSave: any = {}; Object.keys(this.hSave).forEach(k=> { const v = this.hSave[k]; oSave['save:'+ k] = (v instanceof Function) ?v(): v; }); sessionStorage[this.cfg.getNs() +'save'] = JSON.stringify(oSave); const oTmp: any = {}; Object.keys(this.hTmp).forEach(k=> { const v = this.hTmp[k]; oTmp[k] = (v instanceof Function) ?v(): v; }); sessionStorage[this.cfg.getNs() +'tmp'] = JSON.stringify(oTmp); const oMp: any = {}; Object.keys(this.hScope.mp).forEach(k=> { const v = this.hScope.mp[k]; oMp[k] = (v instanceof Function) ?v(): v; }); sessionStorage[this.cfg.getNs() +'mp'] = JSON.stringify(oMp); const oMark: any = {}; Object.keys(this.data.mark).forEach(k=> { const v = this.data.mark[k]; oMark[k] = (v instanceof Function) ?v(): v; }); sessionStorage[this.cfg.getNs() +'mark'] = JSON.stringify(oMark); const oKidoku: any = {}; Object.keys(this.data.kidoku).forEach(k=> { const v = this.data.kidoku[k]; oKidoku[k] = (v instanceof Function) ?v(): v; }); sessionStorage[this.cfg.getNs() +'kidoku'] = JSON.stringify(oKidoku); sys.flush(); } : ()=> sys.flush(); }); } private flush_ = ()=> {}; flush() {this.flush_();} // 先にこのメソッドへの参照を配ってしまうので、中身を入れ替える setDoRecProc(doRecProc: (doRec: boolean)=> void) { this.doRecProc = doRecProc; } private doRecProc = (_doRec: boolean)=> {}; defTmp(name: string, fnc: typeProcVal): void {this.hTmp[name] = fnc;}; cloneMp(): object {return {...this.hScope.mp}} setMp(mp: object) {this.hScope.mp = mp;} setMark(place: number, mark: IMark) {this.data.mark[place] = mark; this.flush()} readonly getMark = (place: number)=> this.data.mark[place]; cloneSave(): object {return {...this.hScope.save}} mark2save(mark: IMark) { this.hSave = this.hScope.save = {...mark.hSave}; this.$doRecLog = this.hSave['sn.doRecLog'] ?? false; } // 既読系 loadScrWork(fn: string): void { if (! (fn in this.hAreaKidoku)) this.hAreaKidoku[fn] = new Areas; } getAreaKidoku = (fn: string): Areas=> this.hAreaKidoku[fn]; saveKidoku(): void { for (const fn in this.hAreaKidoku) { this.data.kidoku[fn] = {...this.hAreaKidoku[fn].hAreas}; } this.flush(); } // しおり // しおりの複写 private copybookmark(hArg: HArg) { if (! ('from' in hArg)) throw 'fromは必須です'; if (! ('to' in hArg)) throw 'toは必須です'; const from = Number(hArg.from); const to = Number(hArg.to); if (from != to) this.setMark(to, {...this.data.mark[from]}); return false; } // しおりの消去 private erasebookmark(hArg: HArg) { const place = hArg.place; if (! place) throw 'placeは必須です'; delete this.data.mark[place]; this.flush(); return false; } // 変数操作 // 変数代入・演算 private let(hArg: HArg) { if (! hArg.name) throw 'nameは必須です'; let autocast = true; if (hArg.cast) { //switch (trim(hArg.cast)) { switch (hArg.cast) { case 'num': CmnLib.argChk_Num(hArg, 'text', NaN); break; case 'int': hArg.text = String(int(CmnLib.argChk_Num(hArg, 'text', NaN))); break; case 'uint': hArg.text = String(uint(CmnLib.argChk_Num(hArg, 'text', NaN))); break; case 'bool': CmnLib.argChk_Boolean(hArg, 'text', false); break; case 'str': autocast = false; break; default: throw 'cast【'+ hArg.cast +'】は未定義です'; } } this.setVal(hArg.name, hArg.text, autocast); return false; } // 絶対値 private let_abs(hArg: HArg) { const n = CmnLib.argChk_Num(hArg, 'text', 0); //hArg.text = Math.abs(n); hArg.text = String((n < 0) ?-n :n); // JavaScriptのMath.abs()で絶対値を取得しないほうが良い理由 | iwb.jp https://iwb.jp/javascript-math-abs-deprecated/ // 数値以外だとほとんどがNaNを返し、booleanは0や1を返しているため使い方によってはバグの原因になることがある。 this.let(hArg); return false; } // 文字列から一字取りだし private let_char_at(hArg: HArg) { hArg.text = (hArg.text ?? '').charAt(CmnLib.argChk_Num(hArg, 'pos', 0)); this.let(hArg); return false; } // 文字列で検索 private let_index_of(hArg: HArg) { const val = hArg.val; if (! val) throw 'valは必須です'; const start = CmnLib.argChk_Num(hArg, 'start', 0); hArg.text = String((hArg.text ?? '').indexOf(val, start)); this.let(hArg); return false; } // 文字列の長さ private let_length(hArg: HArg) { hArg.text = String((hArg.text ?? '').length); this.let(hArg); return false; } // 正規表現で置換 private let_replace(hArg: HArg) { if (! hArg.reg) throw 'regは必須です'; const flags = hArg.flags; const reg = (! flags) ? new RegExp(hArg.reg) : new RegExp(hArg.reg, flags); hArg.text = String(hArg.text ?? '').replace(reg, String(hArg.val)); this.let(hArg); return false; } // 四捨五入 private let_round(hArg: HArg) { const n = CmnLib.argChk_Num(hArg, 'text', 0); hArg.text = String(Math.round(n)); this.let(hArg); return false; } // 正規表現で検索 private let_search(hArg: HArg) { if (! hArg.reg) throw 'regは必須です'; const flags = hArg.flags; const reg = (! flags) ? new RegExp(hArg.reg) : new RegExp(hArg.reg, flags); hArg.text = String((hArg.text ?? '').search(reg)); this.let(hArg); return false; } // 文字列から抜きだし private let_substr(hArg: HArg) { const i = CmnLib.argChk_Num(hArg, 'pos', 0); hArg.text = (hArg.len != 'all') ? (hArg.text ?? '').substr(i, int(CmnLib.argChk_Num(hArg, 'len', 1))) : (hArg.text ?? '').substr(i); this.let(hArg); return false; } // デバッグ・その他 // システム変数の全消去 private clearsysvar() { const sys = this.hSys = this.hScope['sys'] = this.data.sys = {}; const is_nw = (typeof process !== 'undefined'); if (is_nw) { // // this.setVal_Sub('sys:const.sn.window.x', stage.nativeWindow.x); // // this.setVal_Sub('sys:const.sn.window.y', stage.nativeWindow.y); } else { this.setVal_Nochk('sys', 'const.sn.window.x', 0); this.setVal_Nochk('sys', 'const.sn.window.y', 0); } // 文字表示Waitをかけるか this.setVal_Nochk('sys', 'sn.tagCh.doWait', true); this.setVal_Nochk('sys', 'sn.tagCh.doWait_Kidoku', true); // 【既読】 // 文字表示Wait時間 this.setVal_Nochk('sys', 'sn.tagCh.msecWait', this.cfg.oCfg.init.tagch_msecwait); this.setVal_Nochk('sys', 'sn.tagCh.msecWait_Kidoku', this.cfg.oCfg.init.tagch_msecwait); // 【既読】 // 文字表示Wait中スキップのモード this.setVal_Nochk('sys', 'sn.tagCh.canskip', true); // スキップのモード this.setVal_Nochk('sys', 'sn.skip.mode', 's'); // l, p, s // 自動読みすすみモード時の改ページ時のウェイト // // runFirst_sys_an_auto_msecPageWait('sn.auto.msecPageWait', ''); this.setVal_Nochk('sys', 'sn.auto.msecPageWait', CmnLib.argChk_Num(sys, 'sn.auto.msecPageWait', this.cfg.oCfg.init.auto_msecpagewait ?? 3500)); this.setVal_Nochk('sys', 'sn.auto.msecPageWait_Kidoku', CmnLib.argChk_Num(sys, 'sn.auto.msecPageWait', this.cfg.oCfg.init.auto_msecpagewait ?? 3500)); // 自動読みすすみモード時の行クリック待ち時のウェイト this.setVal_Nochk('sys', 'sn.auto.msecLineWait', 500); this.setVal_Nochk('sys', 'sn.auto.msecLineWait_Kidoku', 500); // 【既読】 // SoundMixer.soundTransform = new SoundTransform( // (sys['flash.media.SoundMixer.soundTransform.volume'] = 1) // ); this.setVal_Nochk('sys', 'const.sn.sound.BGM.volume', 1); this.setVal_Nochk('sys', 'const.sn.sound.SE.volume', 1); this.setVal_Nochk('sys', 'const.sn.sound.SYS.volume', 1); for (const fn in this.data.kidoku) this.data.kidoku[fn].hAreas = {}; this.setVal_Nochk('sys', 'TextLayer.Back.Alpha', 0.5); this.hScope['mark'] = this.data.mark = {}; this.setVal_Nochk('sys', 'const.sn.save.place', 1); this.flush(); return false; } // ゲーム変数の全消去 private clearvar() { const mesLayer = this.hSave['const.sn.mesLayer'] ?? ''; const doRecLog = this.hSave['sn.doRecLog'] ?? false; const sLog = this.hSave['const.sn.sLog'] ?? ''; this.hSave = this.hScope.save = {}; this.setVal_Nochk('save', 'const.sn.mesLayer', mesLayer); this.setVal_Nochk('save', 'sn.doRecLog', doRecLog); this.setVal_Nochk('save', 'const.sn.sLog', sLog); return false; } private readonly setVal = (arg_name: string, val: any, autocast = true)=> { if (! arg_name) throw '[変数に値セット] nameは必須です'; if (val == null) throw '[変数に値セット] textは必須です(空文字はOK)'; const o = PropParser.getValName(arg_name); if (o == undefined) throw '[変数参照] name('+ arg_name +')が変数名として異常です'; const hScope = this.hScope[o.scope]; if (! hScope) throw '[変数に値セット] scopeが異常【'+ o.scope +'】です'; const nm = o['name']; if (nm.slice(0, 6) == 'const.' && (nm in hScope)) { throw '[変数に値セット] 変数【'+ nm +'】は書き換え不可です'; } this.setVal_Nochk(o.scope, nm, val, autocast); } setVal_Nochk(scope: string, nm: string, val: any, autocast = false) { const hScope = this.hScope[scope]; if (autocast) val = this.castAuto(val); hScope[nm] = val; const trg = this.hValTrg[scope +':'+ nm]; if (trg != null) trg(nm, val); // if (scope == 'sys') this.flush() // 厳密にはここですべきだが、パフォーマンスに問題があるので // クリック待ちを期待できるwait、waitclick、s、l、pタグで // saveKidoku()をコール。(中で保存しているのでついでに) //console.log(`\tlet s[${scope}] n[${nm}]='${val}' trg[${(trg != null)}]`); } readonly getVal = (arg_name: string, def?: number | string)=> { if (! arg_name) throw '[変数参照] nameは必須です'; const o = PropParser.getValName(arg_name); if (o == undefined) throw '[変数参照] name('+ arg_name +')が変数名として異常です'; const hScope = this.hScope[o['scope']]; if (! hScope) throw '[変数参照] scopeが異常【'+ o['scope'] +'】です'; const name = o['name']; let val = hScope[name]; if (! (name in hScope)) { val = def; const aNm = name.split('.'); const len = aNm.length; let nm = ''; for (let i=0; i<len; ++i) { nm += '.'+ aNm[i]; const bn = nm.slice(1); if (! (bn in hScope)) continue; val = JSON.parse(hScope[bn]); while (++i < len) { if (! (aNm[i] in val)) {val = def; break;} val = val[aNm[i]]; } if (val instanceof Object) val = JSON.stringify(val); break; } } if (val instanceof Function) val = (val as Function)(); //console.log('\tget ['+ arg_name +'] -> s['+ o['scope'] +'] a['+ o['at'] +'] n['+ name +'] ret['+ val +']('+ typeof val +')'); if (o['at'] == '@str') return val; return this.castAuto(val); } private castAuto(val: Object): any { const s_val = val as string; if (s_val == 'true') return true; if (s_val == 'false') return false; if (s_val == 'null') return null; if (s_val == 'undefined') return undefined; this.REG_NUMERICLITERAL.lastIndex = 0; if (this.REG_NUMERICLITERAL.test(s_val)) return parseFloat(s_val); return val; } private REG_NUMERICLITERAL :RegExp = /^-?[\d\.]+$/; // 変数のダンプ private readonly dump_val = ()=> { const val: any = {tmp:{}, sys:{}, save:{}, mp:{}}; for (let scope in val) { const hVal = this.hScope[scope]; const hRet = val[scope]; for (let key in hVal) { const v = hVal[key]; if (Object.prototype.toString.call(v) == '[object Function]') { hRet[key] = v(); } else hRet[key] = v; } } console.info('🥟 [dump_val]', val); return false; } private $doRecLog = false; doRecLog() {return this.$doRecLog;} private hValTrg : {[name: string]: ISetVal} = { // sys 'sys:sn.tagCh.doWait' : name=> this.runFirst_Bool_hSysVal_true(name), 'sys:sn.tagCh.doWait_Kidoku' : name=> this.runFirst_Bool_hSysVal_true(name), 'sys:sn.tagCh.msecWait' : name=> this.runFirst_sys_an_tagCh_msecWait(name), 'sys:sn.tagCh.msecWait_Kidoku' : name=> this.runFirst_sys_an_tagCh_msecWait_Kidoku(name), 'sys:sn.tagCh.canskip' : name=> this.runFirst_Bool_hSysVal_true(name), 'sys:sn.auto.msecPageWait' : name=> this.runFirst_sys_an_auto_msecPageWait(name), 'sys:sn.auto.msecPageWait_Kidoku' : name=> this.runFirst_sys_an_auto_msecPageWait(name), 'sys:sn.auto.msecLineWait' : name=> this.runFirst_sys_an_auto_msecLineWait(name), 'sys:sn.auto.msecLineWait_Kidoku' : name=> this.runFirst_sys_an_auto_msecLineWait(name), // save 'save:sn.doRecLog' : name=> { this.doRecProc( this.$doRecLog = this.runFirst_Bool_hSaveVal_true(name) ); }, 'save:sn.userFnTail' : (_name, val)=> this.cfg.userFnTail = val, // tmp 'tmp:sn.tagL.enabled' : name=> this.runFirst_Bool_hTmp_true(name), 'tmp:sn.skip.all' : name=> this.runFirst_Bool_hTmp_false(name), 'tmp:sn.skip.enabled' : name=> this.runFirst_Bool_hTmp_false(name), 'tmp:sn.auto.enabled' : name=> this.runFirst_Bool_hTmp_false(name), 'tmp:flash.desktop.NativeApplication.nativeApplication.systemIdleMode' : ( ()=> { // NativeApplication.nativeApplication.systemIdleMode = val; } ), 'tmp:sn.chkFontMode' : ()=> { if (this.hTmp['const.sn.onLauncher']) return; if (! this.hTmp['const.sn.isDebugger']) return; // Hyphenation.chkFontMode = CmnLib.argChk_Boolean(this.hTmp, name, true) } }; defValTrg(name: string, fnc: ISetVal) {this.hValTrg[name] = fnc;} private runFirst_Bool_hSysVal_true(name: string): void { CmnLib.argChk_Boolean(this.hSys, name, true); } private runFirst_sys_an_tagCh_msecWait(name: string): void { CmnLib.argChk_Num(this.hSys, name, 10); if (this.hSys['sn.tagCh.doWait']) { // LayerMng.msecChWait = this.hSysVal[name]; } } private runFirst_sys_an_tagCh_msecWait_Kidoku(name: string): void { CmnLib.argChk_Num(this.hSys, name, (this.cfg.oCfg.init.tagch_msecwait == undefined) ? 10 : this.cfg.oCfg.init.tagch_msecwait ); if (this.hSys['sn.tagCh.doWait_Kidoku']) { // LayerMng.msecChWait = this.hSysVal[name]; } } private runFirst_sys_an_auto_msecPageWait(name: string): void { CmnLib.argChk_Num(this.hSys, name, (this.cfg.oCfg.init.auto_msecpagewait == undefined) ? 3500 : this.cfg.oCfg.init.auto_msecpagewait ); } private runFirst_sys_an_auto_msecLineWait(name: string): void { CmnLib.argChk_Num(this.hSys, name, 500); } private runFirst_Bool_hSaveVal_true(name: string) { return CmnLib.argChk_Boolean(this.hSave, name, true); } private runFirst_Bool_hTmp_true(name: string): void { CmnLib.argChk_Boolean(this.hTmp, name, true); } private runFirst_Bool_hTmp_false(name: string): void { CmnLib.argChk_Boolean(this.hTmp, name, false); } };