diff --git a/e_lib/download2.js b/e_lib/download2.js new file mode 100644 index 00000000..bc51d8f0 --- /dev/null +++ b/e_lib/download2.js @@ -0,0 +1,132 @@ +//download.js v3.0, by dandavis; 2008-2014. [CCBY2] see http://danml.com/download.html for tests/usage +// v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime +// v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for larger+faster saves than dataURLs +// v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support + +// data can be a string, Blob, File, or dataURL + + + + +function download(data, strFileName, strMimeType) { + + var self = window, // this script is only for browsers anyway... + u = "application/octet-stream", // this default mime also triggers iframe downloads + m = strMimeType || u, + x = data, + D = document, + a = D.createElement("a"), + z = function(a){return String(a);}, + + + B = self.Blob || self.MozBlob || self.WebKitBlob || z, + BB = self.MSBlobBuilder || self.WebKitBlobBuilder || self.BlobBuilder, + fn = strFileName || "download", + blob, + b, + ua, + fr; + + //if(typeof B.bind === 'function' ){ B=B.bind(self); } + + if(String(this)==="true"){ //reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback + x=[x, m]; + m=x[0]; + x=x[1]; + } + + + + //go ahead and download dataURLs right away + if(String(x).match(/^data\:[\w+\-]+\/[\w+\-]+[,;]/)){ + return navigator.msSaveBlob ? // IE10 can't do a[download], only Blobs: + navigator.msSaveBlob(d2b(x), fn) : + saver(x) ; // everyone else can save dataURLs un-processed + }//end if dataURL passed? + + try{ + + blob = x instanceof B ? + x : + new B([x], {type: m}) ; + }catch(y){ + if(BB){ + b = new BB(); + b.append([x]); + blob = b.getBlob(m); // the blob + } + + } + + + + function d2b(u) { + var p= u.split(/[:;,]/), + t= p[1], + dec= p[2] == "base64" ? atob : decodeURIComponent, + bin= dec(p.pop()), + mx= bin.length, + i= 0, + uia= new Uint8Array(mx); + + for(i;i>>0,s=0;sDe(e)?(r=e+1,a=o-De(e)):(r=e,a=o),{year:r,dayOfYear:a}}function Ie(e,t,n){var s,i,r=Ve(e.year(),t,n),a=Math.floor((e.dayOfYear()-r-1)/7)+1;return a<1?s=a+Ae(i=e.year()-1,t,n):a>Ae(e.year(),t,n)?(s=a-Ae(e.year(),t,n),i=e.year()+1):(i=e.year(),s=a),{week:s,year:i}}function Ae(e,t,n){var s=Ve(e,t,n),i=Ve(e+1,t,n);return(De(e)-s+i)/7}I("w",["ww",2],"wo","week"),I("W",["WW",2],"Wo","isoWeek"),H("week","w"),H("isoWeek","W"),L("week",5),L("isoWeek",5),ue("w",B),ue("ww",B,z),ue("W",B),ue("WW",B,z),fe(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=k(e)});I("d",0,"do","day"),I("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),I("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),I("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),I("e",0,0,"weekday"),I("E",0,0,"isoWeekday"),H("day","d"),H("weekday","e"),H("isoWeekday","E"),L("day",11),L("weekday",11),L("isoWeekday",11),ue("d",B),ue("e",B),ue("E",B),ue("dd",function(e,t){return t.weekdaysMinRegex(e)}),ue("ddd",function(e,t){return t.weekdaysShortRegex(e)}),ue("dddd",function(e,t){return t.weekdaysRegex(e)}),fe(["dd","ddd","dddd"],function(e,t,n,s){var i=n._locale.weekdaysParse(e,s,n._strict);null!=i?t.d=i:g(n).invalidWeekday=e}),fe(["d","e","E"],function(e,t,n,s){t[s]=k(e)});var je="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_");var Ze="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_");var ze="Su_Mo_Tu_We_Th_Fr_Sa".split("_");var $e=ae;var qe=ae;var Je=ae;function Be(){function e(e,t){return t.length-e.length}var t,n,s,i,r,a=[],o=[],u=[],l=[];for(t=0;t<7;t++)n=y([2e3,1]).day(t),s=this.weekdaysMin(n,""),i=this.weekdaysShort(n,""),r=this.weekdays(n,""),a.push(s),o.push(i),u.push(r),l.push(s),l.push(i),l.push(r);for(a.sort(e),o.sort(e),u.sort(e),l.sort(e),t=0;t<7;t++)o[t]=de(o[t]),u[t]=de(u[t]),l[t]=de(l[t]);this._weekdaysRegex=new RegExp("^("+l.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+a.join("|")+")","i")}function Qe(){return this.hours()%12||12}function Xe(e,t){I(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function Ke(e,t){return t._meridiemParse}I("H",["HH",2],0,"hour"),I("h",["hh",2],0,Qe),I("k",["kk",2],0,function(){return this.hours()||24}),I("hmm",0,0,function(){return""+Qe.apply(this)+U(this.minutes(),2)}),I("hmmss",0,0,function(){return""+Qe.apply(this)+U(this.minutes(),2)+U(this.seconds(),2)}),I("Hmm",0,0,function(){return""+this.hours()+U(this.minutes(),2)}),I("Hmmss",0,0,function(){return""+this.hours()+U(this.minutes(),2)+U(this.seconds(),2)}),Xe("a",!0),Xe("A",!1),H("hour","h"),L("hour",13),ue("a",Ke),ue("A",Ke),ue("H",B),ue("h",B),ue("k",B),ue("HH",B,z),ue("hh",B,z),ue("kk",B,z),ue("hmm",Q),ue("hmmss",X),ue("Hmm",Q),ue("Hmmss",X),ce(["H","HH"],ge),ce(["k","kk"],function(e,t,n){var s=k(e);t[ge]=24===s?0:s}),ce(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),ce(["h","hh"],function(e,t,n){t[ge]=k(e),g(n).bigHour=!0}),ce("hmm",function(e,t,n){var s=e.length-2;t[ge]=k(e.substr(0,s)),t[pe]=k(e.substr(s)),g(n).bigHour=!0}),ce("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[ge]=k(e.substr(0,s)),t[pe]=k(e.substr(s,2)),t[ve]=k(e.substr(i)),g(n).bigHour=!0}),ce("Hmm",function(e,t,n){var s=e.length-2;t[ge]=k(e.substr(0,s)),t[pe]=k(e.substr(s))}),ce("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[ge]=k(e.substr(0,s)),t[pe]=k(e.substr(s,2)),t[ve]=k(e.substr(i))});var et,tt=Te("Hours",!0),nt={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:He,monthsShort:Re,week:{dow:0,doy:6},weekdays:je,weekdaysMin:ze,weekdaysShort:Ze,meridiemParse:/[ap]\.?m?\.?/i},st={},it={};function rt(e){return e?e.toLowerCase().replace("_","-"):e}function at(e){var t=null;if(!st[e]&&"undefined"!=typeof module&&module&&module.exports)try{t=et._abbr,require("./locale/"+e),ot(t)}catch(e){}return st[e]}function ot(e,t){var n;return e&&((n=l(t)?lt(e):ut(e,t))?et=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),et._abbr}function ut(e,t){if(null!==t){var n,s=nt;if(t.abbr=e,null!=st[e])T("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=st[e]._config;else if(null!=t.parentLocale)if(null!=st[t.parentLocale])s=st[t.parentLocale]._config;else{if(null==(n=at(t.parentLocale)))return it[t.parentLocale]||(it[t.parentLocale]=[]),it[t.parentLocale].push({name:e,config:t}),null;s=n._config}return st[e]=new P(b(s,t)),it[e]&&it[e].forEach(function(e){ut(e.name,e.config)}),ot(e),st[e]}return delete st[e],null}function lt(e){var t;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return et;if(!o(e)){if(t=at(e))return t;e=[e]}return function(e){for(var t,n,s,i,r=0;r=t&&a(i,n,!0)>=t-1)break;t--}r++}return et}(e)}function dt(e){var t,n=e._a;return n&&-2===g(e).overflow&&(t=n[_e]<0||11Pe(n[me],n[_e])?ye:n[ge]<0||24Ae(n,r,a)?g(e)._overflowWeeks=!0:null!=u?g(e)._overflowWeekday=!0:(o=Ee(n,s,i,r,a),e._a[me]=o.year,e._dayOfYear=o.dayOfYear)}(e),null!=e._dayOfYear&&(r=ht(e._a[me],s[me]),(e._dayOfYear>De(r)||0===e._dayOfYear)&&(g(e)._overflowDayOfYear=!0),n=Ge(r,0,e._dayOfYear),e._a[_e]=n.getUTCMonth(),e._a[ye]=n.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=a[t]=s[t];for(;t<7;t++)e._a[t]=a[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[ge]&&0===e._a[pe]&&0===e._a[ve]&&0===e._a[we]&&(e._nextDay=!0,e._a[ge]=0),e._d=(e._useUTC?Ge:function(e,t,n,s,i,r,a){var o=new Date(e,t,n,s,i,r,a);return e<100&&0<=e&&isFinite(o.getFullYear())&&o.setFullYear(e),o}).apply(null,a),i=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[ge]=24),e._w&&void 0!==e._w.d&&e._w.d!==i&&(g(e).weekdayMismatch=!0)}}var ft=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,mt=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,_t=/Z|[+-]\d\d(?::?\d\d)?/,yt=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],gt=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],pt=/^\/?Date\((\-?\d+)/i;function vt(e){var t,n,s,i,r,a,o=e._i,u=ft.exec(o)||mt.exec(o);if(u){for(g(e).iso=!0,t=0,n=yt.length;tn.valueOf():n.valueOf()this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},ln.isLocal=function(){return!!this.isValid()&&!this._isUTC},ln.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},ln.isUtc=Vt,ln.isUTC=Vt,ln.zoneAbbr=function(){return this._isUTC?"UTC":""},ln.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},ln.dates=n("dates accessor is deprecated. Use date instead.",nn),ln.months=n("months accessor is deprecated. Use month instead",Fe),ln.years=n("years accessor is deprecated. Use year instead",Oe),ln.zone=n("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?("string"!=typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()}),ln.isDSTShifted=n("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!l(this._isDSTShifted))return this._isDSTShifted;var e={};if(w(e,this),(e=Yt(e))._a){var t=e._isUTC?y(e._a):Tt(e._a);this._isDSTShifted=this.isValid()&&0 "; -$citylevel = []; -$citylevel[1] = "수"; -$citylevel[2] = "진"; -$citylevel[3] = "관"; -$citylevel[4] = "이"; -$citylevel[5] = "소"; -$citylevel[6] = "중"; -$citylevel[7] = "대"; -$citylevel[8] = "특"; +$citylevel = getCityLevelList(); $query = "select city,name,gen1,gen2,gen3,level,region,gen1set,gen2set,gen3set from city where nation='{$nation['nation']}' order by region,level desc,binary(name)"; // 도시 이름 목록 $cityresult = MYDB_query($query, $connect) or Error(__LINE__.MYDB_error($connect),""); diff --git a/hwe/battle_simulator.php b/hwe/battle_simulator.php new file mode 100644 index 00000000..bfe34b3c --- /dev/null +++ b/hwe/battle_simulator.php @@ -0,0 +1,473 @@ +setReadOnly(); +$db = DB::db(); +$gameStor = KVStorage::getStorage($db, 'game_env'); + +$startYear = $gameStor->getValue('startyear'); + +?> + + + +<?=UniqueConst::$serverName?>: 전투 시뮬레이터 + + + + + + + + + + + + + + + + +
+
+
+ 전역 설정 +
+
+
+
+
+ +
+ 년 시작 +
+ +
+ +
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+ 출병국 설정 +
+
+
+
+ 국가 성향 +
+ +
+ 기술 +
+ +
+ 등급 +
+
+
+
+ 국가 규모 +
+ +
+ 도시 규모 +
+ +
+ 수도 +
+
+ + +
+ +
+
+ +
+
+
+
+
+
출병자 설정
+ +
+
+
+
+
+
+ 수비국 설정 +
+
+
+
+ 국가 성향 +
+ +
+ 기술 +
+ +
+ 등급 +
+
+
+
+ 국가 규모 +
+ +
+ 도시 규모 +
+ +
+ 수도 +
+
+ + +
+
+
+
+ 수비 +
+ +
+ 성벽 +
+ +
+
+
+
+
+
수비자 설정
+ +
+
+
+
+
수비자 설정
+ +
+
+
+
+ 이름 +
+ +
+ 직위 +
+ +
+ Level +
+ + +
+
+
+ 통솔 +
+ +
+ 무력 +
+ +
+ 지력 +
+ +
+
+
+ 명마 +
+ +
+ 무기 +
+ +
+ 서적 +
+ +
+
+
+ 부상 +
+ +
+ %(건강) +
+
+ 군량 +
+ +
+ 도구 +
+ +
+
+
+ 병종 +
+ +
+ 병사 +
+ +
+ 성격 +
+ +
+
+
+ 훈련 +
+ +
+ 사기 +
+ +
+ 전특 +
+ +
+
+
+ 보병숙련 +
+ +
+ 궁병숙련 +
+ +
+ 기병숙련 +
+ +
+
+
+ 귀병숙련 +
+ +
+ 차병숙련 +
+ +
+ 수비여부 +
+ +
+
+
+ + +
+
+
+
+ 전투 요약 +
+ + + + + + + + + + + + +
전투 일시
전투 횟수
전투 페이즈
준 피해
받은 피해
출병자 군량 소모
수비자 군량 소모
공격자 스킬
수비자1 스킬
+
+
+
+
+
+ 마지막 전투 로그 +
+
+ +
+
+
+
+
+
+ 마지막 전투 상세 로그 +
+
+ +
+
+
+
+
+ + \ No newline at end of file diff --git a/hwe/css/battle_simulator.css b/hwe/css/battle_simulator.css new file mode 100644 index 00000000..a7539a41 --- /dev/null +++ b/hwe/css/battle_simulator.css @@ -0,0 +1,17 @@ +#container{ + width:1024px; + margin:auto; +} + +.defender_add_form .general_detail{ + display:none; +} + + +.form_sample{ + display:none; +} + +input[type=number]{ + text-align:right; +} \ No newline at end of file diff --git a/hwe/func_converter.php b/hwe/func_converter.php index 563d0061..d6ef6935 100644 --- a/hwe/func_converter.php +++ b/hwe/func_converter.php @@ -8,43 +8,43 @@ namespace sammo; * Side effect 없이 값의 변환만을 수행하는 함수들의 모음. */ - function NationCharCall($call) { - switch($call) { - case '명가': $type =13; break; - case '음양가': $type =12; break; - case '종횡가': $type =11; break; - case '불가': $type =10; break; - case '도적': $type = 9; break; - case '오두미도':$type = 8; break; - case '태평도': $type = 7; break; - case '도가': $type = 6; break; - case '묵가': $type = 5; break; - case '덕가': $type = 4; break; - case '병가': $type = 3; break; - case '유가': $type = 2; break; - case '법가': $type = 1; break; - default: $type = 0; break; + static $invTable = []; + if(!$invTable){ + foreach(getNationTypeList() as $typeID => [$name, $pros, $cons]){ + $invTable[$name] = $typeID; + } } - return $type; + return $invTable[$call]??0; +} + +function getCharacterList(){ + $infoText = [ + 9=>['안전', '사기 -5, 징·모병 비용 -20%'], + 8=>['유지', '훈련 -5, 징·모병 비용 -20%'], + 7=>['재간', '명성 -10%, 징·모병 비용 -20%'], + 6=>['출세', '명성 +10%, 징·모병 비용 +20%'], + 5=>['할거', '명성 -10%, 훈련 +5'], + 4=>['정복', '명성 -10%, 사기 +5'], + 3=>['패권', '훈련 +5, 징·모병 비용 +20%'], + 2=>['의협', '사기 +5, 징·모병 비용 +20%'], + 1=>['대의', '명성 +10%, 훈련 -5'], + 0=>['왕좌', '명성 +10%, 사기 -5'], + 10=>['은둔', '명성 -10%, 계급 -10%, 사기 -5, 훈련 -5, 단련 성공률 +10%'], + ]; + return $infoText; } function CharCall($call) { - switch($call) { - case '은둔': $type =10; break; - case '안전'; $type = 9; break; - case '유지'; $type = 8; break; - case '재간'; $type = 7; break; - case '출세'; $type = 6; break; - case '할거'; $type = 5; break; - case '정복'; $type = 4; break; - case '패권'; $type = 3; break; - case '의협'; $type = 2; break; - default: - case '대의'; $type = 1; break; - case '왕좌'; $type = 0; break; + static $invTable = []; + if(\key_exists($call, $invTable)){ + return $invTable[$call]; } - return $type; + + foreach(getCharacterList() as $id => [$name, $info]){ + $invTable[$name] = $id; + } + return $invTable[$call]; } function SpecCall($call) { @@ -120,42 +120,14 @@ function getNationLevel($level) { } function getGenChar($type) { - switch($type) { - case 10: $call = '은둔'; break; - case 9: $call = '안전'; break; - case 8: $call = '유지'; break; - case 7: $call = '재간'; break; - case 6: $call = '출세'; break; - case 5: $call = '할거'; break; - case 4: $call = '정복'; break; - case 3: $call = '패권'; break; - case 2: $call = '의협'; break; - case 1: $call = '대의'; break; - case 0: $call = '왕좌'; break; - } - return $call; + return getCharacterList()[$type][0]; } function getCharInfo(?int $type):?string { if($type === null){ return null; } - - $infoText = [ - 10=>['은둔', '명성 -10%, 계급 -10%, 사기 -5, 훈련 -5, 단련 성공률 +10%'], - 9=>['안전', '사기 -5, 징·모병 비용 -20%'], - 8=>['유지', '훈련 -5, 징·모병 비용 -20%'], - 7=>['재간', '명성 -10%, 징·모병 비용 -20%'], - 6=>['출세', '명성 +10%, 징·모병 비용 +20%'], - 5=>['할거', '명성 -10%, 훈련 +5'], - 4=>['정복', '명성 -10%, 사기 +5'], - 3=>['패권', '훈련 +5, 징·모병 비용 +20%'], - 2=>['의협', '사기 +5, 징·모병 비용 +20%'], - 1=>['대의', '명성 +10%, 훈련 -5'], - 0=>['왕좌', '명성 +10%, 사기 -5'], - ]; - - return $infoText[$type][1]??null; + return getCharacterList()[$type][1]??null; } function getGenSpecial($type) { @@ -251,24 +223,20 @@ function getSpecialInfo(?int $type):?string{ return $infoText[$type][1]??null; } -function getNationType($type) { - switch($type) { - case 13: $call = '명 가'; break; - case 12: $call = '음 양 가'; break; - case 11: $call = '종 횡 가'; break; - case 10: $call = '불 가'; break; - case 9: $call = '도 적'; break; - case 8: $call = '오 두 미 도'; break; - case 7: $call = '태 평 도'; break; - case 6: $call = '도 가'; break; - case 5: $call = '묵 가'; break; - case 4: $call = '덕 가'; break; - case 3: $call = '병 가'; break; - case 2: $call = '유 가'; break; - case 1: $call = '법 가'; break; - case 0: $call = '-'; break; +function getNationType(?int $type) { + if($type === null){ + return '-'; } - return $call; + static $cache = []; + if(\key_exists($type, $cache)){ + return $cache[$type]; + } + + $text = getNationTypeList()[$type][0]??'-'; + $text = join(' ', StringUtil::splitString($text)); + $cache[$type] = $text; + + return $text; } @@ -287,24 +255,12 @@ function getConnect($con) { return $conname; } -function getNationType2($type) { - switch($type) { - case 13: $call = '기술↑ 인구↑ 쌀수입↓ 수성↓'; break; - case 12: $call = '내정↑ 인구↑ 기술↓ 전략↓'; break; - case 11: $call = '전략↑ 수성↑ 금수입↓ 내정↓'; break; - case 10: $call = '민심↑ 수성↑ 금수입↓'; break; - case 9: $call = '계략↑ 금수입↓ 치안↓ 민심↓'; break; - case 8: $call = '쌀수입↑ 인구↑ 기술↓ 수성↓ 내정↓'; break; - case 7: $call = '인구↑ 민심↑ 기술↓ 수성↓'; break; - case 6: $call = '인구↑ 기술↓ 치안↓'; break; - case 5: $call = '수성↑ 기술↓'; break; - case 4: $call = '치안↑인구↑ 민심↑ 쌀수입↓ 수성↓'; break; - case 3: $call = '기술↑ 수성↑ 인구↓ 민심↓'; break; - case 2: $call = '내정↑ 민심↑ 쌀수입↓'; break; - case 1: $call = '금수입↑ 치안↑ 인구↓ 민심↓'; break; - case 0: $call = '-'; break; +function getNationType2(?int $type) { + if($type === null){ + return '-'; } - return $call; + [$name, $pros, $cons] = getNationTypeList()[$type]??['-', '', '']; + return "{$pros} {$cons}"; } function getLevel($level, $nlevel=8) { @@ -491,74 +447,73 @@ function getTechCall($tech) : string { return "{$techLevel}등급"; } -function getDexCall($dex) : string { - if($dex < 2500) { $str = 'F-'; } - elseif($dex < 7500) { $str = 'F'; } - elseif($dex < 15000) { $str = 'F+'; } - elseif($dex < 25000) { $str = 'E-'; } - elseif($dex < 37500) { $str = 'E'; } - elseif($dex < 52500) { $str = 'E+'; } - elseif($dex < 70000) { $str = 'D-'; } - elseif($dex < 90000) { $str = 'D'; } - elseif($dex < 112500) { $str = 'D+'; } - elseif($dex < 137500) { $str = 'C-'; } - elseif($dex < 165000) { $str = 'C'; } - elseif($dex < 195000) { $str = 'C+'; } - elseif($dex < 227500) { $str = 'B-'; } - elseif($dex < 262500) { $str = 'B'; } - elseif($dex < 300000) { $str = 'B+'; } - elseif($dex < 340000) { $str = 'A-'; } - elseif($dex < 382500) { $str = 'A'; } - elseif($dex < 427500) { $str = 'A+'; } - elseif($dex < 475000) { $str = 'S-'; } - elseif($dex < 525000) { $str = 'S'; } - elseif($dex < 577500) { $str = 'S+'; } - elseif($dex < 632500) { $str = 'SS-'; } - elseif($dex < 690000) { $str = 'SS'; } - elseif($dex < 750000) { $str = 'SS+'; } - elseif($dex < 812500) { $str = 'SSS-'; } - elseif($dex < 877500) { $str = 'SSS'; } - elseif($dex < 945000) { $str = 'SSS+'; } - elseif($dex < 1015000) { $str = 'Z-'; } - elseif($dex < 1087500) { $str = 'Z'; } - elseif($dex < 1162500) { $str = 'Z+'; } - else { $str = '?'; } - return $str; +function getDexLevelList(): array{ + return [ + [0, 'navy', 'F-'], + [2500, 'navy', 'F'], + [7500, 'navy', 'F+'], + [15000, 'skyblue', 'E-'], + [25000, 'skyblue', 'E'], + [37500, 'skyblue', 'E+'], + [52500, 'seagreen', 'D-'], + [70000, 'seagreen', 'D'], + [90000, 'seagreen', 'D+'], + [112500, 'teal', 'C-'], + [137500, 'teal', 'C'], + [165000, 'teal', 'C+'], + [195000, 'limegreen', 'B-'], + [227500, 'limegreen', 'B'], + [262500, 'limegreen', 'B+'], + [300000, 'gold', 'A-'], + [340000, 'gold', 'A'], + [382500, 'gold', 'A+'], + [427500, 'darkorange', 'S-'], + [475000, 'darkorange', 'S'], + [525000, 'darkorange', 'S+'], + [577500, 'tomato', 'SS-'], + [632500, 'tomato', 'SS'], + [690000, 'tomato', 'SS+'], + [750000, 'red', 'SSS-'], + [812500, 'red', 'SSS'], + [877500, 'red', 'SSS+'], + [945000, 'darkviolet', 'Z-'], + [1015000, 'darkviolet', 'Z'], + [1087500, 'darkviolet', 'Z+'], + [1162500, 'white', '?'] + ]; } -function getDexLevel($dex) : int { - if($dex < 2500) { $lvl = 0; } - elseif($dex < 7500) { $lvl = 1; } - elseif($dex < 15000) { $lvl = 2; } - elseif($dex < 25000) { $lvl = 3; } - elseif($dex < 37500) { $lvl = 4; } - elseif($dex < 52500) { $lvl = 5; } - elseif($dex < 70000) { $lvl = 6; } - elseif($dex < 90000) { $lvl = 7; } - elseif($dex < 112500) { $lvl = 8; } - elseif($dex < 137500) { $lvl = 9; } - elseif($dex < 165000) { $lvl = 10; } - elseif($dex < 195000) { $lvl = 11; } - elseif($dex < 227500) { $lvl = 12; } - elseif($dex < 262500) { $lvl = 13; } - elseif($dex < 300000) { $lvl = 14; } - elseif($dex < 340000) { $lvl = 15; } - elseif($dex < 382500) { $lvl = 16; } - elseif($dex < 427500) { $lvl = 17; } - elseif($dex < 475000) { $lvl = 18; } - elseif($dex < 525000) { $lvl = 19; } - elseif($dex < 577500) { $lvl = 20; } - elseif($dex < 632500) { $lvl = 21; } - elseif($dex < 690000) { $lvl = 22; } - elseif($dex < 750000) { $lvl = 23; } - elseif($dex < 812500) { $lvl = 24; } - elseif($dex < 877500) { $lvl = 25; } - elseif($dex < 945000) { $lvl = 26; } - elseif($dex < 1015000) { $lvl = 27; } - elseif($dex < 1087500) { $lvl = 28; } - elseif($dex < 1162500) { $lvl = 29; } - else { $lvl = 30; } - return $lvl; +function getDexCall(int $dex) : string { + if($dex < 0){ + throw new \InvalidArgumentException(); + } + + $color = null; + $name = null; + foreach(getDexLevelList() as $dexLevel => [$dexKey, $nextColor, $nextName]){ + if($dex < $dexKey){ + break; + } + $color = $nextColor; + $name = $nextName; + } + + return "{$name}"; +} + +function getDexLevel(int $dex) : int { + if($dex < 0){ + throw new \InvalidArgumentException(); + } + + $retVal = null; + foreach(getDexLevelList() as $dexLevel => [$dexKey, $nextColor, $nextName]){ + if($dex < $dexKey){ + break; + } + $retVal = $dexLevel; + } + return $dexLevel; } function getDexLog($dex1, $dex2) { diff --git a/hwe/func_gamerule.php b/hwe/func_gamerule.php index dd173aae..9ec888d2 100644 --- a/hwe/func_gamerule.php +++ b/hwe/func_gamerule.php @@ -5,6 +5,51 @@ namespace sammo; * 게임 룰에 해당하는 함수 모음 */ +function getNationLevelList():array{ + $table = [ + 0 => ['방랑군', 2, 0], + 1 => ['호족', 2, 1], + 2 => ['군벌', 4, 2], + 3 => ['주자사', 4, 5], + 4 => ['주목', 6, 8], + 5 => ['공', 6, 11], + 6 => ['왕', 8, 16], + 7 => ['황제', 8, 21], + ]; + return $table; +} + +function getNationTypeList():array{ + $table = [ + 13=>['명가', '기술↑ 인구↑', '쌀수입↓ 수성↓'], + 12=>['음양가', '내정↑ 인구↑', '기술↓ 전략↓'], + 11=>['종횡가', '전략↑ 수성↑', '금수입↓ 내정↓'], + 10=>['불가', '민심↑ 수성↑', '금수입↓'], + 9=>['도적', '계략↑', '금수입↓ 치안↓ 민심↓'], + 8=>['오두미도', '쌀수입↑ 인구↑', '기술↓ 수성↓ 내정↓'], + 7=>['태평도', '인구↑ 민심↑', '기술↓ 수성↓'], + 6=>['도가', '인구↑', '기술↓ 치안↓'], + 5=>['묵가', '수성↑', '기술↓'], + 4=>['덕가', '치안↑인구↑ 민심↑', '쌀수입↓ 수성↓'], + 3=>['병가', '기술↑ 수성↑', '인구↓ 민심↓'], + 2=>['유가', '내정↑ 민심↑', '쌀수입↓'], + 1=>['법가', '금수입↑ 치안↑', '인구↓ 민심↓'], + ]; + return $table; +} + +function getCityLevelList():array{ + return [ + 1 => '수', + 2 => '진', + 3 => '관', + 4 => '이', + 5 => '소', + 6 => '중', + 7 => '대', + 8 => '특' + ]; +} /** * 게임 내부에 사용하는 유틸리티 함수들을 분리 diff --git a/hwe/func_process_sabotage.php b/hwe/func_process_sabotage.php index 154872fd..90f8cdf3 100644 --- a/hwe/func_process_sabotage.php +++ b/hwe/func_process_sabotage.php @@ -608,7 +608,6 @@ function process_35(&$general) { $destNation = getNationStaticInfo($destNationID); $generalList = $db->query('SELECT `no`,leader,horse,power,weap,intel,book,injury,level,special,special2 FROM general WHERE city=%i and nation=%i', $destCity['city'], $destCity['nation']); - [ $srcGenScore, $srcSpecialScore, diff --git a/hwe/j_simulate_battle.php b/hwe/j_simulate_battle.php new file mode 100644 index 00000000..ce6e1e9c --- /dev/null +++ b/hwe/j_simulate_battle.php @@ -0,0 +1,417 @@ +setReadOnly(); +$userID = Session::getUserID(); + +$query = Util::getReq('query'); +if($query === null){ + Json::die([ + 'result'=>false, + 'reason'=>'입력값이 없습니다.' + ]); +} + +$action = Util::getReq('action'); +if($action === null || !in_array($action, ['reorder', 'battle'])){ + Json::die([ + 'result'=>false, + 'reason'=>'원하는 동작이 지정되지 않았습니다.' + ]); +} + +$query = Json::decode($query); +if($query === null){ + Json::die([ + 'result'=>false, + 'reason'=>'올바르지 않은 JSON입니다.' + ]); +} + + +$defaultCheck = [ + 'required'=>[ + 'attackerGeneral', 'attackerCity', 'attackerNation', + 'defenderGenerals', 'defenderCity', 'defenderNation', + 'year', 'month', 'repeatCnt' + ], + 'integer'=>[ + 'year','month','repeatCnt' + ], + 'between'=>[ + ['month', [1, 12]] + ], + 'in'=>[ + ['repeatCnt', [1, 1000]] + ], + 'min'=>[ + ['year', 0] + ], + 'array'=>[ + 'attackerGeneral', 'attackerCity', 'attackerNation', + 'defenderGenerals', 'defenderCity', 'defenderNation' + ], +]; + +$v = new Validator($query); +$v->rules($defaultCheck); +if(!$v->validate()){ + Json::die([ + 'result'=>false, + 'reason'=>$v->errorStr() + ]); +} + +$year = $query['year']; +$month = $query['month']; +$repeatCnt = $query['repeatCnt']; + +$rawAttacker = $query['attackerGeneral']; +$rawAttacker['turntime'] = date('Y-m-d H:i:s'); +$rawAttackerCity = $query['attackerCity']; +$rawAttackerNation = $query['attackerNation']; + +$defenderList = $query['defenderGenerals']; +$rawDefenderCity = $query['defenderCity']; +$rawDefenderNation = $query['defenderNation']; + + +$generalCheck = [ + 'required'=>[ + 'no', 'name', 'nation', 'turntime', 'personal', 'special2', 'crew', 'crewtype', 'atmos', 'train', + 'intel', 'intel2', 'book', 'power', 'power2', 'weap', 'injury', 'leader', 'leader2', 'horse', 'item', + 'explevel', 'experience', 'dedication', 'level', 'gold', 'rice', 'dex0', 'dex10', 'dex20', 'dex30', 'dex40', + 'warnum', 'killnum', 'deathnum', 'killcrew', 'deathcrew', 'recwar' + ], + 'integer'=>[ + 'no', 'nation', 'personal', 'special2', 'crew', 'crewtype', 'atmos', 'train', + 'intel', 'intel2', 'book', 'power', 'power2', 'weap', 'injury', 'leader', 'leader2', 'horse', 'item', + 'explevel', 'experience', 'dedication', 'level', 'gold', 'rice', 'dex0', 'dex10', 'dex20', 'dex30', 'dex40', + 'warnum', 'killnum', 'deathnum', 'killcrew', 'deathcrew' + ], + 'min'=>[ + ['no', 1], + ['nation', 1], + ['crew', 0], + ['intel', 0], + ['power', 0], + ['leader', 0], + ['experience', 0], + ['gold', 0], + ['rice', 0], + ['dex0', 0], + ['dex10', 0], + ['dex20', 0], + ['dex30', 0], + ['dex40', 0], + ], + 'between'=>[ + ['train', [40, GameConst::$maxTrainByWar]], + ['atmos', [40, GameConst::$maxAtmosByWar]], + ['book', [0, 26]], + ['weap', [0, 26]], + ['horse', [0, 26]], + ['item', [0, 26]], + ['explevel', [0, 300]], + ['injury', [0, 80]], + ['level', [1, 12]] + ], + 'in'=>[ + ['personal', array_keys(getCharacterList())], + ['special2', array_merge(array_keys(SpecialityConst::WAR), [0])], + ['crewtype', array_keys(GameUnitConst::all())], + ] +]; + +$v = new Validator($rawAttacker); +$v->rules($generalCheck); +if(!$v->validate()){ + Json::die([ + 'result'=>false, + 'reason'=>'[출병자]'.$v->errorStr() + ]); +} + +foreach($defenderList as $idx=>$rawDefenderGeneral){ + $v = new Validator($rawDefenderGeneral); + $v->rules($generalCheck); + if(!$v->validate()){ + $idx+=1; + Json::die([ + 'result'=>false, + 'reason'=>"[수비자{$idx}]".$v->errorStr() + ]); + } +} + + +$cityCheck = [ + 'required'=>[ + 'city', 'nation', 'supply', 'name', + 'pop', 'agri', 'comm', 'secu', 'def', 'wall', + 'rate', 'level', + 'pop2', 'agri2', 'comm2', 'secu2', 'def2', 'wall2', + 'dead', 'state', 'gen1', 'gen2', 'gen3', 'conflict', + ], + 'numeric'=>[ + 'pop', 'agri', 'comm', 'secu', 'def', 'wall', 'rate', 'dead' + ], + 'integer'=>[ + 'city', 'nation', 'supply', + 'pop2', 'agri2', 'comm2', 'secu2', 'def2', 'wall2', + 'state', 'gen1', 'gen2', 'gen3' + ], + 'min'=>[ + ['def', 0], + ['wall', 0], + ['rate', 0], + ['pop', 0], + ['comm', 0], + ['secu', 0], + ['city', 1], + ['nation', 0] + ], + 'in'=>[ + ['level', array_keys(getCityLevelList())] + ] +]; + +$v = new Validator($rawAttackerCity); +$v->rules($cityCheck); +if(!$v->validate()){ + Json::die([ + 'result'=>false, + 'reason'=>'[출병도시]'.$v->errorStr() + ]); +} + +$v = new Validator($rawDefenderCity); +$v->rules($cityCheck); +if(!$v->validate()){ + Json::die([ + 'result'=>false, + 'reason'=>'[수비도시]'.$v->errorStr() + ]); +} + +$nationCheck = [ + 'required'=>[ + 'type', 'tech', 'level', 'capital', + 'nation', 'name', 'gold', 'rice', 'totaltech', 'gennum' + ], + 'integer'=>[ + 'type', 'level', 'capital', 'nation', 'gennum', + ], + 'numeric'=>[ + 'tech', 'gold', 'rice', 'totaltech' + ], + 'min'=>[ + ['tech', 0], + ['totaltech', 0], + ['gold', 0], + ['rice', 0], + ['gennum', 1], + ['gen1', 0], + ['gen2', 0], + ['gen3', 0], + ], + 'in'=>[ + ['type', array_keys(getNationTypeList())], + ['level', array_keys(getNationLevelList())] + ] +]; + +$v = new Validator($rawAttackerNation); +$v->rules($nationCheck); +if(!$v->validate()){ + Json::die([ + 'result'=>false, + 'reason'=>'[출병국]'.$v->errorStr() + ]); +} + +$v = new Validator($rawDefenderNation); +$v->rules($nationCheck); +if(!$v->validate()){ + Json::die([ + 'result'=>false, + 'reason'=>'[수비국]'.$v->errorStr() + ]); +} + +if($action == 'reorder'){ + usort($defenderList, function($lhs, $rhs){ + return -(extractBattleOrder($lhs) <=> extractBattleOrder($rhs)); + }); + + $order = []; + foreach($defenderList as $rawDefenderGeneral){ + $order[] = $rawDefenderGeneral['no']; + } + + Json::die([ + 'result'=>true, + 'reason'=>'success', + 'order'=>$order + ]); +} + +usort($defenderList, function($lhs, $rhs){ + return -(extractBattleOrder($lhs) <=> extractBattleOrder($rhs)); +}); + +$db = DB::db(); +$gameStor = KVStorage::getStorage($db, 'game_env'); +$startYear = $gameStor->startyear; +$cityRate = Util::round(($year - $startYear) / 1.5) + 60; + + +function simulateBattle( + $rawAttacker, $rawAttackerCity, $rawAttackerNation, + $defenderList, $rawDefenderCity, $rawDefenderNation, + $startYear, $year, $month, $cityRate +){ + $attacker = new WarUnitGeneral($rawAttacker, $rawAttackerCity, $rawAttackerNation, true, $year, $month); + $city = new WarUnitCity($rawDefenderCity, $rawDefenderNation, $year, $month, $cityRate); + + $iterDefender = new \ArrayIterator($defenderList); + $iterDefender->rewind(); + + $battleResult = []; + + $attackerRice = $rawAttacker['rice']; + $defenderRice = 0; + + $getNextDefender = function(?WarUnit $prevDefender, bool $reqNext) + use ($iterDefender, $rawDefenderCity, $rawDefenderNation, $year, $month, &$battleResult, &$defenderRice) { + if($prevDefender !== null){ + $prevDefender->getLogger()->rollback(); + $battleResult[] = $prevDefender; + if($prevDefender instanceof WarUnitGeneral){ + $defenderRice -= $prevDefender->getVar('rice'); + } + } + + if(!$reqNext){ + return null; + } + + if(!$iterDefender->valid()){ + return null; + } + + $rawGeneral = $iterDefender->current(); + if(extractBattleOrder($rawGeneral) <= 0){ + return null; + } + + $defenderRice += $rawGeneral['rice']; + + $retVal = new WarUnitGeneral($rawGeneral, $rawDefenderCity, $rawDefenderNation, false, $year, $month); + $iterDefender->next(); + return $retVal; + }; + + $conquerCity = processWar_NG($attacker, $getNextDefender, $city, $year - $startYear); + + $rawDefenderCity = $city->getRaw(); + $updateAttackerNation = []; + $updateDefenderNation = []; + + $attackerRice -= $attacker->getVar('rice'); + + if($city->getPhase() > 0){ + $rice = $city->getKilled() / 100 * 0.8; + $rice *= $city->getCrewType()->rice; + $rice *= getTechCost($rawDefenderNation['tech']); + $rice *= $cityRate / 100 - 0.2; + Util::setRound($rice); + + $defenderRice += $rice; + } + + $totalDead = $attacker->getKilled() + $attacker->getDead(); + $attackerCityDead = $totalDead * 0.4; + $defenderCityDead = $totalDead * 0.6; + + return [$attacker, $city, $battleResult, $conquerCity, $attackerRice, $defenderRice]; +} + +$lastWarLog = []; + +$attackerKilled = 0; +$attackerDead = 0; + +$attackerAvgRice = 0; +$defenderAvgRice = 0; + +$avgPhase = 0; +$avgWar = 0; + +$attackerActivatedSkills = []; +$defendersActivatedSkills = []; + +foreach(range(1, $repeatCnt) as $repeatIdx){ + [$attacker, $city, $battleResult, $conquerCity, $attackerRice, $defenderRice] = simulateBattle( + $rawAttacker, $rawAttackerCity, $rawAttackerNation, + $defenderList, $rawDefenderCity, $rawDefenderNation, + $startYear, $year, $month, $cityRate + ); + $lastWarLog = Util::mapWithKey(function($key, $values){ + return ConvertLog(join('
', $values)); + }, $attacker->getLogger()->rollback()); + + $avgPhase += $attacker->getPhase() / $repeatCnt; + + $attackerKilled += $attacker->getKilled() / $repeatCnt; + $attackerDead += $attacker->getDead() / $repeatCnt; + + $attackerAvgRice += $attackerRice / $repeatCnt; + $defenderAvgRice += $defenderRice / $repeatCnt; + + $avgWar += count($battleResult) / $repeatCnt; + + foreach($attacker->getActivatedSkillLog() as $skillName => $skillCnt){ + if(!key_exists($skillName, $attackerActivatedSkills)){ + $attackerActivatedSkills[$skillName] = $skillCnt / $repeatCnt; + } + else{ + $attackerActivatedSkills[$skillName] += $skillCnt / $repeatCnt; + } + } + + foreach($battleResult as $idx=>$defender){ + while($idx >= count($defendersActivatedSkills)){ + $defendersActivatedSkills[] = []; + } + + $activatedSkills = &$defendersActivatedSkills[$idx]; + foreach($defender->getActivatedSkillLog() as $skillName => $skillCnt){ + if(!key_exists($skillName, $activatedSkills)){ + $activatedSkills[$skillName] = $skillCnt / $repeatCnt; + } + else{ + $activatedSkills[$skillName] += $skillCnt / $repeatCnt; + } + } + + } +} + +Json::die([ + 'result'=>true, + 'datetime'=>$rawAttacker['turntime'], + 'reason'=>'success', + 'lastWarLog'=>$lastWarLog, + 'avgWar'=>$avgWar, + 'phase'=>$avgPhase, + 'killed'=>$attackerKilled, + 'dead'=>$attackerDead, + 'attackerRice'=>$attackerAvgRice, + 'defenderRice'=>$defenderAvgRice, + 'attackerSkills'=>$attackerActivatedSkills, + 'defendersSkills'=>$defendersActivatedSkills, +]); diff --git a/hwe/js/battle_simulator.js b/hwe/js/battle_simulator.js new file mode 100644 index 00000000..ac4c0e67 --- /dev/null +++ b/hwe/js/battle_simulator.js @@ -0,0 +1,800 @@ +jQuery(function($){ + + + var $generalForm = $('.form_sample .general_detail'); + var $defenderHeaderForm = $('.form_sample .card-header'); + var $defenderColumn = $('.defender-column'); + + var defenderNoList = {}; + + var $attackerCard = $('.attacker_form'); + + var initBasicEvent = function(){ + + $('.form_injury').change(function(){ + var $this = $(this); + var $general = getGeneralDetail($this); + var $helptext = $general.find('.injury_helptext'); + + var injury = parseInt($this.val()); + //FIXME: PHP 코드와 항상 일치하도록 변경 + var text = '건강'; + var color = 'white'; + if(injury > 60){ + text = '위독'; + color = 'red'; + } + else if(injury > 40){ + text = '심각'; + color = 'magenta'; + } + else if(injury > 20){ + text = '중상'; + color = 'orange'; + } + else if(injury > 0){ + text = '경상'; + color = 'yellow'; + } + $helptext.html(text).css('color', color); + }); + + $('.export_general').click(function(){ + var $btn = $(this); + var $general = getGeneralDetail($btn); + + var values = exportGeneralInfo($general); + console.log(values); + }); + $('.delete-defender').click(function(){ + var $card = getGeneralFrame($(this)); + deleteDefender($card); + }); + $('.copy-defender').click(function(){ + var $card = getGeneralFrame($(this)); + copyDefender($card); + }); + + $('.add-defender').click(function(){ + addDefender(); + }); + + $('.btn-general-load').click(function(){ + var $file = $(this).prev(); + $file[0].click(); + }) + + $('.form_load_general_file').on('change', function(e){ + e.preventDefault(); + var $this = $(this); + var $card = getGeneralFrame($this); + + var files = e.target.files; + + importGeneralInfoByFile(files, $card); + return false; + }); + + $('.btn-general-save').click(function(){ + var $this = $(this); + var $general = getGeneralDetail($this); + var generalData = exportGeneralInfo($general); + + var filename = "general_{0}.json".format(generalData.name); + var saveData = JSON.stringify({ + objType:'general', + data:generalData + }, null, 4); + + download(saveData, filename, 'application/json'); + }); + + $('.btn-battle-load').click(function(){ + var $file = $(this).prev(); + $file[0].click(); + }) + + $('.form_load_battle_file').on('change', function(e){ + e.preventDefault(); + var $this = $(this); + var files = e.target.files; + + importBattleInfoByFile(files); + return false; + }); + + $('.btn-battle-save').click(function(){ + var battleData = exportAllData(); + var filename = "battle_{0}.json".format(moment().format('YYYYMMDD_hhmmss')); + var saveData = JSON.stringify({ + objType:'battle', + data:battleData + }, null, 4); + + download(saveData, filename, 'application/json'); + }) + + var $generals = $('.general_detail'); + $generals.bind('dragover dragleave', function(e) { + e.stopPropagation() + e.preventDefault() + }) + $generals.bind('drop', function(e){ + e.preventDefault(); + var $this = $(this); + var $card = getGeneralFrame($this); + + var files = e.originalEvent.dataTransfer.files; + + importGeneralInfoByFile(files, $card); + return false; + }); + + var $battlePad = $('.dragpad_battle'); + $battlePad.bind('dragover dragleave', function(e) { + e.stopPropagation() + e.preventDefault() + }) + $battlePad.bind('drop', function(e){ + e.preventDefault(); + + var files = e.originalEvent.dataTransfer.files; + + importBattleInfoByFile(files); + return false; + }); + } + + var importGeneralInfo = function($general, data){ + var setVal = function(query, val){ + $general.find(query).val(val).change(); + } + + setVal('.form_general_name', data.name); + + setVal('.form_general_level', data.level); + setVal('.form_exp_level', data.explevel); + setVal('.form_injury', data.injury); + + setVal('.form_leadership', data.leader); + setVal('.form_general_horse', data.horse); + setVal('.form_power', data.power); + setVal('.form_general_weap', data.weap); + setVal('.form_intel', data.intel); + setVal('.form_general_book', data.book); + setVal('.form_general_item', data.item); + + setVal('.form_injury', data.injury); + + setVal('.form_rice', data.rice); + + setVal('.form_general_character', data.personal); + setVal('.form_general_special_war', data.special2); + + setVal('.form_crew', data.crew); + setVal('.form_crewtype', data.crewtype); + setVal('.form_atmos', data.atmos); + setVal('.form_train', data.train); + + setVal('.form_dex0', data.dex0); + setVal('.form_dex10', data.dex10); + setVal('.form_dex20', data.dex20); + setVal('.form_dex30', data.dex30); + setVal('.form_dex40', data.dex40); + setVal('.form_defend_mode', data.mode); + + if(!setGeneralNo($general, data.no)){ + setGeneralNo($general, generateNewGeneralNo()); + } + } + + var exportGeneralInfo = function($general){ + var getInt = function(query){ + return parseInt($general.find(query).val()); + } + + var getVal = function(query){ + return $general.find(query).val(); + } + + return { + no:getGeneralNo($general), + name:getVal('.form_general_name'), + level:getInt('.form_general_level'), + explevel:getInt('.form_exp_level'), + + leader:getInt('.form_leadership'), + horse:getInt('.form_general_horse'), + power:getInt('.form_power'), + weap:getInt('.form_general_weap'), + intel:getInt('.form_intel'), + book:getInt('.form_general_book'), + item:getInt('.form_general_item'), + + injury:getInt('.form_injury'), + + rice:getInt('.form_rice'), + + personal:getInt('.form_general_character'), + special2:getInt('.form_general_special_war'), + + crew:getInt('.form_crew'), + crewtype:getInt('.form_crewtype'), + atmos:getInt('.form_atmos'), + train:getInt('.form_train'), + + dex0:getInt('.form_dex0'), + dex10:getInt('.form_dex10'), + dex20:getInt('.form_dex20'), + dex30:getInt('.form_dex30'), + dex40:getInt('.form_dex40'), + mode:getInt('.form_defend_mode'), + }; + } + + var importBattleInfoByFile = function(files){ + if(files === null){ + alert('파일 에러!'); + return false; + } + + if(files.length < 1){ + alert("파일 에러!"); + return false; + } + + + var file = files[0]; + if(file.size > 1024*1024){ + alert('파일이 너무 큽니다!'); + return false; + } + if(file.type === ''){ + alert('폴더를 업로드할 수 없습니다!'); + return false; + } + + var reader = new FileReader(); + reader.onload = function(){ + var battleData = {}; + try { + battleData = JSON.parse(reader.result); + } catch(e) { + alert('올바르지 않은 파일 형식입니다'); + return false; + } + + if(!('objType' in battleData)){ + alert('파일 형식을 확인할 수 없습니다'); + return false; + } + + if(battleData.objType != 'battle'){ + alert('전투 데이터가 아닙니다'); + return false; + } + + importBattleInfo(battleData.data); + return true; + }; + + try { + + reader.readAsText(file); + } + catch(e) { + alert('파일을 읽는데 실패했습니다.'); + return false; + } + + return true; + } + + var importGeneralInfoByFile = function(files, $card){ + if($card === undefined){ + $card = addDefender(); + } + + if(files === null){ + alert('파일 에러!'); + return false; + } + + if(files.length < 1){ + alert("파일 에러!"); + return false; + } + + + var file = files[0]; + if(file.size > 1024*1024){ + alert('파일이 너무 큽니다!'); + return false; + } + if(file.type === ''){ + alert('폴더를 업로드할 수 없습니다!'); + return false; + } + + var reader = new FileReader(); + reader.onload = function(){ + var generalData = {}; + try { + generalData = JSON.parse(reader.result); + } catch(e) { + alert('올바르지 않은 파일 형식입니다'); + return false; + } + + if(!('objType' in generalData)){ + alert('파일 형식을 확인할 수 없습니다'); + return false; + } + + if(generalData.objType == 'battle'){ + importBattleInfo(generalData.data); + return true; + } + if(generalData.objType != 'general'){ + alert('장수 데이터가 아닙니다'); + return false; + } + + $card.find('.form_load_general_file').val(''); + + importGeneralInfo($card, generalData.data); + return true; + }; + + try { + + reader.readAsText(file); + } + catch(e) { + alert('파일을 읽는데 실패했습니다.'); + return false; + } + + return true; + } + + var extendGeneralInfoForDB = function(generalData){ + + var dbVal = { + nation: (generalData.no)<=1 ? 1 : 2, + city: (generalData.no)<=1 ? 1 : 3, + turntime:'2018-08-26 12:00', + special:0, + leader2:0, + power2:0, + intel2:0, + + gold:10000, + + dedication:0, + warnum:0, + killnum:0, + deathnum:0, + + killcrew:20000, + deathcrew:20000, + recwar:'SUCCESS', + experience:Math.pow(generalData.explevel, 2), + }; + + return $.extend({}, generalData, dbVal); + } + + var getGeneralFrame = function($btn){ + var $card = $btn.closest('.general_form'); + return $card; + } + + var getGeneralDetail = function($btn){ + var $card = getGeneralFrame($btn); + var $general = $card.find('.general_detail'); + return $general; + } + + var getGeneralNo = function($btn){ + return parseInt(getGeneralFrame($btn).data('general_no')); + } + + var setGeneralNo = function($btn, value){ + if(value == 1){ + //1번은 무조건 공격자임 + return false; + } + if(value in defenderNoList){ + return false; + } + var $card = getGeneralFrame($btn); + $card.data('general_no', value); + defenderNoList[value] = $card; + return true; + } + + var generateNewGeneralNo = function(){ + while(true){ + var newGeneralNo = parseInt(Math.random()*(1<<24))+2; + if(newGeneralNo in defenderNoList){ + continue; + } + return newGeneralNo; + } + } + + var deleteGeneralNo = function($btn){ + var $card = getGeneralFrame($btn); + $card.removeData('general_no'); + var generalNo = getGeneralNo($card); + delete defenderNoList[generalNo]; + } + + var addDefender = function($cardAfter){ + var $newCard = $('
'); + + if($cardAfter === undefined){ + $defenderColumn.append($newCard); + } + else{ + $cardAfter.after($newCard); + } + + $newCard.append($defenderHeaderForm.clone(true,true)); + $newCard.append($generalForm.clone(true,true)); + + $newGeneral = getGeneralDetail($newCard); + setGeneralNo($newCard, generateNewGeneralNo()); + + return $newCard; + } + + var deleteDefender = function($card){ + deleteGeneralNo($card); + $card.detach(); + } + + var copyDefender = function($card){ + var $general = getGeneralDetail($card); + + var generalData = exportGeneralInfo($general); + var $newObj = addDefender($card); + importGeneralInfo(getGeneralDetail($newObj), generalData); + } + + var importBattleInfo = function(battleData){ + + $('.form_load_battle_file').val(''); + console.log(battleData); + + var $attackerNation = $('.attacker_nation'); + var $defenderNation = $('.defender_nation'); + + var attackerGeneral = battleData.attackerGeneral; + var attackerCity = battleData.attackerCity; + var attackerNation = battleData.attackerNation; + + var defenderGenerals = battleData.defenderGenerals; + var defenderCity = battleData.defenderCity; + var defenderNation = battleData.defenderNation; + + $('#year').val(battleData.year); + $('#month').val(battleData.month); + $('#repeat_cnt').val(battleData.repeatCnt); + + $('.delete-defender').click(); + + $attackerNation.find('.form_nation_type').val(attackerNation.type); + $attackerNation.find('.form_tech').val(Math.floor(attackerNation.tech/1000)); + $attackerNation.find('.form_nation_level').val(attackerNation.level); + if(attackerNation.capital == 1){ + $attackerNation.find('.form_is_capital:first').click(); + } + else{ + $attackerNation.find('.form_is_capital:last').click(); + } + $attackerNation.find('.form_city_level').val(attackerCity.level); + + importGeneralInfo($('.attacker_form'), attackerGeneral); + + $defenderNation.find('.form_nation_type').val(defenderNation.type); + $defenderNation.find('.form_tech').val(Math.floor(defenderNation.tech/1000)); + $defenderNation.find('.form_nation_level').val(defenderNation.level); + if(defenderNation.capital == 1){ + $defenderNation.find('.form_is_capital:first').click(); + } + else{ + $defenderNation.find('.form_is_capital:last').click(); + } + $defenderNation.find('.form_city_level').val(defenderCity.level); + $('#city_def').val(defenderCity.def); + $('#city_wall').val(defenderCity.wall); + + $.each(defenderGenerals, function(idx, defenderGeneral){ + var $card = addDefender(); + importGeneralInfo($card, defenderGeneral); + }); + } + + var exportAllData = function(){ + var $attackerNation = $('.attacker_nation'); + var $defenderNation = $('.defender_nation'); + + var attackerGeneral = exportGeneralInfo($('.attacker_form')); + + var attackerCity = { + level:parseInt($attackerNation.find('.form_city_level').val()), + }; + + var attackerNation = { + type:parseInt($attackerNation.find('.form_nation_type').val()), + tech:parseInt($attackerNation.find('.form_tech').val()) * 1000, + level:parseInt($attackerNation.find('.form_nation_level').val()), + capital:$attackerNation.find('.form_is_capital:checked').val()=='1'?1:2, + } + + var defenderGenerals = $('.defender_form').map(function(){ + return exportGeneralInfo($(this)); + }).toArray(); + + var defenderCity = { + def: parseInt($('#city_def').val()), + wall: parseInt($('#city_wall').val()), + level:parseInt($defenderNation.find('.form_city_level').val()), + }; + + var defenderNation = { + type:parseInt($defenderNation.find('.form_nation_type').val()), + tech:parseInt($defenderNation.find('.form_tech').val()) * 1000, + level:parseInt($defenderNation.find('.form_nation_level').val()), + capital:$defenderNation.find('.form_is_capital:checked').val()=='1'?3:4, + } + + var year = parseInt($('#year').val()); + var month = parseInt($('#month').val()); + var repeatCnt = parseInt($('#repeat_cnt').val()); + + return { + attackerGeneral:attackerGeneral, + attackerCity:attackerCity, + attackerNation:attackerNation, + + defenderGenerals:defenderGenerals, + defenderCity:defenderCity, + defenderNation:defenderNation, + + year:year, + month:month, + repeatCnt:repeatCnt, + }; + } + + var extendAllDataForDB = function(allData){ + var defaultNation = { + nation:0, + name:'재야', + capital:0, + level:0, + gold:0, + rice:2000, + type:0, + tech:0, + totaltech:0, + gennum:200, + }; + + var defaultCity = { + nation:0, + supply:1, + name:'도시', + + pop:500000, + agri:10000, + comm:10000, + secu:10000, + def:1000, + wall:1000, + + rate:100, + + pop2:600000, + agri2:12000, + comm2:12000, + secu2:10000, + def2:12000, + wall2:12000, + + dead:0, + + state:0, + gen1:0, + gen2:0, + gen3:0, + + conflict:'{}', + }; + + var attackerNation = $.extend({}, defaultNation, allData.attackerNation); + attackerNation.nation = 1; + attackerNation.name = '출병국'; + attackerNation.totaltech = attackerNation.tech * attackerNation.gennum; + + var attackerCity = $.extend({}, defaultCity, allData.attackerCity); + attackerCity.nation = 1; + attackerCity.city = 1; + + var attackerGeneral = extendGeneralInfoForDB(allData.attackerGeneral); + if(attackerGeneral.level == 4){ + attackerCity.gen1 = attackerGeneral.no; + } + if(attackerGeneral.level == 3){ + attackerCity.gen2 = attackerGeneral.no; + } + if(attackerGeneral.level == 2){ + attackerCity.gen3 = attackerGeneral.no; + } + + var defenderNation = $.extend({}, defaultNation, allData.defenderNation); + defenderNation.nation = 2; + defenderNation.name = '수비국'; + defenderNation.totaltech = defenderNation.tech * defenderNation.gennum; + + var defenderCity = $.extend({}, defaultCity, allData.defenderCity); + defenderCity.nation = 2; + defenderCity.city = 3; + defenderCity.wall2 = defenderCity.wall/5*6; + defenderCity.def2 = defenderCity.def/5*6; + + var defenderGenerals = []; + $.each(allData.defenderGenerals, function(){ + var defenderGeneral = extendGeneralInfoForDB(this); + if(defenderGeneral.level == 4){ + defenderCity.gen1 = defenderGeneral.no; + } + if(defenderGeneral.level == 3){ + defenderCity.gen2 = defenderGeneral.no; + } + if(defenderGeneral.level == 2){ + defenderCity.gen3 = defenderGeneral.no; + } + + defenderGenerals.push(defenderGeneral); + }); + + + return $.extend({}, allData, { + attackerGeneral:attackerGeneral, + attackerCity:attackerCity, + attackerNation:attackerNation, + + defenderGenerals:defenderGenerals, + defenderCity:defenderCity, + defenderNation:defenderNation, + }); + } + + var parseSkillCount = function(skills){ + var result = []; + $.each(skills, function(key, value){ + result.push("{0}({1}회)".format(key, toPretty(value))); + }) + + if(result.length == 0){ + return '-'; + } + return result.join(', '); + } + + var toPretty = function(number){ + if(isInt(number)){ + number = parseInt(number); + } + else{ + number = parseFloat(number).toFixed(2); + } + return numberWithCommas(number); + } + + var showBattleResult = function(result){ + $('#result_datetime').html(result.datetime); + $('#result_warcnt').html(toPretty(result.avgWar)); + $('#result_phase').html(toPretty(result.phase)); + $('#result_killed').html(toPretty(result.killed)); + $('#result_dead').html(toPretty(result.dead)); + $('#result_attackerRice').html(toPretty(result.attackerRice)); + $('#result_defenderRice').html(toPretty(result.defenderRice)); + $('#result_attackerSkills').html(parseSkillCount(result.attackerSkills)); + + $('.result_defenderSkills').detach(); + + var $summary = $('#battle_result_summary'); + + $.each(result.defendersSkills, function(idx, defenderSkills){ + console.log(defenderSkills); + var $result = $("수비자{0} 스킬".format(idx + 1)); + $result.find('td').html(parseSkillCount(defenderSkills)); + $summary.append($result); + }); + + $('#generalBattleResultLog').html(result.lastWarLog.generalBattleResultLog); + $('#generalBattleDetailLog').html(result.lastWarLog.generalBattleDetailLog); + + } + + var beginBattle = function(){ + var data = extendAllDataForDB(exportAllData()); + console.log(data); + $.ajax({ + type:'post', + url:'j_simulate_battle.php', + dataType:'json', + data:{ + action:'battle', + query:JSON.stringify(data), + } + }).then(function(result){ + console.log(result); + if(!result.result){ + alert(result.reason); + return; + } + showBattleResult(result); + + }, function(result){ + alert('전투 개시 실패!'); + }); + } + + + var reorderDefender = function(defenderOrder){ + $.each(defenderOrder, function(idx, generalNo){ + + if(!(generalNo in defenderNoList)){ + //음..? + alert("{0}이 수비자 리스트에 없습니다. 버그인 듯 합니다.".format(generalNo)); + return true; + } + + var $defenderObj = defenderNoList[generalNo]; + $defenderObj.detach(); + $defenderColumn.append($defenderObj); + }) + } + + var requestReorderDefender = function(){ + var data = extendAllDataForDB(exportAllData()); + console.log(data); + $.ajax({ + type:'post', + url:'j_simulate_battle.php', + dataType:'json', + data:{ + action:'reorder', + query:JSON.stringify(data), + } + }).then(function(result){ + console.log(result); + if(!result.result){ + alert(result.reason); + return; + } + reorderDefender(result.order); + + }, function(result){ + alert('재정렬 실패!'); + }); + } + + initBasicEvent(); + $attackerCard.append($generalForm.clone(true,true)); + addDefender(); + + $('.btn-begin_battle').click(function(){ + beginBattle(); + }); + + $('.btn-reorder_defender').click(function(){ + requestReorderDefender(); + }) +}); \ No newline at end of file diff --git a/hwe/js/common.js b/hwe/js/common.js index b529c5cd..4574a3dc 100644 --- a/hwe/js/common.js +++ b/hwe/js/common.js @@ -23,6 +23,14 @@ var escapeHtml = (function (string) { } })(); +/** + * 변수가 정수인지 확인하는 함수 + * @param {*} n 정수인지 확인하기 위한 인자 + * @return {boolean} 정수인지 여부 + */ +function isInt(n) { + return +n === n && !(n % 1); +} /** * object의 array를 id를 key로 삼는 object로 재 변환 @@ -89,6 +97,11 @@ function convColorValue(color) { } + +function numberWithCommas(x) { + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); +} + /** * 단순한 Template 함수. <%변수명%>으로 template 가능 * @see https://github.com/krasimir/absurd/blob/master/lib/processors/html/helpers/TemplateEngine.js diff --git a/hwe/process_war.php b/hwe/process_war.php index fe7ebc2e..35f0f778 100644 --- a/hwe/process_war.php +++ b/hwe/process_war.php @@ -37,7 +37,7 @@ function processWar(array $rawAttacker, array $rawDefenderCity){ $city = new WarUnitCity($rawDefenderCity, $rawDefenderNation, $year, $month, $cityRate); - $defenderList = $db->query('SELECT no,name,nation,turntime,personal,special2,crew,crewtype,atmos,train,intel,intel2,book,power,power2,weap,injury,leader,leader2,horse,item,explevel,experience,dedication,level,gold,rice,dex0,dex10,dex20,dex30,dex40,warnum,killnum,deathnum,killcrew,deathcrew,recwar FROM general WHERE nation=%i AND city=%i AND nation!=0 and crew > 0 and rice>(crew/100) and ((train>=60 and atmos>=60 and mode=1) or (train>=80 and atmos>=80 and mode=2))', $city->getVar('nation'), $city->getVar('city')); + $defenderList = $db->query('SELECT no,name,nation,turntime,personal,special2,crew,crewtype,atmos,train,intel,intel2,book,power,power2,weap,injury,leader,leader2,horse,item,explevel,experience,dedication,level,gold,rice,dex0,dex10,dex20,dex30,dex40,warnum,killnum,deathnum,killcrew,deathcrew,recwar,mode FROM general WHERE nation=%i AND city=%i AND nation!=0 and crew > 0 and rice>(crew/100) and ((train>=60 and atmos>=60 and mode=1) or (train>=80 and atmos>=80 and mode=2))', $city->getVar('nation'), $city->getVar('city')); if(!$defenderList){ $defenderList = []; @@ -63,7 +63,12 @@ function processWar(array $rawAttacker, array $rawDefenderCity){ return null; } - $retVal = new WarUnitGeneral($iterDefender->current(), $rawDefenderCity, $rawDefenderNation, false, $year, $month); + $rawGeneral = $iterDefender->current(); + if(extractBattleOrder($rawGeneral) <= 0){ + return null; + } + + $retVal = new WarUnitGeneral($rawGeneral, $rawDefenderCity, $rawDefenderNation, false, $year, $month); $iterDefender->next(); return $retVal; }; @@ -154,6 +159,26 @@ function processWar(array $rawAttacker, array $rawDefenderCity){ } function extractBattleOrder($general){ + if($general['crew'] == 0){ + return 0; + } + + if($general['rice'] <= $general['crew'] / 100){ + return 0; + } + + if($general['mode'] == 0){ + return 0; + } + + if($general['mode'] == 1 && ($general['train'] < 60 || $general['atmos'] < 60)){ + return 0; + } + + if($general['mode'] == 2 && ($general['train'] < 80 || $general['atmos'] < 80)){ + return 0; + } + return ( $general['leader'] + $general['power'] + diff --git a/hwe/sammo/ActionLogger.php b/hwe/sammo/ActionLogger.php index abc33dd5..f55b68d0 100644 --- a/hwe/sammo/ActionLogger.php +++ b/hwe/sammo/ActionLogger.php @@ -43,6 +43,16 @@ class ActionLogger{ } public function rollback(){ + $backup = [ + 'generalHistoryLog'=>$this->generalHistoryLog, + 'generalActionLog'=>$this->generalActionLog, + 'generalBattleResultLog'=>$this->generalBattleResultLog, + 'generalBattleDetailLog'=>$this->generalBattleDetailLog, + 'nationalHistoryLog'=>$this->nationalHistoryLog, + 'globalHistoryLog'=>$this->globalHistoryLog, + 'globalActionLog'=>$this->globalActionLog, + ]; + $this->generalHistoryLog = []; $this->generalActionLog = []; $this->generalBattleResultLog = []; @@ -50,6 +60,8 @@ class ActionLogger{ $this->nationalHistoryLog = []; $this->globalHistoryLog = []; $this->globalActionLog = []; + + return $backup; } public function flush(){ diff --git a/hwe/sammo/WarUnit.php b/hwe/sammo/WarUnit.php index 91d2b422..5456cf31 100644 --- a/hwe/sammo/WarUnit.php +++ b/hwe/sammo/WarUnit.php @@ -28,11 +28,32 @@ class WarUnit{ protected $warPowerMultiply = 1.0; protected $activatedSkill = []; + protected $logActivatedSkill = []; protected $isFinished = false; private function __construct(){ } + protected function clearActivatedSkill(){ + foreach($this->activatedSkill as $skillName=>$state){ + if(!$state){ + continue; + } + + if(!key_exists($skillName, $this->logActivatedSkill)){ + $this->logActivatedSkill[$skillName] = 1; + } + else{ + $this->logActivatedSkill[$skillName] += 1; + } + } + $this->activatedSkill = []; + } + + function getActivatedSkillLog():array{ + return $this->logActivatedSkill; + } + function getRaw():array{ return $this->raw; } @@ -183,7 +204,7 @@ class WarUnit{ $this->oppose = $oppose; $this->killedCurr = 0; $this->deadCurr = 0; - $this->activatedSkill = []; + $this->clearActivatedSkill(); } function getOppose():?WarUnit{ @@ -312,7 +333,7 @@ class WarUnit{ } function beginPhase():void{ - $this->activatedSkill = []; + $this->clearActivatedSkill(); $this->computeWarPower(); } diff --git a/hwe/sammo/WarUnitCity.php b/hwe/sammo/WarUnitCity.php index bad6a251..2f58a161 100644 --- a/hwe/sammo/WarUnitCity.php +++ b/hwe/sammo/WarUnitCity.php @@ -90,6 +90,7 @@ class WarUnitCity extends WarUnit{ if($this->isFinished){ return; } + $this->clearActivatedSkill(); $this->isFinished = true; $this->updateVar('def', Util::round($this->getHP() / 10)); diff --git a/hwe/sammo/WarUnitGeneral.php b/hwe/sammo/WarUnitGeneral.php index e372751f..a59c859d 100644 --- a/hwe/sammo/WarUnitGeneral.php +++ b/hwe/sammo/WarUnitGeneral.php @@ -954,6 +954,7 @@ class WarUnitGeneral extends WarUnit{ if($this->isFinished){ return; } + $this->clearActivatedSkill(); $this->isFinished = true; $this->increaseVar('killcrew', $this->killed); diff --git a/src/sammo/Util.php b/src/sammo/Util.php index df473fdd..41e84a7d 100644 --- a/src/sammo/Util.php +++ b/src/sammo/Util.php @@ -350,6 +350,8 @@ class Util extends \utilphp\util shuffle($keys); + $new = []; + foreach($keys as $key) { $new[$key] = $array[$key]; }