오윤석

Merge branch 'master' into release

1 +const analyzeStats = function(characterInfo, analysisEquipment) {
2 + const jobModel = require('./job');
3 + const job = jobModel[characterInfo.character.job];
4 + const jobDefault = jobModel.default;
5 + const weaponConst = require('./weapon')[analysisEquipment.weapon] || 1;
6 +
7 + let rebootDamage = 0;
8 + if (characterInfo.character.server.name.indexOf("리부트") == 0) {
9 + // 리부트, 리부트2 월드 반영
10 + rebootDamage = parseInt(characterInfo.character.level / 2);
11 + }
12 +
13 + const stats = {
14 + major: {
15 + pure: 0,
16 + percent: analysisEquipment.majorPercent +
17 + job.stats.passive.major.percent +
18 + jobDefault.stats.passive.major.percent,
19 + added: 0
20 + },
21 + minor: characterInfo.stats.minor,
22 + damage: {
23 + all: characterInfo.stats.damageHyper +
24 + analysisEquipment.damagePercent +
25 + job.stats.passive.damage.all +
26 + jobDefault.stats.passive.damage.all +
27 + rebootDamage,
28 + boss: characterInfo.stats.bossAttackDamage
29 + },
30 + finalDamage: job.stats.passive.finalDamage,
31 + criticalDamage: characterInfo.stats.criticalDamage + jobDefault.stats.passive.criticalDamage,
32 + attackPower: {
33 + pure: 0,
34 + percent: analysisEquipment.attackPowerPercent +
35 + job.stats.passive.attackPower.percent
36 + },
37 + ignoreGuard: characterInfo.stats.ignoreGuard
38 + };
39 +
40 + stats.major.added = characterInfo.stats.majorHyper +
41 + analysisEquipment.majorArcane +
42 + jobDefault.stats.passive.major.added;
43 + stats.major.pure = (characterInfo.stats.major - stats.major.added) / (1 + stats.major.percent / 100);
44 +
45 + stats.attackPower.pure = characterInfo.stats.statAttackPower * 100 / (characterInfo.stats.major * 4 + stats.minor) / job.jobConst / weaponConst / (1 + stats.attackPower.percent / 100) / (1 + stats.damage.all / 100) / (1 + stats.finalDamage / 100);
46 +
47 + return stats;
48 +}
49 +
50 +const calculateEfficiency = function(stats, job, weapon) {
51 + const efficiency = {
52 + major: {
53 + pure: 1,
54 + percent: 0
55 + },
56 + attackPower: {
57 + pure: 0,
58 + percent: 0,
59 + },
60 + damage: 0,
61 + criticalDamage: 0,
62 + ignoreGuard: 0
63 + };
64 +
65 + const defaultPower = calculatePower(stats, job, weapon);
66 +
67 + stats.major.pure += 1;
68 + const majorPure = calculatePower(stats, job, weapon) - defaultPower;
69 + stats.major.pure -= 1;
70 +
71 + if (majorPure == 0)
72 + return efficiency;
73 +
74 + stats.major.percent += 1;
75 + efficiency.major.percent = (calculatePower(stats, job, weapon) - defaultPower) / majorPure;
76 + stats.major.percent -= 1;
77 +
78 + stats.attackPower.pure += 1;
79 + efficiency.attackPower.pure = (calculatePower(stats, job, weapon) - defaultPower) / majorPure;
80 + stats.attackPower.pure -= 1;
81 +
82 + stats.attackPower.percent += 1;
83 + efficiency.attackPower.percent = (calculatePower(stats, job, weapon) - defaultPower) / majorPure;
84 + stats.attackPower.percent -= 1;
85 +
86 + stats.damage.all += 1;
87 + efficiency.damage = (calculatePower(stats, job, weapon) - defaultPower) / majorPure;
88 + stats.damage.all -= 1;
89 +
90 + stats.criticalDamage += 1;
91 + efficiency.criticalDamage = (calculatePower(stats, job, weapon) - defaultPower) / majorPure;
92 + stats.criticalDamage -= 1;
93 +
94 + // 곱연산
95 + const ignoreGuardSaved = stats.ignoreGuard;
96 + stats.ignoreGuard = (1 - (1 - stats.ignoreGuard / 100) * 0.99) * 100;
97 + efficiency.ignoreGuard = (calculatePower(stats, job, weapon) - defaultPower) / majorPure;
98 + stats.ignoreGuard = ignoreGuardSaved;
99 +
100 + return efficiency;
101 +}
102 +
103 +// 버프 적용 스탯 구하기
104 +const getBuffStats = function(stats, job) {
105 + const jobModel = require('./job');
106 + const buff = jobModel[job].stats.active;
107 + const defaultBuff = jobModel.default.stats.active;
108 +
109 + return {
110 + major: {
111 + pure: stats.major.pure + buff.major.pure,
112 + percent: stats.major.percent + buff.major.percent,
113 + added: stats.major.added
114 + },
115 + minor: stats.minor,
116 + damage: {
117 + all: stats.damage.all + buff.damage.all + defaultBuff.damage.all,
118 + boss: stats.damage.boss + buff.damage.boss + defaultBuff.damage.boss
119 + },
120 + finalDamage: stats.finalDamage,
121 + criticalDamage: stats.criticalDamage + buff.criticalDamage + defaultBuff.criticalDamage,
122 + attackPower: {
123 + pure: stats.attackPower.pure + buff.attackPower.pure,
124 + percent: stats.attackPower.percent + buff.attackPower.percent + defaultBuff.attackPower.percent
125 + },
126 + ignoreGuard: (1 - (1 - (stats.ignoreGuard / 100)) * (1 - (buff.ignoreGuard / 100)) * (1 - (defaultBuff.ignoreGuard / 100))) * 100
127 + };
128 +}
129 +
130 +// 크리티컬 데미지, 보스 공격력, 방어율 무시를 반영하여 방어율 300% 몬스터 공격시 데미지 산출 값
131 +const calculatePower = function(stats, job, weapon) {
132 + const jobConst = require('./job')[job].jobConst;
133 + const weaponConst = require('./weapon')[weapon];
134 + return Math.max(
135 + (
136 + (stats.major.pure * (1 + stats.major.percent / 100) + stats.major.added) * 4 +
137 + stats.minor
138 + ) *
139 + 0.01 *
140 + (stats.attackPower.pure * (1 + stats.attackPower.percent / 100)) *
141 + jobConst *
142 + weaponConst *
143 + (1 + stats.damage.all / 100 + stats.damage.boss / 100) *
144 + (1 + stats.finalDamage / 100) *
145 + (1.35 + stats.criticalDamage / 100) *
146 + (1 - 3 * (1 - stats.ignoreGuard / 100)),
147 + 1);
148 +}
149 +
150 +module.exports = {
151 + analyzeStats: analyzeStats,
152 + calculateEfficiency: calculateEfficiency,
153 + getBuffStats: getBuffStats,
154 + calculatePower: calculatePower,
155 +}
...\ No newline at end of file ...\ No newline at end of file
1 +axios = require('axios');
2 +
3 +const crwalCharacterCode = async function(nickname, isReboot = false) {
4 + try {
5 + const resp = await axios.get("https://maplestory.nexon.com/Ranking/World/Total?c=" + encodeURI(nickname) + "&w=" + (isReboot ? "0" : "254"));
6 +
7 + const regex = new RegExp(`<dt><a href=\\"\\/Common\\/Character\\/Detail\\/[^\\?]+?\\?p=(.+?)\\"\\s+target=.+?\\/>${nickname}<\\/a><\\/dt>`);
8 + const regexResult = regex.exec(resp.data);
9 +
10 + if (!regexResult) {
11 + if (isReboot)
12 + return -2;
13 + else
14 + return await crwalCharacterCode(nickname, true);
15 + }
16 +
17 + return regexResult[1];
18 + } catch (error) {
19 + console.log(error);
20 + return -1;
21 + }
22 +};
23 +
24 +const getCharacterInfo = async function(nickname, characterCode) {
25 + try {
26 + const resp = await axios.get("https://maplestory.nexon.com/Common/Character/Detail/" + encodeURI(nickname) + "?p=" + characterCode);
27 +
28 + if (resp.data.indexOf("공개하지 않은 정보입니다.") >= 0) {
29 + throw new Error("private_character");
30 + }
31 +
32 + if (resp.data.indexOf("메이플스토리 게임 점검 중에는 이용하실 수 없습니다.") >= 0) {
33 + throw new Error("game_checking");
34 + }
35 +
36 + const character = {
37 + nickname: nickname,
38 + characterCode: characterCode,
39 + job: null,
40 + level: null,
41 + avatar: null,
42 + server: {
43 + icon: null,
44 + name: null
45 + },
46 + majorName: null,
47 + attackPowerName: null
48 + };
49 + const stats = {
50 + major: 0,
51 + minor: 0,
52 + majorHyper: 0,
53 + damageHyper: 0,
54 + criticalDamage: 0,
55 + bossAttackDamage: 0,
56 + ignoreGuard: 0,
57 + statAttackPower: 0
58 + };
59 +
60 + const { JSDOM } = require('jsdom');
61 + const dom = new JSDOM(resp.data);
62 + const $ = (require('jquery'))(dom.window);
63 +
64 + const jobModel = require('./job');
65 + const statModel = require('./stat');
66 +
67 + character.job = $(".tab01_con_wrap .table_style01:eq(0) tbody tr:eq(0) td:eq(1) span").text();
68 + character.level = parseInt($(".char_info dl:eq(0) dd").text().substring(3));
69 + character.avatar = $(".char_img img").attr("src");
70 + character.server = {
71 + name: $(".char_info dl:eq(2) dd").text(),
72 + icon: $(".char_info dl:eq(2) dd img").attr("src")
73 + };
74 + character.majorName = jobModel[character.job].major;
75 + character.attackPowerName = character.majorName == "INT" ? "마력" : "공격력";
76 +
77 + const $statInfo = $(".tab01_con_wrap .table_style01:eq(1)");
78 + $("tbody tr", $statInfo).each(function() {
79 + if ($("th", this).length == 1) {
80 + if ($("th span", this).text() == "하이퍼스탯") {
81 + const values = $("td span", this).html().split("<br>");
82 +
83 + const regexMajor = new RegExp(`${statModel[character.majorName].korean} (\\d+) 증가`);
84 + const regexDamage = new RegExp(`^데미지 (\\d+)% 증가`);
85 +
86 + let regexResult;
87 + for (let i = 0; i < values.length; i++) {
88 + if (regexResult = regexMajor.exec(values[i]))
89 + stats['majorHyper'] = parseInt(regexResult[1]);
90 + else if (regexResult = regexDamage.exec(values[i]))
91 + stats['damageHyper'] = parseInt(regexResult[1]);
92 + }
93 + }
94 + } else {
95 + for (let i = 0; i < 2; i++) {
96 + const statName = $(`th:eq(${i}) span`, this).text();
97 + const value = $(`td:eq(${i}) span`, this).text().replace(/\,/g, "");
98 +
99 + switch (statName) {
100 + case character.majorName:
101 + stats['major'] = parseInt(value);
102 + break;
103 + case jobModel[character.job].minor:
104 + stats['minor'] = parseInt(value);
105 + break;
106 + case "크리티컬 데미지":
107 + stats['criticalDamage'] = parseInt(value);
108 + break;
109 + case "보스공격력":
110 + stats['bossAttackDamage'] = parseInt(value);
111 + break;
112 + case "방어율무시":
113 + stats['ignoreGuard'] = parseInt(value);
114 + break;
115 + case "스탯공격력":
116 + stats['statAttackPower'] = parseInt(value.split(' ~ ')[1]);
117 + }
118 + }
119 + }
120 + });
121 +
122 + return {
123 + character: character,
124 + stats: stats
125 + };
126 + } catch (error) {
127 + console.log(error);
128 + if (error.message == "private_character")
129 + return -1;
130 + else if (error.message == "game_checking")
131 + return -2;
132 + else
133 + return -999;
134 + }
135 +}
136 +
137 +const analyzeEquipment = async function(nickname, characterCode, job) {
138 + try {
139 + const resp = await axios.get("https://maplestory.nexon.com/Common/Character/Detail/" + encodeURI(nickname) + "/Equipment?p=" + characterCode);
140 +
141 + if (resp.data.indexOf("공개하지 않은 정보입니다.") >= 0) {
142 + throw new Error("private_character");
143 + }
144 +
145 + if (resp.data.indexOf("메이플스토리 게임 점검 중에는 이용하실 수 없습니다.") >= 0) {
146 + throw new Error("game_checking");
147 + }
148 +
149 + const { JSDOM } = require('jsdom');
150 + const dom = new JSDOM(resp.data);
151 + const $ = (require('jquery'))(dom.window);
152 +
153 + // 아케인심볼 분석
154 + let majorArcane = 0;
155 + const arcaneURLs = [];
156 + $(".tab03_con_wrap .arcane_weapon_wrap .item_pot li span a").each(async function() {
157 + if (!!$(this).attr("href"))
158 + arcaneURLs.push("https://maplestory.nexon.com" + $(this).attr("href"));
159 + });
160 +
161 + for (let i = 0; i < arcaneURLs.length; i++) {
162 + const equipmentResp = await axios.get(arcaneURLs[i], {
163 + headers: {
164 + 'X-Requested-With': 'XMLHttpRequest'
165 + }
166 + });
167 +
168 + const equipmentDom = new JSDOM(equipmentResp.data.view);
169 + const $equipment = (require('jquery'))(equipmentDom.window);
170 +
171 + majorArcane += parseInt($equipment(".stet_info ul li:eq(2) .point_td font:eq(0)").text().substring(1));
172 + }
173 +
174 + // 장비 분석
175 + const jobModel = require('./job');
176 +
177 + let damagePercent = 0;
178 + let majorPercent = 0;
179 + let attackPowerPercent = 0;
180 + let weapon = undefined;
181 + const equipmentURLs = [];
182 + $(".tab01_con_wrap .weapon_wrap .item_pot li span a").each(async function() {
183 + equipmentURLs.push("https://maplestory.nexon.com" + $(this).attr("href"));
184 + });
185 +
186 + for (let i = 0; i < equipmentURLs.length; i++) {
187 + const equipmentResp = await axios.get(equipmentURLs[i], {
188 + headers: {
189 + 'X-Requested-With': 'XMLHttpRequest'
190 + }
191 + });
192 +
193 + const equipmentDom = new JSDOM(equipmentResp.data.view);
194 + const $equipment = (require('jquery'))(equipmentDom.window);
195 +
196 + const equipmentType = $equipment(".item_ability .ablilty02:eq(1) .job_name em").text();
197 + if (equipmentType.indexOf("손무기") >= 0 && equipmentType.indexOf("블레이드") < 0 && equipmentType.indexOf("대검") < 0) {
198 + weapon = equipmentType.split(" (")[0];
199 + }
200 +
201 + $equipment(".stet_info ul li").each(function() {
202 + const regexMajor1 = new RegExp(`${jobModel[job].major} : \\+(\\d+)%`);
203 + const regexMajor2 = new RegExp(`올스탯 : \\+(\\d+)%`);
204 + const regexAttackPower = (jobModel[job].major == "INT") ?
205 + new RegExp(`마력 : \\+(\\d+)%`) :
206 + new RegExp(`공격력 : \\+(\\d+)%`);
207 + const regexDamage = new RegExp(`^데미지 : \\+(\\d+)%`);
208 +
209 + if ($(this).find(".stet_th span").text() == "올스탯") {
210 + majorPercent += parseInt($(this).find(".point_td font:eq(0)").text().substring(1));
211 + } else if ($(this).find(".stet_th span").text().indexOf("잠재옵션") >= 0) {
212 + const values = $(this).find(".point_td").html().split("<br>");
213 + for (let j = 0; j < values.length; j++) {
214 + const value = values[j].trim();
215 + let regexResult;
216 +
217 + if (regexResult = (regexMajor1.exec(value) || regexMajor2.exec(value))) {
218 + majorPercent += parseInt(regexResult[1]);
219 + } else if (regexResult = regexAttackPower.exec(value)) {
220 + attackPowerPercent += parseInt(regexResult[1]);
221 + } else if (regexResult = regexDamage.exec(value)) {
222 + damagePercent += parseInt(regexResult[1]);
223 + }
224 + }
225 + }
226 + })
227 + }
228 +
229 + return {
230 + majorArcane: majorArcane,
231 + majorPercent: majorPercent,
232 + attackPowerPercent: attackPowerPercent,
233 + damagePercent: damagePercent,
234 + weapon: weapon
235 + };
236 + } catch (error) {
237 + console.log(error);
238 + if (error.message == "private_character")
239 + return -1;
240 + else if (error.message == "game_checking")
241 + return -2;
242 + else
243 + return -999;
244 + }
245 +}
246 +
247 +module.exports = {
248 + crwalCharacterCode: crwalCharacterCode,
249 + getCharacterInfo: getCharacterInfo,
250 + analyzeEquipment: analyzeEquipment,
251 +}
...\ No newline at end of file ...\ No newline at end of file
This diff is collapsed. Click to expand it.