From 9778e496aba267e222596d0daf4d3cf9c77214f4 Mon Sep 17 00:00:00 2001 From: shykai Date: Thu, 18 Dec 2025 12:42:41 +0800 Subject: [PATCH 01/46] fix --- dist/bundle.js | 2 +- dist/patchNote.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dist/bundle.js b/dist/bundle.js index 94bf5770..498e9969 100644 --- a/dist/bundle.js +++ b/dist/bundle.js @@ -1466,7 +1466,7 @@ class Trigger { \************************/ /***/ ((module) => { -module.exports = /*#__PURE__*/JSON.parse('{"2025年12月18日":["支持成就系统及对应buff生效"],"2025年12月6日":["修复游戏更新后技能在无trigger情况下由[]变为null时造成的异常"],"2025年11月7日":["兼容支持从CN镜像站调用API获取价格"],"2025年10月14日":["修复怪物攻击间隔数值未能适配攻击等级的问题"],"2025年9月17日":["修复暴击光环的trigger缺陷"],"2025年9月9日":["复活时不再错误的清空所有buff","团灭日志增加反伤、荆棘和DOT伤害记录"],"2025年8月21日":["增加单挑战斗批量模拟和对应怪物选项","增加MooPass和社区buff的选项及对应功能","精炼装备数值加强","秘法主教属性削弱","init_client_info_v1.20250819.0.json游戏数据更新"],"2025年8月20日":["修复经验和掉落计算在极端情况下的可能异常"],"2025年8月19日":["合并Test和Temp分支的rework内容","init_client_info_v1.20250818.0.json游戏数据更新"],"2025年8月18日":["修复贯穿技能可能对相同目标造成重复伤害的问题","修复团灭日志在黑夜模式下的显示异常","战斗等级公式更新","钟乳石魔像的荆棘数值调整","init_client_info_v1.20250626.0_0817.json游戏数据更新"],"2025年8月16日":["增加停止模拟按钮 by BKN46","增加技能顺序调整按钮 by BKN46","增加团灭日志 by TruthLight","怪物属性更新","奥术反射更名为报应","init_client_info_v1.20250626.0_0815.json游戏数据更新"],"2025年8月14日":["怪物属性更新","远程和法师装备属性调整","反伤计算上限调整","修复战斗间隔释放技能的异常","修复技能释放判断逻辑的异常","法力值耗尽比例更加准确","调整远程经验的15%和魔法经验的12%映射到攻击经验","init_client_info_v1.20250626.0_0813.json游戏数据更新"],"2025年8月11日":["怪物属性更新","近战和物理技能施法时间更新","盾击和重锤数值调整","双手盾防御经验加成调整","init_client_info_v1.20250626.0_0811.json游戏数据更新"],"2025年8月8日":["实现组队等级差过大时对掉落和经验的惩罚","实现怪物经验随狂暴进度百分比增加","暴击光环数值调整","增加战斗等级数值显示","增加等级差距惩罚数值显示","init_client_info_v1.20250626.0_0807.json游戏数据更新"],"2025年8月7日":["修复组队战斗时一些重复物品掉落数量异常的缺陷 by contr4l","init_client_info_v1.20250626.0_0806.json游戏数据更新"],"2025年8月3日":["怪物狂暴机制及对应trigger生效","精炼装备更新,护符数值调整,守护光环增加闪避率","init_client_info_v1.20250626.0_0802.json游戏数据更新","狂怒层数修正为5层","招架结算机制调整"],"2025年7月31日":["物品数据和怪物属性更新","尖刺外壳和奥术反射重做","强化数值更新","删除异常trigger","狮鹫盾的虚弱重做","君王剑招架对队友生效","狂怒特效最大层数修正为6层","涟漪特效增加10MP恢复","反伤正确显示其命中率","反伤机制调整","同步双手盾属性和反伤荆棘技能数值的调整"],"2025年7月22日":["暴击光环受远程等级加成","光环基础数值和等级加成调整"],"2025年7月17日":["批量模拟支持勾选星球","经验分配比例调整至30%+70%","光环及对应trigger,并按对应技能等级百分比加成","水火自然默认调整为元素光环","init_client_info_v1.20250626.0_0717.json游戏数据更新"],"2025年7月11日":["怪物经验和技能等级公式更新","闪避和抗性计算公式更新","力量更替为近战以及对应的兼容","init_client_info_v1.20250626.0_0711.json游戏数据更新"],"2025年7月10日":["修复贯穿技能由敌人释放时可能多次击中相同目标的缺陷"],"2025年7月9日":["掉落和掉率调整","经验调整","疫病射击和破甲之刺调整","怪物自动恢复移除","疫病射击trigger调整","获取价格使用官方API"],"2025年7月7日":["怪物属性缩放和地图多难度","法师技能调整和装备上\'技能伤害\'词缀生效","攻击等级和房屋等级对施法速度的影响生效","物品调整","精准重做以攻击等级计算","TEST 远程魔法经验的10%映射到攻击经验!","经验重做和护符装备"]}'); +module.exports = /*#__PURE__*/JSON.parse('{"2025年12月18日":["支持成就系统及对应buff效果","地下城怪物的掉落不再生效"],"2025年12月6日":["修复游戏更新后技能在无trigger情况下由[]变为null时造成的异常"],"2025年11月7日":["兼容支持从CN镜像站调用API获取价格"],"2025年10月14日":["修复怪物攻击间隔数值未能适配攻击等级的问题"],"2025年9月17日":["修复暴击光环的trigger缺陷"],"2025年9月9日":["复活时不再错误的清空所有buff","团灭日志增加反伤、荆棘和DOT伤害记录"],"2025年8月21日":["增加单挑战斗批量模拟和对应怪物选项","增加MooPass和社区buff的选项及对应功能","精炼装备数值加强","秘法主教属性削弱","init_client_info_v1.20250819.0.json游戏数据更新"],"2025年8月20日":["修复经验和掉落计算在极端情况下的可能异常"],"2025年8月19日":["合并Test和Temp分支的rework内容","init_client_info_v1.20250818.0.json游戏数据更新"],"2025年8月18日":["修复贯穿技能可能对相同目标造成重复伤害的问题","修复团灭日志在黑夜模式下的显示异常","战斗等级公式更新","钟乳石魔像的荆棘数值调整","init_client_info_v1.20250626.0_0817.json游戏数据更新"],"2025年8月16日":["增加停止模拟按钮 by BKN46","增加技能顺序调整按钮 by BKN46","增加团灭日志 by TruthLight","怪物属性更新","奥术反射更名为报应","init_client_info_v1.20250626.0_0815.json游戏数据更新"],"2025年8月14日":["怪物属性更新","远程和法师装备属性调整","反伤计算上限调整","修复战斗间隔释放技能的异常","修复技能释放判断逻辑的异常","法力值耗尽比例更加准确","调整远程经验的15%和魔法经验的12%映射到攻击经验","init_client_info_v1.20250626.0_0813.json游戏数据更新"],"2025年8月11日":["怪物属性更新","近战和物理技能施法时间更新","盾击和重锤数值调整","双手盾防御经验加成调整","init_client_info_v1.20250626.0_0811.json游戏数据更新"],"2025年8月8日":["实现组队等级差过大时对掉落和经验的惩罚","实现怪物经验随狂暴进度百分比增加","暴击光环数值调整","增加战斗等级数值显示","增加等级差距惩罚数值显示","init_client_info_v1.20250626.0_0807.json游戏数据更新"],"2025年8月7日":["修复组队战斗时一些重复物品掉落数量异常的缺陷 by contr4l","init_client_info_v1.20250626.0_0806.json游戏数据更新"],"2025年8月3日":["怪物狂暴机制及对应trigger生效","精炼装备更新,护符数值调整,守护光环增加闪避率","init_client_info_v1.20250626.0_0802.json游戏数据更新","狂怒层数修正为5层","招架结算机制调整"],"2025年7月31日":["物品数据和怪物属性更新","尖刺外壳和奥术反射重做","强化数值更新","删除异常trigger","狮鹫盾的虚弱重做","君王剑招架对队友生效","狂怒特效最大层数修正为6层","涟漪特效增加10MP恢复","反伤正确显示其命中率","反伤机制调整","同步双手盾属性和反伤荆棘技能数值的调整"],"2025年7月22日":["暴击光环受远程等级加成","光环基础数值和等级加成调整"],"2025年7月17日":["批量模拟支持勾选星球","经验分配比例调整至30%+70%","光环及对应trigger,并按对应技能等级百分比加成","水火自然默认调整为元素光环","init_client_info_v1.20250626.0_0717.json游戏数据更新"],"2025年7月11日":["怪物经验和技能等级公式更新","闪避和抗性计算公式更新","力量更替为近战以及对应的兼容","init_client_info_v1.20250626.0_0711.json游戏数据更新"],"2025年7月10日":["修复贯穿技能由敌人释放时可能多次击中相同目标的缺陷"],"2025年7月9日":["掉落和掉率调整","经验调整","疫病射击和破甲之刺调整","怪物自动恢复移除","疫病射击trigger调整","获取价格使用官方API"],"2025年7月7日":["怪物属性缩放和地图多难度","法师技能调整和装备上\'技能伤害\'词缀生效","攻击等级和房屋等级对施法速度的影响生效","物品调整","精准重做以攻击等级计算","TEST 远程魔法经验的10%映射到攻击经验!","经验重做和护符装备"]}'); /***/ }), diff --git a/dist/patchNote.json b/dist/patchNote.json index eadba280..1c5d1580 100644 --- a/dist/patchNote.json +++ b/dist/patchNote.json @@ -1,7 +1,8 @@ { "2025年12月18日": [ - "支持成就系统及对应buff生效" + "支持成就系统及对应buff效果", + "地下城怪物的掉落不再生效" ], "2025年12月6日": [ "修复游戏更新后技能在无trigger情况下由[]变为null时造成的异常" From 128679677d16dc7935343c697b76bbaab2c5c867 Mon Sep 17 00:00:00 2001 From: shykai Date: Thu, 18 Dec 2025 16:16:30 +0800 Subject: [PATCH 02/46] fix --- dist/bundle.js | 13 ++++++++++++- dist/bundle.js.map | 2 +- src/main.js | 13 ++++++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/dist/bundle.js b/dist/bundle.js index 498e9969..afcd2e23 100644 --- a/dist/bundle.js +++ b/dist/bundle.js @@ -2011,8 +2011,19 @@ function initAchievementsModal(){ bufDesc.appendChild(buffValue); cardHeader.appendChild(bufDesc); - let cardStatics = createElement("div", "ms-auto", `(0/${detailMapCount})`); + let cardStatics = createElement("div", "ms-auto btn", `(0/${detailMapCount})`); cardStatics.id = `AchTier${tier.sortIndex}Statics`; + cardStatics.dataset.checked = "true"; + cardStatics.addEventListener("click", function (e) { + const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier="${tier.sortIndex}"]`); + for (const check of checks) { + check.checked = cardStatics.dataset.checked == "true"; + const hrid = check.dataset.achievementHrid; + player.achievements[hrid] = check.checked; + } + cardStatics.dataset.checked = cardStatics.dataset.checked == "true" ? "false" : "true"; + refreshAchievementStatics(); + }); cardHeader.appendChild(cardStatics); card.appendChild(cardHeader); diff --git a/dist/bundle.js.map b/dist/bundle.js.map index ca0543f4..bc969c74 100644 --- a/dist/bundle.js.map +++ b/dist/bundle.js.map @@ -1 +1 @@ -{"version":3,"file":"bundle.js","mappings":";;;;;;;;;;;;;;;;;AAA0B;AACkC;AAC5B;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,wDAAgB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,6CAAI;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;ACxNG;AACkD;AACR;AACpE;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,gEAAwB;AAChE;AACA,0CAA0C,4DAAoB;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,WAAW;;;;;;;;;;;;;;AC1B1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,IAAI,EAAC;;;;;;;;;;;;;;;ACdpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wDAAwD;AACtF,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,mBAAmB;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;;AC/gBA;AAC4B;AACtB;AAChC;AACA;AACA;AACA;AACA;AACA,6BAA6B,qDAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;ACtF4B;AACmD;AACzG;AACA;AACA;AACA;AACA,uBAAuB,qDAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,iFAAoC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS,EAAC;;;;;;;;;;;;;;;;;AC/CC;AACsC;AAChE;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,0DAAkB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS;;;;;;;;;;;;;;;;;;;;AC7BQ;AACM;AACA;AACF;AACA;AACI;AACxC;AACA,qBAAqB,mDAAU;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,kDAAS;AACrD;AACA;AACA,qDAAqD,mDAAU;AAC/D,2DAA2D,mDAAU;AACrE,qEAAqE,gDAAO;AAC5E;AACA;AACA,2CAA2C,kDAAS;AACpD;AACA,SAAS;AACT;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,MAAM,EAAC;;;;;;;;;;;;;;;;ACvLsE;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,wEAAgC;AAC5C;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UCjLvB;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;UAEA;UACA;;;;;WCzBA;WACA;WACA;WACA;WACA,yCAAyC,wCAAwC;WACjF;WACA;WACA;;;;;WCPA;WACA;WACA;WACA;WACA;;;;;WCJA;WACA;WACA;WACA;WACA,GAAG;WACH;WACA;WACA,CAAC;;;;;WCPD;;;;;WCAA;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D;;;;;WCNA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;;;;WClBA;;WAEA;WACA;WACA;WACA;WACA;WACA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrBuD;AACN;AAC2B;AACN;AACU;AAC7B;AACM;AACN;AACyD;AACF;AACE;AACA;AAClC;AACc;AACN;AACE;AACF;AACG;AACF;AACnF;AAC0C;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,2FAA4B;AAC5D,qCAAqC,qHAAiC;AACtE;AACA;AACA;AACA,iBAAiB,kEAAM;AACvB;AACA,IAAI,SAAI;AACR,IAAI,WAAM;AACV,IAAI,cAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,iBAAiB;AACxpC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,iBAAiB;AACxpC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,iBAAiB;AACxpC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,iBAAiB;AACxpC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,iBAAiB;AACxpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA,sCAAsC,qEAAa;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,mCAAmC,0EAAkB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,iFAAkB;AAClD;AACA,4FAA4F,eAAe;AAC3G;AACA;AACA;AACA,uDAAuD,eAAe;AACtE,6BAA6B,KAAK,GAAG,MAAM;AAC3C;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,iFAAkB;AAClD;AACA,sCAAsC,6EAAoB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,eAAe;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gEAAgE,eAAe;AAC/E,mCAAmC,eAAe;AAClD;AACA;AACA;AACA;AACA;AACA,gCAAgC,eAAe;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,iBAAiB;AACpD;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,iDAAiD,iBAAiB;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,qEAAa;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,qEAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,qEAAS;AACvD,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,6EAAoB;AACvD;AACA;AACA,qBAAqB,4EAAmB;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,sCAAsC,qEAAa;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,SAAI;AACZ,YAAY,SAAI,mBAAmB,SAAI;AACvC,2BAA2B,qEAAa,CAAC,SAAI;AAC7C,uBAAuB,SAAI;AAC3B;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,qFAAqF,SAAI;AACzF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,uCAAuC,qEAAa;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,WAAM;AACd,YAAY,WAAM,mBAAmB,WAAM;AAC3C,2BAA2B,qEAAa,CAAC,WAAM;AAC/C,uBAAuB,WAAM;AAC7B;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,sFAAsF,WAAM;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D,UAAU;AACV,0CAA0C,wEAAgB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,cAAS;AACjB,YAAY,cAAS,mBAAmB,cAAS;AACjD,8BAA8B,wEAAgB,CAAC,cAAS;AACxD,uBAAuB,cAAS;AAChC;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,4DAA4D,yFAAgC;AAC5F,2DAA2D,yFAAgC;AAC3F,4DAA4D,yFAAgC,YAAY,cAAS;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAS;AACjC,IAAI,cAAS,iBAAiB,cAAS;AACvC,IAAI,cAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,cAAS;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,SAAI;AAChC;AACA;AACA,4BAA4B,WAAM;AAClC;AACA;AACA,4BAA4B,cAAS;AACrC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,qEAAa;AACrD,MAAM;AACN,wCAAwC,wEAAgB;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,yFAAgC;AAC5C;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,wFAAgC;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,wFAAgC;AACrD;AACA;AACA;AACA,mCAAmC,uFAA+B;AAClE,MAAM;AACN,mCAAmC,uFAA+B;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uFAA+B;AACnD;AACA,qEAAqE,yFAAgC;AACrG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,wEAAe;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2GAA2G,WAAW;AACtH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,WAAW;AAC5C,yDAAyD,WAAW;AACpE;AACA;AACA;AACA,gEAAgE,WAAW;AAC3E;AACA;AACA,qGAAqG,WAAW;AAChH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qFAAqF,WAAW;AAChG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4GAA4G,WAAW;AACvH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,WAAW;AAC7C,0DAA0D,WAAW;AACrE;AACA;AACA;AACA,iEAAiE,WAAW;AAC5E;AACA;AACA,sGAAsG,WAAW;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sFAAsF,WAAW;AACjG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,OAAO;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uBAAuB;AAC3C,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,+EAAsB;AAClC,+BAA+B,+EAAsB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,iJAAiJ;AAC9L;AACA,gBAAgB,+EAAsB;AACtC,mCAAmC,+EAAsB;AACzD;AACA;AACA;AACA,qDAAqD,uIAAuI;AAC5L;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,+BAA+B;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,UAAU,kCAAkC;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uEAAuE,oDAAoD;AAC3H,iEAAiE,oDAAoD;AACrH,iFAAiF,oDAAoD;AACrI;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,eAAe;AACrC;AACA;AACA;AACA;AACA,0BAA0B,mBAAmB;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,SAAS;AAC3D;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,KAAK;AACL,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,iBAAiB;AACrC;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,+CAA+C,kBAAkB;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,+EAAsB;AACvC;AACA;AACA;AACA,SAAS;AACT;AACA,UAAU,kCAAkC,qEAAqE;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2EAA2E,wEAAgB;AAC3F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa,qEAAa;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,qEAAa;AACjC,iCAAiC,qEAAa;AAC9C;AACA,kBAAkB,SAAS,wEAAgB;AAC3C,iCAAiC,wEAAgB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,qEAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kFAAkF,IAAI;AACtF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,QAAQ;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,QAAQ;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wEAAgB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,6BAA6B;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qEAAqE,UAAU;AAC/E;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,sCAAsC,4BAA4B;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,qCAAqC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC,2FAA4B;AAChE;AACA;AACA;AACA;AACA,yCAAyC,qHAAiC;AAC1E;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,SAAI;AACxB,yCAAyC,sEAAU,CAAC,SAAI,gBAAgB,SAAI;AAC5E;AACA,kBAAkB;AAClB;AACA;AACA;AACA,oBAAoB,WAAM;AAC1B,yCAAyC,sEAAU,CAAC,WAAM,gBAAgB,WAAM;AAChF;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,cAAS,mCAAmC,yFAAgC;AAChG;AACA,sCAAsC,mEAAO,CAAC,cAAS,iDAAiD,cAAS;AACjH;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,oDAAoD;AACxE;AACA;AACA;AACA;AACA;AACA,wCAAwC,qHAAiC;AACzE;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;AACA;AACA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,wCAAwC;AACrF,kCAAkC,uDAAuD;AACzF;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,qHAAiC;AAC9E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,KAAK,SAAS,qEAAS;AAC5E;AACA,KAAK;AACL;AACA;AACA;AACA,yBAAyB,sEAAU;AACnC;AACA;AACA;AACA;AACA,0BAA0B,sEAAU;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,mEAAO;AAC1C;AACA;AACA;AACA,mBAAmB,kEAAM;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wEAAwE,UAAU;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,IAAI;AAC7D;AACA;AACA;AACA;AACA;AACA,sCAAsC,wEAAe;AACrD;AACA;AACA;AACA;AACA;AACA,4CAA4C,0BAA0B;AACtE;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,wCAAwC;AACjG,8CAA8C,uDAAuD;AACrG;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,oDAAoD;AACpF;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,WAAW,QAAQ,KAAK;AACzD;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,KAAK;AACxC,oCAAoC,KAAK,YAAY,UAAU;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,wBAAwB,WAAW,WAAW;AACnF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd,qEAAqE,WAAW;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd,sEAAsE,YAAY;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sEAAsE,cAAc,IAAI,YAAY;AACpG;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,YAAY,IAAI,eAAe,GAAG,WAAW;AACxF;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,kCAAkC;AAClC,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB,qBAAqB;AACrB,gBAAgB;AAChB,kBAAkB;AAClB,qBAAqB;AACrB,sBAAsB;AACtB,sBAAsB;AACtB,wBAAwB;AACxB;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,uFAA+B;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,SAAI;AAClB,gBAAgB,WAAM;AACtB,mBAAmB,cAAS;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,qEAAa;AACxC,yBAAyB,qEAAa;AACtC;AACA,wCAAwC,gCAAgC,qEAAa;AACrF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yCAAyC;AACzC;AACA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;AACA;AACA;AACA,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,sBAAsB,4GAAmD;AACzE;AACA,aAAa;AACb;AACA;AACA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,6CAAS;AAC9B;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,6CAAS;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sources":["webpack://mwicombatsimulator/./src/combatsimulator/ability.js","webpack://mwicombatsimulator/./src/combatsimulator/achievement.js","webpack://mwicombatsimulator/./src/combatsimulator/buff.js","webpack://mwicombatsimulator/./src/combatsimulator/combatUnit.js","webpack://mwicombatsimulator/./src/combatsimulator/consumable.js","webpack://mwicombatsimulator/./src/combatsimulator/equipment.js","webpack://mwicombatsimulator/./src/combatsimulator/houseRoom.js","webpack://mwicombatsimulator/./src/combatsimulator/player.js","webpack://mwicombatsimulator/./src/combatsimulator/trigger.js","webpack://mwicombatsimulator/webpack/bootstrap","webpack://mwicombatsimulator/webpack/runtime/define property getters","webpack://mwicombatsimulator/webpack/runtime/get javascript chunk filename","webpack://mwicombatsimulator/webpack/runtime/global","webpack://mwicombatsimulator/webpack/runtime/hasOwnProperty shorthand","webpack://mwicombatsimulator/webpack/runtime/make namespace object","webpack://mwicombatsimulator/webpack/runtime/publicPath","webpack://mwicombatsimulator/webpack/runtime/jsonp chunk loading","webpack://mwicombatsimulator/./src/main.js"],"sourcesContent":["import Buff from \"./buff\";\r\nimport abilityDetailMap from \"./data/abilityDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nconst abilityFromCombatStat = {\r\n \"blaze\":\r\n {\r\n \"hrid\": \"/abilities/blaze\",\r\n \"name\": \"Blaze\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"allEnemies\",\r\n \"effectType\": \"/ability_effect_types/damage\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"/damage_types/fire\",\r\n \"baseDamageFlat\": 0,\r\n \"baseDamageFlatLevelBonus\": 0.0,\r\n \"baseDamageRatio\": 0.3,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/number_of_active_units\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n },\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/current_hp\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n }\r\n ],\r\n },\r\n \"bloom\":\r\n {\r\n \"hrid\": \"/abilities/bloom\",\r\n \"name\": \"Bloom\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"lowestHpAlly\",\r\n \"effectType\": \"/ability_effect_types/heal\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"\",\r\n \"baseDamageFlat\": 10,\r\n \"baseDamageFlatLevelBonus\": 0,\r\n \"baseDamageRatio\": 0.15,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_allies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/lowest_hp_percentage\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/less_than_equal\",\r\n \"value\": 100\r\n }\r\n ],\r\n }\r\n}\r\n\r\nclass Ability {\r\n constructor(hrid, level = 1, triggers = null) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameAbility = abilityDetailMap[hrid];\r\n if (!gameAbility) {\r\n gameAbility = abilityFromCombatStat[hrid];\r\n }\r\n if (!gameAbility) {\r\n throw new Error(\"No ability found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.manaCost = gameAbility.manaCost;\r\n this.cooldownDuration = gameAbility.cooldownDuration;\r\n this.castDuration = gameAbility.castDuration;\r\n this.isSpecialAbility = gameAbility.isSpecialAbility;\r\n\r\n this.abilityEffects = [];\r\n\r\n for (const effect of gameAbility.abilityEffects) {\r\n let abilityEffect = {\r\n targetType: effect.targetType,\r\n effectType: effect.effectType,\r\n combatStyleHrid: effect.combatStyleHrid,\r\n damageType: effect.damageType,\r\n damageFlat: effect.baseDamageFlat + (this.level - 1) * effect.baseDamageFlatLevelBonus,\r\n damageRatio: effect.baseDamageRatio + (this.level - 1) * effect.baseDamageRatioLevelBonus,\r\n bonusAccuracyRatio: effect.bonusAccuracyRatio + (this.level - 1) * effect.bonusAccuracyRatioLevelBonus,\r\n damageOverTimeRatio: effect.damageOverTimeRatio,\r\n damageOverTimeDuration: effect.damageOverTimeDuration,\r\n armorDamageRatio: effect.armorDamageRatio + (this.level - 1) * effect.armorDamageRatioLevelBonus,\r\n hpDrainRatio: effect.hpDrainRatio,\r\n pierceChance: effect.pierceChance,\r\n blindChance: effect.blindChance,\r\n blindDuration: effect.blindDuration,\r\n silenceChance: effect.silenceChance,\r\n silenceDuration: effect.silenceDuration,\r\n stunChance: effect.stunChance,\r\n stunDuration: effect.stunDuration,\r\n spendHpRatio: effect.spendHpRatio,\r\n buffs: null,\r\n };\r\n if (effect.buffs) {\r\n abilityEffect.buffs = [];\r\n for (const buff of effect.buffs) {\r\n abilityEffect.buffs.push(new Buff(buff, this.level));\r\n }\r\n }\r\n this.abilityEffects.push(abilityEffect);\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameAbility.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let ability = new Ability(dto.hrid, dto.level, triggers);\r\n\r\n return ability;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n\r\n if (source.isSilenced) {\r\n return false;\r\n }\r\n\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = this.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Ability;\r\n","import Buff from \"./buff\";\r\nimport achievementTierDetailMap from \"./data/achievementTierDetailMap.json\";\r\nimport achievementDetailMap from \"./data/achievementDetailMap.json\";\r\n\r\nclass Achievement {\r\n constructor(achievements) {\r\n this.achievements = achievements;\r\n this.buffs = [];\r\n\r\n for(const tier of Object.values(achievementTierDetailMap)) {\r\n let isGetAll = true;\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid)\r\n for(const achievement of Object.values(detailMap)) {\r\n if(!this.achievements[achievement.hrid] || this.achievements[achievement.hrid] == false) {\r\n isGetAll = false;\r\n break;\r\n }\r\n }\r\n if(isGetAll) {\r\n let buff = new Buff(tier.buff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default Achievement;","class Buff {\r\n startTime;\r\n\r\n constructor(buff, level = 1) {\r\n this.uniqueHrid = buff.uniqueHrid;\r\n this.typeHrid = buff.typeHrid;\r\n this.ratioBoost = buff.ratioBoost + (level - 1) * buff.ratioBoostLevelBonus;\r\n this.flatBoost = buff.flatBoost + (level - 1) * buff.flatBoostLevelBonus;\r\n this.duration = buff.duration;\r\n this.multiplierForSkillHrid = buff.multiplierForSkillHrid ?? \"\";\r\n this.multiplierPerSkillLevel = buff.multiplierPerSkillLevel ?? 0;\r\n }\r\n}\r\n\r\nexport default Buff;\r\n","class CombatUnit {\r\n isPlayer;\r\n isStunned = false;\r\n stunExpireTime = null;\r\n isBlinded = false;\r\n blindExpireTime = null;\r\n isSilenced = false;\r\n silenceExpireTime = null;\r\n\r\n isOutOfMana = false;\r\n\r\n // Base levels which don't change after initialization\r\n staminaLevel = 1;\r\n intelligenceLevel = 1;\r\n attackLevel = 1;\r\n meleeLevel = 1;\r\n defenseLevel = 1;\r\n rangedLevel = 1;\r\n magicLevel = 1;\r\n\r\n experience = 0;\r\n experienceRate = 0;\r\n enrageTime = 0;\r\n\r\n abilities = [null, null, null, null];\r\n food = [null, null, null];\r\n drinks = [null, null, null];\r\n houseRooms = [];\r\n achievements = null;\r\n dropTable = [];\r\n rareDropTable = [];\r\n abilityManaCosts = new Map();\r\n\r\n // Calculated combat stats including temporary buffs\r\n combatDetails = {\r\n staminaLevel: 1,\r\n intelligenceLevel: 1,\r\n attackLevel: 1,\r\n meleeLevel: 1,\r\n defenseLevel: 1,\r\n rangedLevel: 1,\r\n magicLevel: 1,\r\n maxHitpoints: 110,\r\n currentHitpoints: 110,\r\n maxManapoints: 110,\r\n currentManapoints: 110,\r\n stabAccuracyRating: 11,\r\n slashAccuracyRating: 11,\r\n smashAccuracyRating: 11,\r\n rangedAccuracyRating: 11,\r\n magicAccuracyRating: 11,\r\n stabMaxDamage: 11,\r\n slashMaxDamage: 11,\r\n smashMaxDamage: 11,\r\n rangedMaxDamage: 11,\r\n magicMaxDamage: 11,\r\n stabEvasionRating: 11,\r\n slashEvasionRating: 11,\r\n smashEvasionRating: 11,\r\n rangedEvasionRating: 11,\r\n magicEvasionRating: 11,\r\n defensiveMaxDamage: 0,\r\n totalArmor: 0.2,\r\n totalWaterResistance: 0.4,\r\n totalNatureResistance: 0.4,\r\n totalFireResistance: 0.4,\r\n abilityHaste: 0,\r\n tenacity: 0,\r\n totalThreat: 100,\r\n combatStats: {\r\n combatStyleHrid: \"/combat_styles/smash\",\r\n damageType: \"/damage_types/physical\",\r\n attackInterval: 3000000000,\r\n autoAttackDamage: 0,\r\n abilityDamage: 0,\r\n criticalRate: 0,\r\n criticalDamage: 0,\r\n stabAccuracy: 0,\r\n slashAccuracy: 0,\r\n smashAccuracy: 0,\r\n rangedAccuracy: 0,\r\n magicAccuracy: 0,\r\n stabDamage: 0,\r\n slashDamage: 0,\r\n smashDamage: 0,\r\n rangedDamage: 0,\r\n magicDamage: 0,\r\n defensiveDamage: 0,\r\n taskDamage: 0,\r\n physicalAmplify: 0,\r\n waterAmplify: 0,\r\n natureAmplify: 0,\r\n fireAmplify: 0,\r\n healingAmplify: 0,\r\n physicalThorns: 0,\r\n elementalThorns: 0,\r\n maxHitpoints: 0,\r\n maxManapoints: 0,\r\n stabEvasion: 0,\r\n slashEvasion: 0,\r\n smashEvasion: 0,\r\n rangedEvasion: 0,\r\n magicEvasion: 0,\r\n armor: 0,\r\n waterResistance: 0,\r\n natureResistance: 0,\r\n fireResistance: 0,\r\n lifeSteal: 0,\r\n hpRegenPer10: 0.01,\r\n mpRegenPer10: 0.01,\r\n combatDropRate: 0,\r\n combatDropQuantity: 0,\r\n combatRareFind: 0,\r\n combatExperience: 0,\r\n foodSlots: 1,\r\n drinkSlots: 1,\r\n armorPenetration: 0,\r\n waterPenetration: 0,\r\n naturePenetration: 0,\r\n firePenetration: 0,\r\n manaLeech: 0,\r\n castSpeed: 0,\r\n threat: 100,\r\n parry: 0,\r\n mayhem: 0,\r\n pierce: 0,\r\n curse: 0,\r\n ripple: 0,\r\n bloom: 0,\r\n blaze: 0,\r\n weaken: 0,\r\n fury: 0,\r\n foodHaste: 0,\r\n drinkConcentration: 0,\r\n damageTaken: 0,\r\n attackSpeed: 0,\r\n armorDamageRatio: 0,\r\n hpDrainRatio: 0,\r\n primaryTraining: \"\",\r\n focusTraining: \"\",\r\n staminaExperience: 0,\r\n intelligenceExperience: 0,\r\n attackExperience: 0,\r\n defenseExperience: 0,\r\n meleeExperience: 0,\r\n rangedExperience: 0,\r\n magicExperience: 0,\r\n retaliation: 0,\r\n },\r\n };\r\n combatBuffs = {};\r\n permanentBuffs = {};\r\n zoneBuffs = {};\r\n extraBuffs = {};\r\n\r\n constructor() { }\r\n\r\n updateCombatDetails() {\r\n if (this.isPlayer) {\r\n if (this.combatDetails.combatStats.hpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01 + this.combatDetails.combatStats.hpRegenPer10;\r\n }\r\n if (this.combatDetails.combatStats.mpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01 + this.combatDetails.combatStats.mpRegenPer10;\r\n }\r\n }\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((stat) => {\r\n this.combatDetails[stat + \"Level\"] = this[stat + \"Level\"];\r\n let boosts = this.getBuffBoosts(\"/buff_types/\" + stat + \"_level\");\r\n boosts.forEach((buff) => {\r\n this.combatDetails[stat + \"Level\"] += (this[stat + \"Level\"] * buff.ratioBoost);\r\n this.combatDetails[stat + \"Level\"] += buff.flatBoost;\r\n });\r\n });\r\n\r\n this.combatDetails.maxHitpoints = Math.floor\r\n (10 * (10 + this.combatDetails.staminaLevel) + this.combatDetails.combatStats.maxHitpoints);\r\n this.combatDetails.maxManapoints = Math.floor\r\n (10 * (10 + this.combatDetails.intelligenceLevel) + this.combatDetails.combatStats.maxManapoints);\r\n\r\n let accuracyRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_accuracy\").ratioBoost;\r\n let damageRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_damage\").ratioBoost;\r\n // if (accuracyRatioBoostFromFury > 0) {\r\n // console.log(\"Fury Boost: \" + accuracyRatioBoostFromFury);\r\n // }\r\n\r\n let accuracyRatioBoost = this.getBuffBoost(\"/buff_types/accuracy\").ratioBoost;\r\n let damageRatioBoost = this.getBuffBoost(\"/buff_types/damage\").ratioBoost;\r\n\r\n [\"stab\", \"slash\", \"smash\"].forEach((style) => {\r\n this.combatDetails[style + \"AccuracyRating\"] =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Accuracy\"]) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails[style + \"MaxDamage\"] =\r\n (10 + this.combatDetails.meleeLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Damage\"]) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n let baseEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats[style + \"Evasion\"]);\r\n this.combatDetails[style + \"EvasionRating\"] = baseEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails[style + \"EvasionRating\"] += boost.flatBoost;\r\n this.combatDetails[style + \"EvasionRating\"] += baseEvasion * boost.ratioBoost;\r\n }\r\n });\r\n\r\n this.combatDetails.defensiveMaxDamage = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.defensiveDamage);\r\n\r\n // when equiped bulwark\r\n if (this.equipment?.['/equipment_types/two_hand']?.hrid.includes(\"bulwark\")) {\r\n this.combatDetails.smashMaxDamage += this.combatDetails.defensiveMaxDamage;\r\n }\r\n\r\n this.combatDetails.rangedAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.rangedAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.rangedMaxDamage =\r\n (10 + this.combatDetails.rangedLevel) *\r\n (1 + this.combatDetails.combatStats.rangedDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseRangedEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.rangedEvasion);\r\n this.combatDetails.rangedEvasionRating = baseRangedEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.rangedEvasionRating += boost.flatBoost;\r\n this.combatDetails.rangedEvasionRating += baseRangedEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.damageTaken = this.getBuffBoost(\"/buff_types/damage_taken\").flatBoost;\r\n // if (this.combatDetails.combatStats.damageTaken > 0) {\r\n // console.log(\"Damage taken: \" + this.combatDetails.combatStats.damageTaken);\r\n // }\r\n\r\n this.combatDetails.magicAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.magicAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.magicMaxDamage =\r\n (10 + this.combatDetails.magicLevel) *\r\n (1 + this.combatDetails.combatStats.magicDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseMagicEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.magicEvasion);\r\n this.combatDetails.magicEvasionRating = baseMagicEvasion;\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.magicEvasionRating += boost.flatBoost;\r\n this.combatDetails.magicEvasionRating += baseMagicEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.physicalAmplify += this.getBuffBoost(\"/buff_types/physical_amplify\").flatBoost;\r\n this.combatDetails.combatStats.waterAmplify += this.getBuffBoost(\"/buff_types/water_amplify\").flatBoost;\r\n this.combatDetails.combatStats.natureAmplify += this.getBuffBoost(\"/buff_types/nature_amplify\").flatBoost;\r\n this.combatDetails.combatStats.fireAmplify += this.getBuffBoost(\"/buff_types/fire_amplify\").flatBoost;\r\n\r\n this.combatDetails.combatStats.attackInterval /= (1 + (this.combatDetails.attackLevel / 2000));\r\n\r\n let baseAttackSpeed = this.combatDetails.combatStats.attackSpeed;\r\n this.combatDetails.combatStats.attackInterval /= (1 + baseAttackSpeed);\r\n let attackIntervalBoosts = this.getBuffBoosts(\"/buff_types/attack_speed\");\r\n let attackIntervalRatioBoost = attackIntervalBoosts\r\n .map((boost) => boost.ratioBoost)\r\n .reduce((prev, cur) => prev + cur, 0);\r\n this.combatDetails.combatStats.attackInterval /= (1 + attackIntervalRatioBoost);\r\n\r\n let baseArmor = 0.2 * this.combatDetails.defenseLevel + this.combatDetails.combatStats.armor;\r\n this.combatDetails.totalArmor = baseArmor;\r\n let armorBoosts = this.getBuffBoosts(\"/buff_types/armor\");\r\n for (const boost of armorBoosts) {\r\n this.combatDetails.totalArmor += boost.flatBoost;\r\n this.combatDetails.totalArmor += baseArmor * boost.ratioBoost;\r\n }\r\n\r\n let baseWaterResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.waterResistance;\r\n this.combatDetails.totalWaterResistance = baseWaterResistance;\r\n let waterResistanceBoosts = this.getBuffBoosts(\"/buff_types/water_resistance\");\r\n for (const boost of waterResistanceBoosts) {\r\n this.combatDetails.totalWaterResistance += boost.flatBoost;\r\n this.combatDetails.totalWaterResistance += baseWaterResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseNatureResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.natureResistance;\r\n this.combatDetails.totalNatureResistance = baseNatureResistance;\r\n let natureResistanceBoosts = this.getBuffBoosts(\"/buff_types/nature_resistance\");\r\n for (const boost of natureResistanceBoosts) {\r\n this.combatDetails.totalNatureResistance += boost.flatBoost;\r\n this.combatDetails.totalNatureResistance += baseNatureResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseFireResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.fireResistance;\r\n this.combatDetails.totalFireResistance = baseFireResistance;\r\n let fireResistanceBoosts = this.getBuffBoosts(\"/buff_types/fire_resistance\");\r\n for (const boost of fireResistanceBoosts) {\r\n this.combatDetails.totalFireResistance += boost.flatBoost;\r\n this.combatDetails.totalFireResistance += baseFireResistance * boost.ratioBoost;\r\n }\r\n\r\n let hpRegenBoosts = this.getBuffBoost(\"/buff_types/hp_regen\");\r\n this.combatDetails.combatStats.hpRegenPer10 += this.combatDetails.combatStats.hpRegenPer10 * hpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.hpRegenPer10 += hpRegenBoosts.flatBoost;\r\n\r\n let mpRegenBoosts = this.getBuffBoost(\"/buff_types/mp_regen\");\r\n this.combatDetails.combatStats.mpRegenPer10 += this.combatDetails.combatStats.mpRegenPer10 * mpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.mpRegenPer10 += mpRegenBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.lifeSteal += this.getBuffBoost(\"/buff_types/life_steal\").flatBoost;\r\n this.combatDetails.combatStats.physicalThorns += this.getBuffBoost(\r\n \"/buff_types/physical_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.elementalThorns += this.getBuffBoost(\r\n \"/buff_types/elemental_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.combatExperience += this.getBuffBoost(\"/buff_types/wisdom\").flatBoost;\r\n this.combatDetails.combatStats.criticalRate += this.getBuffBoost(\"/buff_types/critical_rate\").flatBoost;\r\n this.combatDetails.combatStats.criticalDamage += this.getBuffBoost(\"/buff_types/critical_damage\").flatBoost;\r\n\r\n this.combatDetails.combatStats.castSpeed += this.getBuffBoost(\"/buff_types/cast_speed\").flatBoost;\r\n this.combatDetails.combatStats.castSpeed += this.combatDetails[\"attackLevel\"] / 2000;\r\n\r\n let combatDropRateBoosts = this.getBuffBoost(\"/buff_types/combat_drop_rate\");\r\n this.combatDetails.combatStats.combatDropRate += (1 + this.combatDetails.combatStats.combatDropRate) * combatDropRateBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropRate += combatDropRateBoosts.flatBoost;\r\n let combatRareFindBoosts = this.getBuffBoost(\"/buff_types/rare_find\");\r\n this.combatDetails.combatStats.combatRareFind += (1 + this.combatDetails.combatStats.combatRareFind) * combatRareFindBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatRareFind += combatRareFindBoosts.flatBoost;\r\n let combatDropQuantityBoosts = this.getBuffBoost(\"/buff_types/combat_drop_quantity\");\r\n this.combatDetails.combatStats.combatDropQuantity += (1 + this.combatDetails.combatStats.combatDropQuantity) * combatDropQuantityBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropQuantity += combatDropQuantityBoosts.flatBoost;\r\n\r\n let baseThreat = 100 + this.combatDetails.combatStats.threat;\r\n this.combatDetails.totalThreat = baseThreat;\r\n let threatBoosts = this.getBuffBoost(\"/buff_types/threat\");\r\n if (threatBoosts.ratioBoost !== 0) {\r\n this.combatDetails.combatStats.threat += baseThreat * threatBoosts.ratioBoost;\r\n } else {\r\n this.combatDetails.combatStats.threat = baseThreat;\r\n }\r\n this.combatDetails.combatStats.threat += threatBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.retaliation += this.getBuffBoost(\"/buff_types/retaliation\").flatBoost;\r\n }\r\n\r\n addBuff(buff, currentTime) {\r\n buff.startTime = currentTime;\r\n this.combatBuffs[buff.uniqueHrid] = buff;\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n removeBuff(buff) {\r\n if (!this.combatBuffs[buff.uniqueHrid]) {\r\n return;\r\n }\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n addPermanentBuff(buff) {\r\n if (this.permanentBuffs[buff.typeHrid]) {\r\n this.permanentBuffs[buff.typeHrid].flatBoost += buff.flatBoost;\r\n this.permanentBuffs[buff.typeHrid].ratioBoost += buff.ratioBoost;\r\n } else {\r\n this.permanentBuffs[buff.typeHrid] = buff;\r\n }\r\n }\r\n\r\n generatePermanentBuffs() {\r\n for (let i = 0; i < this.houseRooms.length; i++) {\r\n const houseRoom = this.houseRooms[i];\r\n houseRoom.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n\r\n if (this.achievements) {\r\n this.achievements.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.zoneBuffs) {\r\n this.zoneBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.extraBuffs) {\r\n this.extraBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n }\r\n\r\n removeExpiredBuffs(currentTime) {\r\n let expiredBuffs = Object.values(this.combatBuffs).filter(\r\n (buff) => buff.startTime + buff.duration <= currentTime\r\n );\r\n expiredBuffs.forEach((buff) => {\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n });\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearBuffs() {\r\n this.combatBuffs = structuredClone(this.permanentBuffs);\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearCCs() {\r\n this.isStunned = false;\r\n this.stunExpireTime = null;\r\n this.isSilenced = false;\r\n this.silenceExpireTime = null;\r\n this.isBlinded = false;\r\n this.blindExpireTime = null;\r\n this.combatDetails.combatStats.damageTaken = 0;\r\n }\r\n\r\n getBuffBoosts(type) {\r\n let boosts = [];\r\n Object.values(this.combatBuffs)\r\n .filter((buff) => buff.typeHrid == type)\r\n .forEach((buff) => {\r\n boosts.push({ ratioBoost: buff.ratioBoost, flatBoost: buff.flatBoost });\r\n });\r\n\r\n return boosts;\r\n }\r\n\r\n getBuffBoost(type) {\r\n let boosts = this.getBuffBoosts(type);\r\n\r\n let boost = {\r\n ratioBoost: 0,\r\n flatBoost: 0,\r\n };\r\n\r\n for (let i = 0; i < boosts.length; i++) {\r\n boost.ratioBoost += boosts[i]?.ratioBoost ?? 0;\r\n boost.flatBoost += boosts[i]?.flatBoost ?? 0;\r\n }\r\n\r\n return boost;\r\n }\r\n\r\n reset(currentTime = 0) {\r\n this.clearCCs();\r\n this.clearBuffs();\r\n this.updateCombatDetails();\r\n this.resetCooldowns(currentTime);\r\n\r\n this.combatDetails.currentHitpoints = this.combatDetails.maxHitpoints;\r\n this.combatDetails.currentManapoints = this.combatDetails.maxManapoints;\r\n }\r\n\r\n resetCooldowns(currentTime = 0) {\r\n this.food.filter((food) => food != null).forEach((food) => (food.lastUsed = Number.MIN_SAFE_INTEGER));\r\n this.drinks.filter((drink) => drink != null).forEach((drink) => (drink.lastUsed = Number.MIN_SAFE_INTEGER));\r\n\r\n let haste = this.combatDetails.combatStats.abilityHaste;\r\n\r\n this.abilities\r\n .filter((ability) => ability != null)\r\n .forEach((ability) => {\r\n if (this.isPlayer) {\r\n ability.lastUsed = Number.MIN_SAFE_INTEGER;\r\n } else {\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n ability.lastUsed = currentTime - Math.floor(cooldownDuration * 0.5) + Math.floor(Math.random() * cooldownDuration * 0.5);\r\n }\r\n });\r\n }\r\n\r\n addHitpoints(hitpoints) {\r\n let hitpointsAdded = 0;\r\n\r\n if (this.combatDetails.currentHitpoints >= this.combatDetails.maxHitpoints) {\r\n return hitpointsAdded;\r\n }\r\n\r\n let newHitpoints = Math.min(this.combatDetails.currentHitpoints + hitpoints, this.combatDetails.maxHitpoints);\r\n hitpointsAdded = newHitpoints - this.combatDetails.currentHitpoints;\r\n this.combatDetails.currentHitpoints = newHitpoints;\r\n\r\n return hitpointsAdded;\r\n }\r\n\r\n addManapoints(manapoints) {\r\n let manapointsAdded = 0;\r\n\r\n if (this.combatDetails.currentManapoints >= this.combatDetails.maxManapoints) {\r\n return manapointsAdded;\r\n }\r\n\r\n let newManapoints = Math.min(\r\n this.combatDetails.currentManapoints + manapoints,\r\n this.combatDetails.maxManapoints\r\n );\r\n manapointsAdded = newManapoints - this.combatDetails.currentManapoints;\r\n this.combatDetails.currentManapoints = newManapoints;\r\n\r\n return manapointsAdded;\r\n }\r\n}\r\n\r\nexport default CombatUnit;\r\n","import Buff from \"./buff\";\r\nimport itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nclass Consumable {\r\n constructor(hrid, triggers = null) {\r\n this.hrid = hrid;\r\n\r\n let gameConsumable = itemDetailMap[this.hrid];\r\n if (!gameConsumable) {\r\n throw new Error(\"No consumable found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.cooldownDuration = gameConsumable.consumableDetail.cooldownDuration;\r\n this.hitpointRestore = gameConsumable.consumableDetail.hitpointRestore;\r\n this.manapointRestore = gameConsumable.consumableDetail.manapointRestore;\r\n this.recoveryDuration = gameConsumable.consumableDetail.recoveryDuration;\r\n this.catagoryHrid = gameConsumable.categoryHrid;\r\n\r\n this.buffs = [];\r\n if (gameConsumable.consumableDetail.buffs) {\r\n for (const consumableBuff of gameConsumable.consumableDetail.buffs) {\r\n let buff = new Buff(consumableBuff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameConsumable.consumableDetail.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let consumable = new Consumable(dto.hrid, triggers);\r\n\r\n return consumable;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n let consumableHaste;\r\n if (this.catagoryHrid.includes(\"food\")) {\r\n consumableHaste = source.combatDetails.combatStats.foodHaste\r\n } else {\r\n consumableHaste = source.combatDetails.combatStats.drinkConcentration;\r\n }\r\n let cooldownDuration = this.cooldownDuration;\r\n if (consumableHaste > 0) {\r\n cooldownDuration = cooldownDuration / (1 + consumableHaste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Consumable;\r\n","import itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport enhancementLevelTotalMultiplierTable from \"./data/enhancementLevelTotalBonusMultiplierTable.json\";\r\n\r\nclass Equipment {\r\n constructor(hrid, enhancementLevel) {\r\n this.hrid = hrid;\r\n let gameItem = itemDetailMap[this.hrid];\r\n if (!gameItem) {\r\n throw new Error(\"No equipment found for hrid: \" + this.hrid);\r\n }\r\n this.gameItem = gameItem;\r\n this.enhancementLevel = enhancementLevel;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let equipment = new Equipment(dto.hrid, dto.enhancementLevel);\r\n\r\n return equipment;\r\n }\r\n\r\n getCombatStat(combatStat) {\r\n let multiplier = enhancementLevelTotalMultiplierTable[this.enhancementLevel];\r\n if(this.gameItem.equipmentDetail.combatStats[combatStat]) {\r\n let enhancementBonus = this.gameItem.equipmentDetail.combatEnhancementBonuses[combatStat] || 0;\r\n let stat = this.gameItem.equipmentDetail.combatStats[combatStat] + multiplier * enhancementBonus;\r\n return stat;\r\n }\r\n return 0;\r\n }\r\n\r\n getCombatStyle() {\r\n return this.gameItem.equipmentDetail.combatStats.combatStyleHrids[0];\r\n }\r\n\r\n getDamageType() {\r\n return this.gameItem.equipmentDetail.combatStats.damageType;\r\n }\r\n\r\n getPrimaryTraining() {\r\n return this.gameItem.equipmentDetail.combatStats.primaryTraining;\r\n }\r\n\r\n getFocusTraining(){\r\n return this.gameItem.equipmentDetail.combatStats.focusTraining;\r\n }\r\n}\r\n\r\nexport default Equipment;\r\n","import Buff from \"./buff\";\r\nimport houseRoomDetailMap from \"./data/houseRoomDetailMap.json\";\r\n\r\nclass HouseRoom {\r\n constructor(hrid, level) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameHouseRoom = houseRoomDetailMap[this.hrid];\r\n if (!gameHouseRoom) {\r\n throw new Error(\"No house room found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.buffs = [];\r\n if (gameHouseRoom.actionBuffs) {\r\n for (const actionBuff of gameHouseRoom.actionBuffs) {\r\n let buff = new Buff(actionBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n if (gameHouseRoom.globalBuffs) {\r\n for (const globalBuff of gameHouseRoom.globalBuffs) {\r\n let buff = new Buff(globalBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default HouseRoom;","import Ability from \"./ability\";\r\nimport CombatUnit from \"./combatUnit\";\r\nimport Consumable from \"./consumable\";\r\nimport Equipment from \"./equipment\";\r\nimport HouseRoom from \"./houseRoom\";\r\nimport Achievement from \"./achievement\";\r\n\r\nclass Player extends CombatUnit {\r\n equipment = {\r\n \"/equipment_types/head\": null,\r\n \"/equipment_types/body\": null,\r\n \"/equipment_types/legs\": null,\r\n \"/equipment_types/feet\": null,\r\n \"/equipment_types/hands\": null,\r\n \"/equipment_types/main_hand\": null,\r\n \"/equipment_types/two_hand\": null,\r\n \"/equipment_types/off_hand\": null,\r\n \"/equipment_types/pouch\": null,\r\n \"/equipment_types/back\": null,\r\n };\r\n\r\n constructor() {\r\n super();\r\n\r\n this.isPlayer = true;\r\n this.hrid = \"player\";\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let player = new Player();\r\n\r\n player.staminaLevel = dto.staminaLevel;\r\n player.intelligenceLevel = dto.intelligenceLevel;\r\n player.attackLevel = dto.attackLevel;\r\n player.meleeLevel = dto.meleeLevel;\r\n player.defenseLevel = dto.defenseLevel;\r\n player.rangedLevel = dto.rangedLevel;\r\n player.magicLevel = dto.magicLevel;\r\n\r\n player.hrid = dto.hrid;\r\n\r\n for (const [key, value] of Object.entries(dto.equipment)) {\r\n player.equipment[key] = value ? Equipment.createFromDTO(value) : null;\r\n }\r\n\r\n player.food = dto.food.map((food) => (food ? Consumable.createFromDTO(food) : null));\r\n player.drinks = dto.drinks.map((drink) => (drink ? Consumable.createFromDTO(drink) : null));\r\n player.abilities = dto.abilities.map((ability) => (ability ? Ability.createFromDTO(ability) : null));\r\n Object.entries(dto.houseRooms).forEach(houseRoom => {\r\n if (houseRoom[1] > 0) {\r\n player.houseRooms.push(new HouseRoom(houseRoom[0], houseRoom[1]))\r\n }\r\n });\r\n\r\n player.achievements = new Achievement(dto.achievements);\r\n\r\n player.debuffOnLevelGap = dto.debuffOnLevelGap;\r\n\r\n return player;\r\n }\r\n\r\n updateCombatDetails() {\r\n if (this.equipment[\"/equipment_types/main_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/main_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/main_hand\"].getPrimaryTraining();\r\n } else if (this.equipment[\"/equipment_types/two_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/two_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/two_hand\"].getPrimaryTraining();\r\n } else {\r\n this.combatDetails.combatStats.combatStyleHrid = \"/combat_styles/smash\";\r\n this.combatDetails.combatStats.damageType = \"/damage_types/physical\";\r\n this.combatDetails.combatStats.attackInterval = 3000000000;\r\n this.combatDetails.combatStats.primaryTraining = \"/skills/melee\";\r\n }\r\n\r\n if (this.equipment[\"/equipment_types/charm\"]) {\r\n this.combatDetails.combatStats.focusTraining = this.equipment[\"/equipment_types/charm\"].getFocusTraining();\r\n } else {\r\n this.combatDetails.combatStats.focusTraining = \"\";\r\n }\r\n\r\n [\r\n \"stabAccuracy\",\r\n \"slashAccuracy\",\r\n \"smashAccuracy\",\r\n \"rangedAccuracy\",\r\n \"magicAccuracy\",\r\n \"stabDamage\",\r\n \"slashDamage\",\r\n \"smashDamage\",\r\n \"rangedDamage\",\r\n \"magicDamage\",\r\n \"defensiveDamage\",\r\n \"taskDamage\",\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"stabEvasion\",\r\n \"slashEvasion\",\r\n \"smashEvasion\",\r\n \"rangedEvasion\",\r\n \"magicEvasion\",\r\n \"armor\",\r\n \"waterResistance\",\r\n \"natureResistance\",\r\n \"fireResistance\",\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"combatDropRate\",\r\n \"combatRareFind\",\r\n \"combatDropQuantity\",\r\n \"combatExperience\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"abilityHaste\",\r\n \"tenacity\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"threat\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"foodHaste\",\r\n \"drinkConcentration\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\",\r\n \"retaliation\"\r\n ].forEach((stat) => {\r\n this.combatDetails.combatStats[stat] = Object.values(this.equipment)\r\n .filter((equipment) => equipment != null)\r\n .map((equipment) => equipment.getCombatStat(stat))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n });\r\n\r\n if (this.equipment[\"/equipment_types/pouch\"]) {\r\n this.combatDetails.combatStats.foodSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"foodSlots\");\r\n this.combatDetails.combatStats.drinkSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"drinkSlots\");\r\n } else {\r\n this.combatDetails.combatStats.foodSlots = 1;\r\n this.combatDetails.combatStats.drinkSlots = 1;\r\n }\r\n\r\n super.updateCombatDetails();\r\n }\r\n}\r\n\r\nexport default Player;\r\n","import combatTriggerDependencyDetailMap from \"./data/combatTriggerDependencyDetailMap.json\";\r\n\r\nclass Trigger {\r\n constructor(dependencyHrid, conditionHrid, comparatorHrid, value = 0) {\r\n this.dependencyHrid = dependencyHrid;\r\n this.conditionHrid = conditionHrid;\r\n this.comparatorHrid = comparatorHrid;\r\n this.value = value;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let trigger = new Trigger(dto.dependencyHrid, dto.conditionHrid, dto.comparatorHrid, dto.value);\r\n\r\n return trigger;\r\n }\r\n\r\n isActive(source, target, friendlies, enemies, currentTime) {\r\n if (combatTriggerDependencyDetailMap[this.dependencyHrid].isSingleTarget) {\r\n return this.isActiveSingleTarget(source, target, currentTime);\r\n } else {\r\n return this.isActiveMultiTarget(friendlies, enemies, currentTime);\r\n }\r\n }\r\n\r\n isActiveSingleTarget(source, target, currentTime) {\r\n let dependencyValue;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/self\":\r\n dependencyValue = this.getDependencyValue(source, currentTime);\r\n break;\r\n case \"/combat_trigger_dependencies/targeted_enemy\":\r\n if (!target) {\r\n return false;\r\n }\r\n dependencyValue = this.getDependencyValue(target, currentTime);\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n isActiveMultiTarget(friendlies, enemies, currentTime) {\r\n let dependency;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/all_allies\":\r\n dependency = friendlies;\r\n break;\r\n case \"/combat_trigger_dependencies/all_enemies\":\r\n if (!enemies) {\r\n return false;\r\n }\r\n dependency = enemies;\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n let dependencyValue;\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/number_of_active_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints > 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/number_of_dead_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints <= 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/lowest_hp_percentage\":\r\n dependencyValue = dependency.reduce((prev, curr) => {\r\n let currentHpPercentage = curr.combatDetails.currentHitpoints / curr.combatDetails.maxHitpoints;\r\n return currentHpPercentage < prev ? currentHpPercentage : prev;\r\n }, 2) * 100;\r\n break;\r\n default:\r\n dependencyValue = dependency\r\n .map((unit) => this.getDependencyValue(unit, currentTime))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n break;\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n getDependencyValue(source, currentTime) {\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/berserk\":\r\n case \"/combat_trigger_conditions/frenzy\":\r\n case \"/combat_trigger_conditions/precision\":\r\n case \"/combat_trigger_conditions/vampirism\":\r\n case \"/combat_trigger_conditions/attack_coffee\":\r\n case \"/combat_trigger_conditions/defense_coffee\":\r\n case \"/combat_trigger_conditions/lucky_coffee\":\r\n case \"/combat_trigger_conditions/magic_coffee\":\r\n case \"/combat_trigger_conditions/melee_coffee\":\r\n case \"/combat_trigger_conditions/ranged_coffee\":\r\n case \"/combat_trigger_conditions/swiftness_coffee\":\r\n case \"/combat_trigger_conditions/wisdom_coffee\":\r\n case \"/combat_trigger_conditions/ice_spear\":\r\n case \"/combat_trigger_conditions/puncture\":\r\n case \"/combat_trigger_conditions/frost_surge\":\r\n case \"/combat_trigger_conditions/elusiveness\":\r\n case \"/combat_trigger_conditions/channeling_coffee\":\r\n case \"/combat_trigger_conditions/fierce_aura\":\r\n case \"/combat_trigger_conditions/invincible_armor\":\r\n case \"/combat_trigger_conditions/invincible_fire_resistance\":\r\n case \"/combat_trigger_conditions/invincible_nature_resistance\":\r\n case \"/combat_trigger_conditions/invincible_water_resistance\":\r\n case \"/combat_trigger_conditions/provoke\":\r\n case \"/combat_trigger_conditions/taunt\":\r\n case \"/combat_trigger_conditions/crippling_slash\":\r\n case \"/combat_trigger_conditions/mana_spring\":\r\n case \"/combat_trigger_conditions/retribution\":\r\n case \"/combat_trigger_conditions/fracturing_impact\":\r\n case \"/combat_trigger_conditions/maim\":\r\n case \"/combat_trigger_conditions/curse\":\r\n case \"/combat_trigger_conditions/weaken\":\r\n let buffHrid = \"/buff_uniques\";\r\n buffHrid += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n return source.combatBuffs[buffHrid];\r\n case \"/combat_trigger_conditions/critical_aura\":\r\n case \"/combat_trigger_conditions/critical_coffee\":\r\n case \"/combat_trigger_conditions/intelligence_coffee\":\r\n case \"/combat_trigger_conditions/stamina_coffee\":\r\n case \"/combat_trigger_conditions/elemental_affinity\":\r\n case \"/combat_trigger_conditions/fury\":\r\n case \"/combat_trigger_conditions/guardian_aura\":\r\n case \"/combat_trigger_conditions/insanity\":\r\n case \"/combat_trigger_conditions/spike_shell\":\r\n case \"/combat_trigger_conditions/toxic_pollen\":\r\n case \"/combat_trigger_conditions/invincible\":\r\n case \"/combat_trigger_conditions/mystic_aura\":\r\n case \"/combat_trigger_conditions/pestilent_shot\":\r\n case \"/combat_trigger_conditions/smoke_burst\":\r\n case \"/combat_trigger_conditions/speed_aura\":\r\n case \"/combat_trigger_conditions/toughness\":\r\n case \"/combat_trigger_conditions/enrage\":\r\n let buffPrefix = \"/buff_uniques\";\r\n buffPrefix += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n let buffs = Object.keys(source.combatBuffs).filter(buff => buff.startsWith(buffPrefix));\r\n return source.combatBuffs[buffs?.[0]];\r\n case \"/combat_trigger_conditions/current_hp\":\r\n return source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/current_mp\":\r\n return source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/missing_hp\":\r\n return source.combatDetails.maxHitpoints - source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/missing_mp\":\r\n return source.combatDetails.maxManapoints - source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/stun_status\":\r\n // Replicate the game's behaviour of \"stun status active\" triggers activating\r\n // immediately after the stun has worn off\r\n return source.isStunned || source.stunExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/blind_status\":\r\n return source.isBlinded || source.blindExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/silence_status\":\r\n return source.isSilenced || source.silenceExpireTime == currentTime;\r\n default:\r\n throw new Error(\"Unknown conditionHrid in trigger: \" + this.conditionHrid);\r\n }\r\n }\r\n\r\n compareValue(dependencyValue) {\r\n switch (this.comparatorHrid) {\r\n case \"/combat_trigger_comparators/greater_than_equal\":\r\n return dependencyValue >= this.value;\r\n case \"/combat_trigger_comparators/less_than_equal\":\r\n return dependencyValue <= this.value;\r\n case \"/combat_trigger_comparators/is_active\":\r\n return !!dependencyValue;\r\n case \"/combat_trigger_comparators/is_inactive\":\r\n return !dependencyValue;\r\n default:\r\n throw new Error(\"Unknown comparatorHrid in trigger: \" + this.comparatorHrid);\r\n }\r\n }\r\n}\r\n\r\nexport default Trigger;\r\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","// This function allow to reference async chunks\n__webpack_require__.u = (chunkId) => {\n\t// return url for filenames based on template\n\treturn \"\" + chunkId + \".bundle.js\";\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","var scriptUrl;\nif (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + \"\";\nvar document = __webpack_require__.g.document;\nif (!scriptUrl && document) {\n\tif (document.currentScript)\n\t\tscriptUrl = document.currentScript.src;\n\tif (!scriptUrl) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tif(scripts.length) {\n\t\t\tvar i = scripts.length - 1;\n\t\t\twhile (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;\n\t\t}\n\t}\n}\n// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration\n// or pass an empty string (\"\") and set the __webpack_public_path__ variable from your code to use your own logic.\nif (!scriptUrl) throw new Error(\"Automatic publicPath is not supported in this browser\");\nscriptUrl = scriptUrl.replace(/#.*$/, \"\").replace(/\\?.*$/, \"\").replace(/\\/[^\\/]+$/, \"/\");\n__webpack_require__.p = scriptUrl;","__webpack_require__.b = document.baseURI || self.location.href;\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t\"main\": 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n// no on chunks loaded\n\n// no jsonp function","import Equipment from \"./combatsimulator/equipment.js\";\r\nimport Player from \"./combatsimulator/player.js\";\r\nimport abilityDetailMap from \"./combatsimulator/data/abilityDetailMap.json\";\r\nimport itemDetailMap from \"./combatsimulator/data/itemDetailMap.json\";\r\nimport houseRoomDetailMap from \"./combatsimulator/data/houseRoomDetailMap.json\";\r\nimport Ability from \"./combatsimulator/ability.js\";\r\nimport Consumable from \"./combatsimulator/consumable.js\";\r\nimport HouseRoom from \"./combatsimulator/houseRoom\"\r\nimport combatTriggerDependencyDetailMap from \"./combatsimulator/data/combatTriggerDependencyDetailMap.json\";\r\nimport combatTriggerConditionDetailMap from \"./combatsimulator/data/combatTriggerConditionDetailMap.json\";\r\nimport combatTriggerComparatorDetailMap from \"./combatsimulator/data/combatTriggerComparatorDetailMap.json\";\r\nimport abilitySlotsLevelRequirementList from \"./combatsimulator/data/abilitySlotsLevelRequirementList.json\";\r\nimport actionDetailMap from \"./combatsimulator/data/actionDetailMap.json\";\r\nimport combatMonsterDetailMap from \"./combatsimulator/data/combatMonsterDetailMap.json\";\r\nimport damageTypeDetailMap from \"./combatsimulator/data/damageTypeDetailMap.json\";\r\nimport combatStyleDetailMap from \"./combatsimulator/data/combatStyleDetailMap.json\";\r\nimport openableLootDropMap from \"./combatsimulator/data/openableLootDropMap.json\";\r\nimport achievementTierMap from \"./combatsimulator/data/achievementTierDetailMap.json\"\r\nimport achievementDetailMap from \"./combatsimulator/data/achievementDetailMap.json\"\r\n\r\nimport patchNote from \"../patchNote.json\";\r\n\r\nconst ONE_SECOND = 1e9;\r\nconst ONE_HOUR = 60 * 60 * ONE_SECOND;\r\n\r\nlet buttonStartSimulation = document.getElementById(\"buttonStartSimulation\");\r\nlet buttonStopSimulation = document.getElementById(\"buttonStopSimulation\");\r\nlet progressbar = document.getElementById(\"simulationProgressBar\");\r\nlet simStartTime = 0;\r\n\r\nlet worker = new Worker(new URL(\"worker.js\", import.meta.url));\r\nlet multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n\r\n\r\n\r\nlet player = new Player();\r\nlet selectedPlayers = [];\r\nlet food = [null, null, null];\r\nlet drinks = [null, null, null];\r\nlet abilities = [null, null, null, null];\r\nlet triggerMap = {};\r\nlet modalTriggers = [];\r\nlet currentSimResults = {};\r\n\r\nlet currentPlayerTabId = '1';\r\nlet playerDataMap = {\r\n \"1\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},achievements:{}}\",\r\n \"2\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},achievements:{}}\",\r\n \"3\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},achievements:{}}\",\r\n \"4\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},achievements:{}}\",\r\n \"5\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},achievements:{}}\"\r\n};\r\nwindow.revenue = 0;\r\nwindow.noRngRevenue = 0;\r\nwindow.expenses = 0;\r\nwindow.profit = 0;\r\nwindow.noRngProfit = 0;\r\n\r\n// #region Worker\r\n\r\nfunction onWorkerMessage(event) {\r\n switch (event.data.type) {\r\n case \"simulation_result\":\r\n progressbar.style.width = \"100%\";\r\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n //console.log(\"SIM RESULTS: \", event.data.simResult);\r\n showSimulationResult(event.data.simResult);\r\n updateContent();\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n document.getElementById('buttonShowAllSimData').style.display = 'none';\r\n break;\r\n case \"simulation_progress\":\r\n let progress = Math.floor(100 * event.data.progress);\r\n progressbar.style.width = progress + \"%\";\r\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n break;\r\n case \"simulation_error\":\r\n showErrorModal(event.data.error.toString());\r\n break;\r\n }\r\n};\r\n\r\nfunction onMultiWorkerMessage(event) {\r\n switch (event.data.type) {\r\n case \"simulation_result_allZones\":\r\n progressbar.style.width = \"100%\";\r\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n showAllSimulationResults(event.data.simResults);\r\n updateContent();\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n document.getElementById('buttonShowAllSimData').style.display = 'block';\r\n break;\r\n case \"simulation_progress\":\r\n let progress = Math.floor(100 * event.data.progress);\r\n progressbar.style.width = progress + \"%\";\r\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n break;\r\n case \"simulation_error\":\r\n showErrorModal(event.data.error.toString());\r\n break;\r\n }\r\n};\r\n\r\n// #endregion\r\n\r\n// #region Equipment\r\n\r\nfunction initEquipmentSection() {\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n initEquipmentSelect(type);\r\n initEnhancementLevelInput(type);\r\n });\r\n}\r\n\r\nfunction initEquipmentSelect(equipmentType) {\r\n let selectId = \"selectEquipment_\";\r\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\r\n selectId += \"weapon\";\r\n } else {\r\n selectId += equipmentType;\r\n }\r\n let selectElement = document.getElementById(selectId);\r\n\r\n let gameEquipment = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/equipment\")\r\n .filter((item) => item.equipmentDetail.type == \"/equipment_types/\" + equipmentType)\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const equipment of Object.values(gameEquipment)) {\r\n let opt = new Option(equipment.name, equipment.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + equipment.hrid);\r\n selectElement.add(opt);\r\n }\r\n\r\n selectElement.addEventListener(\"change\", (event) => {\r\n equipmentSelectHandler(event, equipmentType);\r\n });\r\n}\r\n\r\nfunction initHouseRoomsModal() {\r\n let houseRoomsList = document.getElementById(\"houseRoomsList\");\r\n let newChildren = [];\r\n let houseRooms = Object.values(houseRoomDetailMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n player.houseRooms = {};\r\n\r\n for (const room of Object.values(houseRooms)) {\r\n player.houseRooms[room.hrid] = 0;\r\n\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let nameCol = createElement(\"div\", \"col-md-4 offset-md-3 align-self-center\", room.name);\r\n nameCol.setAttribute(\"data-i18n\", \"houseRoomNames.\" + room.hrid);\r\n row.appendChild(nameCol);\r\n\r\n let levelCol = createElement(\"div\", \"col-md-2\");\r\n let levelInput = createHouseInput(room.hrid);\r\n\r\n levelInput.addEventListener(\"input\", function (e) {\r\n let inputValue = e.target.value;\r\n const hrid = e.target.dataset.houseHrid;\r\n player.houseRooms[hrid] = parseInt(inputValue);\r\n });\r\n\r\n levelCol.appendChild(levelInput);\r\n row.appendChild(levelCol);\r\n\r\n newChildren.push(row);\r\n }\r\n\r\n houseRoomsList.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction createHouseInput(hrid) {\r\n let levelInput = document.createElement(\"input\");\r\n levelInput.className = \"form-control\";\r\n levelInput.type = \"number\";\r\n levelInput.placeholder = 0;\r\n levelInput.min = 0;\r\n levelInput.max = 8;\r\n levelInput.step = 1;\r\n levelInput.dataset.houseHrid = hrid;\r\n\r\n return levelInput;\r\n}\r\n\r\nfunction refreshAchievementStatics() {\r\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n for(const tier of Object.values(tierMap)) {\r\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\r\n const done = Array.from(checks).filter(cb => cb.checked).length;\r\n const total = checks.length;\r\n\r\n const stat = document.getElementById(`AchTier${tier.sortIndex}Statics`);\r\n stat.innerText = `(${done}/${total})`;\r\n if (done == total) {\r\n // set to green\r\n stat.classList.remove(\"text-secondary\");\r\n stat.classList.add(\"text-success\");\r\n } else {\r\n // set to secondary\r\n stat.classList.remove(\"text-success\");\r\n stat.classList.add(\"text-secondary\");\r\n }\r\n }\r\n}\r\n\r\nfunction initAchievementsModal(){\r\n let achievementsList = document.getElementById(\"achievementsList\");\r\n let newChildren = [];\r\n player.achievements = {};\r\n\r\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n for(const tier of Object.values(tierMap)) {\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid).sort((a, b) => a.sortIndex - b.sortIndex);\r\n let detailMapCount = detailMap.length;\r\n if (detailMapCount <= 0) continue;\r\n\r\n let card = createElement(\"div\", \"card\");\r\n let cardHeader = createElement(\"div\", \"card-header d-flex align-items-center\");\r\n\r\n let cardTitle = createElement(\"a\", \"btn\", tier.name);\r\n cardTitle.setAttribute(\"data-bs-toggle\",\"collapse\");\r\n cardTitle.setAttribute(\"href\", `#AchTier${tier.sortIndex}`);\r\n cardTitle.setAttribute(\"data-i18n\", \"achievementTierNames.\"+tier.hrid);\r\n cardHeader.appendChild(cardTitle);\r\n\r\n let bufDesc = createElement(\"div\", \"small text-secondary\");\r\n let buffName = createElement(\"i\", \"\");\r\n buffName.setAttribute(\"data-i18n\", \"buffTypeNames.\"+tier[\"buff\"].typeHrid);\r\n bufDesc.appendChild(buffName);\r\n let buffValue = createElement(\"i\", \"\");\r\n buffValue.innerText = \":+\" + parseFloat(tier[\"buff\"].ratioBoost==0?tier[\"buff\"].flatBoost:tier[\"buff\"].ratioBoost)*100 + \"%\";\r\n bufDesc.appendChild(buffValue);\r\n cardHeader.appendChild(bufDesc);\r\n\r\n let cardStatics = createElement(\"div\", \"ms-auto\", `(0/${detailMapCount})`);\r\n cardStatics.id = `AchTier${tier.sortIndex}Statics`;\r\n cardHeader.appendChild(cardStatics);\r\n\r\n card.appendChild(cardHeader);\r\n\r\n let cardMain = createElement(\"div\", \"collapse\");\r\n cardMain.id = `AchTier${tier.sortIndex}`;\r\n let cardBody = createElement(\"div\", \"card-body\");\r\n\r\n for (const detail of Object.values(detailMap)) {\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let formCheck = createElement(\"div\", \"form-check\");\r\n let input = createElement(\"input\", \"form-check-input\");\r\n input.setAttribute(\"type\", \"checkbox\");\r\n input.setAttribute(\"data-tier\", tier.sortIndex);\r\n input.id = `AchDetail${detail.sortIndex}`;\r\n input.dataset.achievementHrid = detail.hrid;\r\n input.addEventListener(\"change\", function (e) {\r\n const hrid = e.target.dataset.achievementHrid;\r\n player.achievements[hrid] = e.target.checked;\r\n\r\n refreshAchievementStatics();\r\n });\r\n formCheck.appendChild(input);\r\n\r\n let name = createElement(\"label\", \"form-check-label\", detail.name);\r\n name.setAttribute(\"data-i18n\", \"achievementNames.\" + detail.hrid);\r\n name.setAttribute(\"for\", `AchDetail${detail.sortIndex}`);\r\n formCheck.appendChild(name);\r\n row.appendChild(formCheck);\r\n cardBody.appendChild(row);\r\n }\r\n cardMain.appendChild(cardBody);\r\n card.appendChild(cardMain);\r\n\r\n newChildren.push(card);\r\n }\r\n\r\n achievementsList.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction initEnhancementLevelInput(equipmentType) {\r\n let inputId = \"inputEquipmentEnhancementLevel_\";\r\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\r\n inputId += \"weapon\";\r\n } else {\r\n inputId += equipmentType;\r\n }\r\n\r\n let inputElement = document.getElementById(inputId);\r\n inputElement.value = 0;\r\n inputElement.addEventListener(\"change\", enhancementLevelInputHandler);\r\n}\r\n\r\nfunction equipmentSelectHandler(event, type) {\r\n let equipmentType = \"/equipment_types/\" + type;\r\n\r\n if (!event.target.value) {\r\n updateEquipmentState();\r\n updateUI();\r\n return;\r\n }\r\n\r\n let gameItem = itemDetailMap[event.target.value];\r\n\r\n // Weapon select has two handlers because of mainhand and twohand weapons. Ignore the handler with the wrong type\r\n if (gameItem.equipmentDetail.type != equipmentType) {\r\n return;\r\n }\r\n\r\n if (type == \"two_hand\") {\r\n document.getElementById(\"selectEquipment_off_hand\").value = \"\";\r\n document.getElementById(\"inputEquipmentEnhancementLevel_off_hand\").value = 0;\r\n }\r\n if (type == \"off_hand\" && player.equipment[\"/equipment_types/two_hand\"]) {\r\n document.getElementById(\"selectEquipment_weapon\").value = \"\";\r\n document.getElementById(\"inputEquipmentEnhancementLevel_weapon\").value = 0;\r\n }\r\n\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\nfunction enhancementLevelInputHandler() {\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\nfunction updateEquipmentState() {\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentType = \"/equipment_types/\" + type;\r\n let selectType = type;\r\n if (type == \"main_hand\" || type == \"two_hand\") {\r\n selectType = \"weapon\";\r\n }\r\n\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + selectType);\r\n let equipmentHrid = equipmentSelect.value;\r\n\r\n if (!equipmentHrid) {\r\n player.equipment[equipmentType] = null;\r\n return;\r\n }\r\n\r\n let gameItem = itemDetailMap[equipmentHrid];\r\n\r\n // Clear old weapon if a weapon of a different type is equipped\r\n if (gameItem.equipmentDetail.type != equipmentType) {\r\n player.equipment[equipmentType] = null;\r\n return;\r\n }\r\n\r\n let enhancementLevel = Number(document.getElementById(\"inputEquipmentEnhancementLevel_\" + selectType).value);\r\n player.equipment[equipmentType] = new Equipment(gameItem.hrid, enhancementLevel);\r\n });\r\n}\r\n\r\ndocument.getElementById(\"selectEquipment_set\").onchange = changeEquipmentSetListener;\r\n\r\nfunction changeEquipmentSetListener() {\r\n let value = this.value\r\n let optgroupType = this.options[this.selectedIndex].parentNode.label;\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\"].forEach((type) => {\r\n let selectType = type;\r\n\r\n let currentEquipment = document.getElementById(\"selectEquipment_\" + selectType);\r\n if (type === \"feet\") {\r\n type = \"_boots\";\r\n }\r\n if (type === \"hands\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_bracers\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_gloves\";\r\n } else {\r\n type = \"_gauntlets\";\r\n }\r\n }\r\n if (type === \"head\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_hood\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_hat\";\r\n } else {\r\n type = \"_helmet\";\r\n }\r\n }\r\n if (type === \"legs\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_chaps\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_robe_bottoms\";\r\n } else {\r\n type = \"_plate_legs\";\r\n }\r\n }\r\n if (type === \"body\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_tunic\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_robe_top\";\r\n } else {\r\n type = \"_plate_body\";\r\n }\r\n }\r\n currentEquipment.value = \"/items/\" + value.toLowerCase() + type;\r\n });\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Combat Stats\r\n\r\nfunction updateCombatStatsUI() {\r\n player.updateCombatDetails();\r\n\r\n let combatStyleElement = document.getElementById(\"combatStat_combatStyleHrid\");\r\n let combatStyle = player.combatDetails.combatStats.combatStyleHrid;\r\n combatStyleElement.setAttribute(\"data-i18n\", \"combatStyleNames.\" + combatStyle);\r\n combatStyleElement.innerHTML = combatStyleDetailMap[combatStyle].name;\r\n\r\n let damageTypeElement = document.getElementById(\"combatStat_damageType\");\r\n let damageType = damageTypeDetailMap[player.combatDetails.combatStats.damageType];\r\n damageTypeElement.setAttribute(\"data-i18n\", \"damageTypeNames.\" + damageType.hrid);\r\n damageTypeElement.innerHTML = damageType.name;\r\n\r\n let attackIntervalElement = document.getElementById(\"combatStat_attackInterval\");\r\n attackIntervalElement.innerHTML = (player.combatDetails.combatStats.attackInterval / 1e9).toLocaleString() + \"s\";\r\n\r\n let primaryTrainingElement = document.getElementById(\"combatStat_primaryTraining\");\r\n let primaryTraining = player.combatDetails.combatStats.primaryTraining;\r\n primaryTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + primaryTraining);\r\n primaryTrainingElement.innerHTML = primaryTraining;\r\n\r\n let focusTrainingElement = document.getElementById(\"combatStat_focusTraining\");\r\n let focusTraining = player.combatDetails.combatStats.focusTraining;\r\n if (focusTraining) {\r\n focusTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + focusTraining);\r\n } else {\r\n focusTrainingElement.setAttribute(\"data-i18n\", \"characterSelectPage.slots.empty\");\r\n }\r\n focusTrainingElement.innerHTML = focusTraining;\r\n\r\n [\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"stabAccuracyRating\",\r\n \"stabMaxDamage\",\r\n \"slashAccuracyRating\",\r\n \"slashMaxDamage\",\r\n \"smashAccuracyRating\",\r\n \"smashMaxDamage\",\r\n \"rangedAccuracyRating\",\r\n \"rangedMaxDamage\",\r\n \"magicAccuracyRating\",\r\n \"magicMaxDamage\",\r\n \"defensiveMaxDamage\",\r\n \"stabEvasionRating\",\r\n \"slashEvasionRating\",\r\n \"smashEvasionRating\",\r\n \"rangedEvasionRating\",\r\n \"magicEvasionRating\",\r\n \"totalArmor\",\r\n \"totalWaterResistance\",\r\n \"totalNatureResistance\",\r\n \"totalFireResistance\",\r\n \"totalThreat\"\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n element.innerHTML = Math.floor(player.combatDetails[stat]);\r\n });\r\n\r\n [\r\n \"abilityHaste\",\r\n \"tenacity\"\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n element.innerHTML = Math.floor(player.combatDetails.combatStats[stat]);\r\n });\r\n\r\n [\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"combatExperience\",\r\n \"taskDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"drinkConcentration\",\r\n \"foodHaste\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\"\r\n\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n let value = (100 * player.combatDetails.combatStats[stat]).toLocaleString([], {\r\n minimumFractionDigits: 0,\r\n maximumFractionDigits: 4,\r\n });\r\n element.innerHTML = value + \"%\";\r\n });\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Level\r\n\r\nfunction initLevelSection() {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n levelInput.value = 1;\r\n levelInput.addEventListener(\"change\", levelInputHandler);\r\n });\r\n}\r\n\r\nfunction levelInputHandler() {\r\n updateLevels();\r\n updateUI();\r\n}\r\n\r\nfunction updateLevels() {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n player[skill + \"Level\"] = Number(levelInput.value);\r\n });\r\n updateCombatLevel();\r\n}\r\n\r\nfunction calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel) {\r\n return Math.floor(\r\n 0.1 * (staminaLevel + intelligenceLevel + attackLevel + defenseLevel + Math.max(meleeLevel, rangedLevel, magicLevel))\r\n + 0.5 * Math.max(attackLevel, defenseLevel, meleeLevel, rangedLevel, magicLevel)\r\n );\r\n}\r\n\r\n\r\nfunction updateCombatLevel() {\r\n let staminaLevel = player[\"staminaLevel\"];\r\n let intelligenceLevel = player[\"intelligenceLevel\"];\r\n let defenseLevel = player[\"defenseLevel\"];\r\n let attackLevel = player[\"attackLevel\"];\r\n let meleeLevel = player[\"meleeLevel\"];\r\n let rangedLevel = player[\"rangedLevel\"];\r\n let magicLevel = player[\"magicLevel\"];\r\n\r\n let levelInput = document.getElementById(\"inputLevel_combat\");\r\n levelInput.value = calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel);;\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Food\r\n\r\nfunction initFoodSection() {\r\n for (let i = 0; i < 3; i++) {\r\n let element = document.getElementById(\"selectFood_\" + i);\r\n\r\n let gameFoods = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/food\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const food of Object.values(gameFoods)) {\r\n let opt = new Option(food.name, food.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + food.hrid);\r\n element.add(opt);\r\n }\r\n\r\n element.addEventListener(\"change\", foodSelectHandler);\r\n }\r\n}\r\n\r\nfunction foodSelectHandler() {\r\n updateFoodState();\r\n updateUI();\r\n}\r\n\r\nfunction updateFoodState() {\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n food[i] = foodSelect.value;\r\n if (food[i] && !triggerMap[food[i]]) {\r\n let gameItem = itemDetailMap[food[i]];\r\n triggerMap[food[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateFoodUI() {\r\n for (let i = 0; i < 3; i++) {\r\n let selectElement = document.getElementById(\"selectFood_\" + i);\r\n let triggerButton = document.getElementById(\"buttonFoodTrigger_\" + i);\r\n\r\n selectElement.disabled = i >= player.combatDetails.combatStats.foodSlots;\r\n triggerButton.disabled = i >= player.combatDetails.combatStats.foodSlots || !food[i];\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Drinks\r\n\r\nfunction initDrinksSection() {\r\n for (let i = 0; i < 3; i++) {\r\n let element = document.getElementById(\"selectDrink_\" + i);\r\n\r\n let gameDrinks = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/drink\")\r\n .filter((item) => item.consumableDetail.usableInActionTypeMap[\"/action_types/combat\"])\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const drink of Object.values(gameDrinks)) {\r\n let opt = new Option(drink.name, drink.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + drink.hrid);\r\n element.add(opt);\r\n }\r\n\r\n element.addEventListener(\"change\", drinkSelectHandler);\r\n }\r\n}\r\n\r\nfunction drinkSelectHandler() {\r\n updateDrinksState();\r\n updateDrinksUI();\r\n}\r\n\r\nfunction updateDrinksState() {\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n drinks[i] = drinkSelect.value;\r\n if (drinks[i] && !triggerMap[drinks[i]]) {\r\n let gameItem = itemDetailMap[drinks[i]];\r\n triggerMap[drinks[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateDrinksUI() {\r\n for (let i = 0; i < 3; i++) {\r\n let selectElement = document.getElementById(\"selectDrink_\" + i);\r\n let triggerButton = document.getElementById(\"buttonDrinkTrigger_\" + i);\r\n\r\n selectElement.disabled = i >= player.combatDetails.combatStats.drinkSlots;\r\n triggerButton.disabled = i >= player.combatDetails.combatStats.drinkSlots || !drinks[i];\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Abilities\r\n\r\nfunction initAbilitiesSection() {\r\n for (let i = 0; i < 5; i++) {\r\n let selectElement = document.getElementById(\"selectAbility_\" + i);\r\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\r\n\r\n inputElement.value = 1;\r\n\r\n let gameAbilities;\r\n if (i == 0) {\r\n gameAbilities = Object.values(abilityDetailMap).filter(x => x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\r\n } else {\r\n gameAbilities = Object.values(abilityDetailMap).filter(x => !x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\r\n }\r\n\r\n\r\n for (const ability of Object.values(gameAbilities)) {\r\n let opt = new Option(ability.name, ability.hrid);\r\n opt.setAttribute(\"data-i18n\", \"abilityNames.\" + ability.hrid);\r\n selectElement.add(opt);\r\n }\r\n\r\n selectElement.addEventListener(\"change\", abilitySelectHandler);\r\n }\r\n}\r\n\r\nfunction abilitySelectHandler() {\r\n updateAbilityState();\r\n updateAbilityUI();\r\n}\r\n\r\nfunction updateAbilityState() {\r\n for (let i = 0; i < 5; i++) {\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\r\n abilities[i] = abilitySelect.value;\r\n if (abilities[i] && !triggerMap[abilities[i]]) {\r\n let gameAbility = abilityDetailMap[abilities[i]];\r\n triggerMap[abilities[i]] = structuredClone(gameAbility.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateAbilityUI() {\r\n for (let i = 0; i < 5; i++) {\r\n let selectElement = document.getElementById(\"selectAbility_\" + i);\r\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let triggerButton = document.getElementById(\"buttonAbilityTrigger_\" + i);\r\n\r\n selectElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\r\n inputElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\r\n triggerButton.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1] || !abilities[i];\r\n let moveUpButton = document.getElementById(\"selectAbilityMoveUp_\" + i);\r\n moveUpButton.onclick = () => swapAbilityOrder(i, -1);\r\n }\r\n}\r\n\r\nfunction swapAbilityOrder(abilityIndex, step) {\r\n const swapIndex = abilityIndex + step;\r\n if (swapIndex < 0 || swapIndex > 4) {\r\n return;\r\n }\r\n\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilityIndex);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilityIndex);\r\n\r\n const tempAbility = abilities[abilityIndex];\r\n abilities[abilityIndex] = abilities[swapIndex];\r\n abilities[swapIndex] = tempAbility;\r\n\r\n const tempLevel = abilityLevelInput.value;\r\n abilityLevelInput.value = document.getElementById(\"inputAbilityLevel_\" + swapIndex).value;\r\n document.getElementById(\"inputAbilityLevel_\" + swapIndex).value = tempLevel;\r\n\r\n abilitySelect.value = document.getElementById(\"selectAbility_\" + (swapIndex)).value;\r\n document.getElementById(\"selectAbility_\" + swapIndex).value = abilities[swapIndex];\r\n\r\n updateAbilityState();\r\n updateAbilityUI();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Trigger\r\n\r\nfunction initTriggerModal() {\r\n let modal = document.getElementById(\"triggerModal\");\r\n modal.addEventListener(\"show.bs.modal\", (event) => triggerModalShownHandler(event));\r\n\r\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\r\n triggerSaveButton.addEventListener(\"click\", (event) => triggerModalSaveHandler(event));\r\n\r\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\r\n triggerAddButton.addEventListener(\"click\", (event) => triggerAddButtonHandler(event));\r\n\r\n let triggerDefaultButton = document.getElementById(\"buttonDefaultTrigger\");\r\n triggerDefaultButton.addEventListener(\"click\", (event) => triggerDefaultButtonHandler(event));\r\n\r\n for (let i = 0; i < 4; i++) {\r\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\r\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\r\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\r\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\r\n let triggerRemoveButton = document.getElementById(\"buttonRemoveTrigger_\" + i);\r\n\r\n triggerDependencySelect.addEventListener(\"change\", (event) => triggerDependencySelectHandler(event, i));\r\n triggerConditionSelect.addEventListener(\"change\", (event) => triggerConditionSelectHandler(event, i));\r\n triggerComparatorSelect.addEventListener(\"change\", (event) => triggerComparatorSelectHander(event, i));\r\n triggerValueInput.addEventListener(\"change\", (event) => triggerValueInputHandler(event, i));\r\n triggerRemoveButton.addEventListener(\"click\", (event) => triggerRemoveButtonHandler(event, i));\r\n }\r\n}\r\n\r\nfunction triggerModalShownHandler(event) {\r\n let triggerButton = event.relatedTarget;\r\n\r\n let triggerType = triggerButton.getAttribute(\"data-bs-triggertype\");\r\n let triggerIndex = Number(triggerButton.getAttribute(\"data-bs-triggerindex\"));\r\n\r\n let triggerTarget;\r\n switch (triggerType) {\r\n case \"food\":\r\n triggerTarget = food[triggerIndex];\r\n break;\r\n case \"drink\":\r\n triggerTarget = drinks[triggerIndex];\r\n break;\r\n case \"ability\":\r\n triggerTarget = abilities[triggerIndex];\r\n break;\r\n }\r\n\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n triggerTargetnput.value = triggerTarget;\r\n modalTriggers = triggerMap[triggerTarget];\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerModalSaveHandler(event) {\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n let triggerTarget = triggerTargetnput.value;\r\n\r\n triggerMap[triggerTarget] = modalTriggers;\r\n}\r\n\r\nfunction triggerDependencySelectHandler(event, index) {\r\n modalTriggers[index].dependencyHrid = event.target.value;\r\n modalTriggers[index].conditionHrid = \"\";\r\n modalTriggers[index].comparatorHrid = \"\";\r\n modalTriggers[index].value = 0;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerConditionSelectHandler(event, index) {\r\n modalTriggers[index].conditionHrid = event.target.value;\r\n modalTriggers[index].comparatorHrid = \"\";\r\n modalTriggers[index].value = 0;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerComparatorSelectHander(event, index) {\r\n modalTriggers[index].comparatorHrid = event.target.value;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerValueInputHandler(event, index) {\r\n modalTriggers[index].value = Number(event.target.value);\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerRemoveButtonHandler(event, index) {\r\n modalTriggers.splice(index, 1);\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerAddButtonHandler(event) {\r\n if (modalTriggers.length == 4) {\r\n return;\r\n }\r\n\r\n modalTriggers.push({\r\n dependencyHrid: \"\",\r\n conditionHrid: \"\",\r\n comparatorHrid: \"\",\r\n value: 0,\r\n });\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerDefaultButtonHandler(event) {\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n let triggerTarget = triggerTargetnput.value;\r\n\r\n if (triggerTarget.startsWith(\"/items/\")) {\r\n modalTriggers = structuredClone(itemDetailMap[triggerTarget].consumableDetail.defaultCombatTriggers);\r\n } else {\r\n modalTriggers = structuredClone(abilityDetailMap[triggerTarget].defaultCombatTriggers);\r\n }\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction updateTriggerModal() {\r\n let triggerStartTextElement = document.getElementById(\"triggerStartText\");\r\n if (modalTriggers.length == 0) {\r\n triggerStartTextElement.innerHTML = \"Activate as soon as it's off cooldown\";\r\n } else {\r\n triggerStartTextElement.innerHTML = \"Activate when:\";\r\n }\r\n\r\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\r\n triggerAddButton.disabled = modalTriggers.length == 4;\r\n\r\n let triggersValid = true;\r\n\r\n for (let i = 0; i < 4; i++) {\r\n let triggerElement = document.getElementById(\"modalTrigger_\" + i);\r\n\r\n if (!modalTriggers[i]) {\r\n hideElement(triggerElement);\r\n continue;\r\n }\r\n\r\n showElement(triggerElement);\r\n\r\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\r\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\r\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\r\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\r\n\r\n showElement(triggerDependencySelect);\r\n fillTriggerDependencySelect(triggerDependencySelect);\r\n\r\n if (modalTriggers[i].dependencyHrid == \"\") {\r\n hideElement(triggerConditionSelect);\r\n hideElement(triggerComparatorSelect);\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerDependencySelect.value = modalTriggers[i].dependencyHrid;\r\n showElement(triggerConditionSelect);\r\n fillTriggerConditionSelect(triggerConditionSelect, modalTriggers[i].dependencyHrid);\r\n\r\n if (modalTriggers[i].conditionHrid == \"\") {\r\n hideElement(triggerComparatorSelect);\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerConditionSelect.value = modalTriggers[i].conditionHrid;\r\n showElement(triggerComparatorSelect);\r\n fillTriggerComparatorSelect(triggerComparatorSelect, modalTriggers[i].conditionHrid);\r\n\r\n if (modalTriggers[i].comparatorHrid == \"\") {\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerComparatorSelect.value = modalTriggers[i].comparatorHrid;\r\n\r\n if (combatTriggerComparatorDetailMap[modalTriggers[i].comparatorHrid].allowValue) {\r\n showElement(triggerValueInput);\r\n triggerValueInput.value = modalTriggers[i].value;\r\n } else {\r\n hideElement(triggerValueInput);\r\n }\r\n }\r\n\r\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\r\n triggerSaveButton.disabled = !triggersValid;\r\n\r\n updateContent();\r\n}\r\n\r\nfunction fillTriggerDependencySelect(element) {\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const dependency of Object.values(combatTriggerDependencyDetailMap).sort(\r\n (a, b) => a.sortIndex - b.sortIndex\r\n )) {\r\n let opt = new Option(dependency.name, dependency.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerDependencyNames.\" + dependency.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction fillTriggerConditionSelect(element, dependencyHrid) {\r\n let dependency = combatTriggerDependencyDetailMap[dependencyHrid];\r\n\r\n let conditions;\r\n if (dependency.isSingleTarget) {\r\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isSingleTarget);\r\n } else {\r\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isMultiTarget);\r\n }\r\n\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const condition of Object.values(conditions).sort((a, b) => a.sortIndex - b.sortIndex)) {\r\n let opt = new Option(condition.name, condition.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerConditionNames.\" + condition.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction fillTriggerComparatorSelect(element, conditionHrid) {\r\n let condition = combatTriggerConditionDetailMap[conditionHrid];\r\n\r\n let comparators = condition.allowedComparatorHrids.map((hrid) => combatTriggerComparatorDetailMap[hrid]);\r\n\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const comparator of Object.values(comparators).sort((a, b) => a.sortIndex - b.sortIndex)) {\r\n let opt = new Option(comparator.name, comparator.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerComparatorNames.\" + comparator.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction hideElement(element) {\r\n element.classList.remove(\"d-flex\");\r\n element.classList.add(\"d-none\");\r\n}\r\n\r\nfunction showElement(element) {\r\n element.classList.remove(\"d-none\");\r\n element.classList.add(\"d-flex\");\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Zones\r\n\r\nfunction initZones() {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n\r\n // TOOD dungeon wave spawns\r\n let gameZones = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const zone of Object.values(gameZones)) {\r\n let opt = new Option(zone.name, zone.hrid);\r\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + zone.hrid);\r\n zoneSelect.add(opt);\r\n }\r\n\r\n\r\n let zoneCheckBox = document.getElementById(\"zoneCheckBox\");\r\n let checkAllZonesToggle = document.getElementById('checkAllZones');\r\n\r\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\r\n simAllZonesToggle.addEventListener(\"change\", (event) => {\r\n if (simAllZonesToggle.checked) {\r\n zoneCheckBox.classList.remove(\"d-none\");\r\n zoneCheckBox.querySelectorAll(\".zone-checkbox\").forEach(checkbox => checkbox.checked = true);\r\n checkAllZonesToggle.checked = true;\r\n } else {\r\n zoneCheckBox.classList.add(\"d-none\");\r\n }\r\n });\r\n\r\n let zoneHrids = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .flat();\r\n\r\n for (const zoneHrid of zoneHrids) {\r\n const newZone = document.createElement('div');\r\n newZone.classList.add('form-check');\r\n newZone.innerHTML = `\r\n \r\n \r\n `;\r\n zoneCheckBox.append(newZone);\r\n }\r\n\r\n let checkZoneToggles = document.querySelectorAll('.zone-checkbox');\r\n checkAllZonesToggle.addEventListener('change', () => {\r\n checkZoneToggles.forEach(cb => cb.checked = checkAllZonesToggle.checked);\r\n });\r\n\r\n checkZoneToggles.forEach(cb =>\r\n cb.addEventListener('change', () => {\r\n checkAllZonesToggle.checked = [...checkZoneToggles].every(x => x.checked);\r\n })\r\n );\r\n\r\n\r\n let soloCheckBox = document.getElementById(\"soloCheckBox\");\r\n let checkAllSolosToggle = document.getElementById('checkAllSolos');\r\n\r\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\r\n simAllSoloToggle.addEventListener(\"change\", (event) => {\r\n if (simAllSoloToggle.checked) {\r\n soloCheckBox.classList.remove(\"d-none\");\r\n soloCheckBox.querySelectorAll(\".solo-checkbox\").forEach(checkbox => checkbox.checked = true);\r\n checkAllSolosToggle.checked = true;\r\n } else {\r\n soloCheckBox.classList.add(\"d-none\");\r\n }\r\n });\r\n\r\n let soloHrids = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount == 1)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .flat();\r\n\r\n for (const zoneHrid of soloHrids) {\r\n const newZone = document.createElement('div');\r\n newZone.classList.add('form-check');\r\n newZone.innerHTML = `\r\n \r\n \r\n `;\r\n soloCheckBox.append(newZone);\r\n }\r\n\r\n let checkSoloToggles = document.querySelectorAll('.solo-checkbox');\r\n checkAllSolosToggle.addEventListener('change', () => {\r\n checkSoloToggles.forEach(cb => cb.checked = checkAllSolosToggle.checked);\r\n });\r\n\r\n checkSoloToggles.forEach(cb =>\r\n cb.addEventListener('change', () => {\r\n checkAllSolosToggle.checked = [...checkSoloToggles].every(x => x.checked);\r\n })\r\n );\r\n}\r\n\r\nfunction initDungeons() {\r\n let dungeonSelect = document.getElementById(\"selectDungeon\");\r\n\r\n let gameDungeons = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category == \"/action_categories/combat/dungeons\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const dungeon of Object.values(gameDungeons)) {\r\n let opt = new Option(dungeon.name, dungeon.hrid);\r\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + dungeon.hrid);\r\n dungeonSelect.add(opt);\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Simulation Result\r\n\r\nfunction createDamageDoneAccordion(enemyIndex) {\r\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageDoneAccordionEnemy${enemyIndex}`);\r\n\r\n const colDiv = createElement('div', 'col');\r\n const accordionMainDiv = createElement('div', 'accordion');\r\n const accordionItemDiv = createElement('div', 'accordion-item');\r\n\r\n const headerH2 = createElement('h2', 'accordion-header');\r\n const button = createElement('button', 'accordion-button collapsed',\r\n `Damage Done (Enemy ${enemyIndex})`,\r\n `buttonSimulationResultDamageDoneAccordionEnemy${enemyIndex}`\r\n );\r\n button.setAttribute('type', 'button');\r\n button.setAttribute('data-bs-toggle', 'collapse');\r\n button.setAttribute('data-bs-target', `#collapseDamageDone${enemyIndex}`);\r\n button.style.padding = '0.5em';\r\n\r\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageDone${enemyIndex}`);\r\n const accordionBodyDiv = createElement('div', 'accordion-body');\r\n\r\n const headerRow = createElement('div', 'row');\r\n headerRow.innerHTML = `\r\n
Source
\r\n
Hitchance
\r\n
DPS
\r\n
%
\r\n `;\r\n\r\n const resultDiv = createElement('div', '', '', `simulationResultDamageDoneEnemy${enemyIndex}`);\r\n\r\n accordionBodyDiv.appendChild(headerRow);\r\n accordionBodyDiv.appendChild(resultDiv);\r\n collapseDiv.appendChild(accordionBodyDiv);\r\n headerH2.appendChild(button);\r\n accordionItemDiv.appendChild(headerH2);\r\n accordionItemDiv.appendChild(collapseDiv);\r\n accordionMainDiv.appendChild(accordionItemDiv);\r\n colDiv.appendChild(accordionMainDiv);\r\n accordionDiv.appendChild(colDiv);\r\n\r\n return accordionDiv;\r\n}\r\nfunction createDamageTakenAccordion(enemyIndex) {\r\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageTakenAccordionEnemy${enemyIndex}`);\r\n\r\n const colDiv = createElement('div', 'col');\r\n const accordionMainDiv = createElement('div', 'accordion');\r\n const accordionItemDiv = createElement('div', 'accordion-item');\r\n\r\n const headerH2 = createElement('h2', 'accordion-header');\r\n const button = createElement('button', 'accordion-button collapsed',\r\n `Damage Taken (Enemy ${enemyIndex})`,\r\n `buttonSimulationResultDamageTakenAccordionEnemy${enemyIndex}`\r\n );\r\n button.setAttribute('type', 'button');\r\n button.setAttribute('data-bs-toggle', 'collapse');\r\n button.setAttribute('data-bs-target', `#collapseDamageTaken${enemyIndex}`);\r\n button.style.padding = '0.5em';\r\n\r\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageTaken${enemyIndex}`);\r\n const accordionBodyDiv = createElement('div', 'accordion-body');\r\n\r\n const headerRow = createElement('div', 'row');\r\n headerRow.innerHTML = `\r\n
Source
\r\n
Hitchance
\r\n
DPS
\r\n
%
\r\n `;\r\n\r\n const resultDiv = createElement('div', '', '', `simulationResultDamageTakenEnemy${enemyIndex}`);\r\n\r\n accordionBodyDiv.appendChild(headerRow);\r\n accordionBodyDiv.appendChild(resultDiv);\r\n collapseDiv.appendChild(accordionBodyDiv);\r\n headerH2.appendChild(button);\r\n accordionItemDiv.appendChild(headerH2);\r\n accordionItemDiv.appendChild(collapseDiv);\r\n accordionMainDiv.appendChild(accordionItemDiv);\r\n colDiv.appendChild(accordionMainDiv);\r\n accordionDiv.appendChild(colDiv);\r\n\r\n return accordionDiv;\r\n}\r\n\r\n\r\nfunction initDamageDoneTaken() {\r\n for (let i = 64; i > 0; i--) {\r\n document.getElementById(\"simulationResultTotalDamageDone\").insertAdjacentElement('afterend', createDamageDoneAccordion(i));\r\n document.getElementById(\"simulationResultTotalDamageTaken\").insertAdjacentElement('afterend', createDamageTakenAccordion(i));\r\n }\r\n}\r\n\r\nfunction showSimulationResult(simResult) {\r\n currentSimResults = simResult;\r\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\r\n expensesModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\r\n revenueModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let noRngRevenueModalTable = document.querySelector(\"#noRngRevenueTable > tbody\");\r\n noRngRevenueModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let playerToDisplay = \"player1\";\r\n if (selectedPlayers.includes(parseInt(currentPlayerTabId))) {\r\n playerToDisplay = \"player\" + currentPlayerTabId;\r\n }\r\n if (!simResult.dropRateMultiplier[playerToDisplay]) {\r\n return;\r\n }\r\n\r\n showKills(simResult, playerToDisplay);\r\n showDeaths(simResult, playerToDisplay);\r\n showExperienceGained(simResult, playerToDisplay);\r\n showConsumablesUsed(simResult, playerToDisplay);\r\n showHpSpent(simResult, playerToDisplay);\r\n showManaUsed(simResult, playerToDisplay);\r\n showHitpointsGained(simResult, playerToDisplay);\r\n showManapointsGained(simResult, playerToDisplay);\r\n showDamageDone(simResult, playerToDisplay);\r\n showDamageTaken(simResult, playerToDisplay);\r\n renderWipeEvents(simResult);\r\n window.profit = window.revenue - window.expenses;\r\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\r\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\r\n window.noRngProfit = window.noRngRevenue - window.expenses;\r\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\r\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\r\n}\r\n\r\nfunction showAllSimulationResults(simResults) {\r\n let displaySimResults = manipulateSimResultsDataForDisplay(simResults);\r\n updateAllSimsModal(displaySimResults);\r\n}\r\n\r\nfunction manipulateSimResultsDataForDisplay(simResults) {\r\n let displaySimResults = [];\r\n for (let i = 0; i < simResults.length; i++) {\r\n for (let j = 0; j < selectedPlayers.length; j++) {\r\n let playerToDisplay = \"player\" + selectedPlayers[j].toString();\r\n let simResult = simResults[i];\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let zoneName = simResult.zoneName;\r\n let difficultyTier = simResult.difficultyTier;\r\n let encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\r\n let playerDeaths = simResult.deaths[playerToDisplay] ?? 0;\r\n let deathsPerHour = (playerDeaths / hoursSimulated).toFixed(2);\r\n\r\n let totalExperience = 0;\r\n if (simResult.experienceGained[playerToDisplay]) {\r\n totalExperience = Object.values(simResult.experienceGained[playerToDisplay]).reduce((prev, cur) => prev + cur, 0);\r\n }\r\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\r\n\r\n let experiencePerHour = {};\r\n const skills = [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"];\r\n skills.forEach((skill) => {\r\n const skillLower = skill.toLowerCase();\r\n let experience = simResult.experienceGained[playerToDisplay]?.[skillLower] ?? 0;\r\n let experiencePerHourValue = 0;\r\n if (experience != 0) {\r\n experiencePerHourValue = (experience / hoursSimulated).toFixed(0);\r\n }\r\n experiencePerHour[skill] = experiencePerHourValue;\r\n });\r\n getDropProfit(simResult, playerToDisplay);\r\n let noRngRevenue = simResult[\"noRngRevenue\"];\r\n let noRngProfit = simResult[\"noRngProfit\"];\r\n let expenses = simResult[\"expenses\"];\r\n\r\n let displaySimRow = {\r\n \"ZoneName\": zoneName, \"DifficultyTier\": difficultyTier, \"Player\": playerToDisplay, \"Encounters\": encountersPerHour, \"Deaths\": deathsPerHour,\r\n \"TotalExperience\": totalExperiencePerHour, \"Stamina\": experiencePerHour[\"Stamina\"],\r\n \"Intelligence\": experiencePerHour[\"Intelligence\"], \"Attack\": experiencePerHour[\"Attack\"],\r\n \"Magic\": experiencePerHour[\"Magic\"], \"Ranged\": experiencePerHour[\"Ranged\"],\r\n \"Melee\": experiencePerHour[\"Melee\"], \"Defense\": experiencePerHour[\"Defense\"],\r\n \"noRngRevenue\": noRngRevenue,\r\n \"expenses\": expenses,\r\n \"noRngProfit\": noRngProfit\r\n };\r\n displaySimResults.push(displaySimRow);\r\n }\r\n }\r\n return displaySimResults;\r\n}\r\n\r\nfunction fidDropAmount(dropAmount) {\r\n if (Number.isInteger(dropAmount)) return dropAmount;\r\n\r\n const intPart = Math.floor(dropAmount);\r\n const fracPart = dropAmount - intPart;\r\n return Math.random() < fracPart ? intPart + 1 : intPart;\r\n}\r\n\r\nfunction calcDropMaps(simResult, playerToDisplay) {\r\n let dropRateMultiplier = simResult.dropRateMultiplier[playerToDisplay];\r\n let rareFindMultiplier = simResult.rareFindMultiplier[playerToDisplay];\r\n let combatDropQuantity = simResult.combatDropQuantity[playerToDisplay];\r\n let debuffOnLevelGap = simResult.debuffOnLevelGap[playerToDisplay];\r\n\r\n let numberOfPlayers = simResult.numberOfPlayers;\r\n let monsters = Object.keys(simResult.deaths)\r\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\r\n .sort();\r\n\r\n const totalDropMap = new Map();\r\n const noRngTotalDropMap = new Map();\r\n for (const monster of monsters) {\r\n const dropMap = new Map();\r\n const rareDropMap = new Map();\r\n if (combatMonsterDetailMap[monster].dropTable) {\r\n for (const drop of combatMonsterDetailMap[monster].dropTable) {\r\n if (drop.minDifficultyTier > simResult.difficultyTier) {\r\n continue;\r\n }\r\n\r\n let multiplier = 1.0 + 0.1 * simResult.difficultyTier;\r\n let dropRate = Math.min(1.0, multiplier * (drop.dropRate + (drop.dropRatePerDifficultyTier ?? 0) * simResult.difficultyTier));\r\n if (dropRate <= 0) continue;\r\n\r\n dropMap.set(drop.itemHrid, { \"dropRate\": Math.min(1.0, dropRate * dropRateMultiplier), \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\r\n }\r\n if (combatMonsterDetailMap[monster].rareDropTable)\r\n for (const drop of combatMonsterDetailMap[monster].rareDropTable) {\r\n if (drop.minDifficultyTier > simResult.difficultyTier) {\r\n continue;\r\n }\r\n rareDropMap.set(drop.itemHrid, { \"dropRate\": drop.dropRate * rareFindMultiplier, \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\r\n }\r\n\r\n for (let dropObject of dropMap.values()) {\r\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\r\n\r\n }\r\n for (let dropObject of rareDropMap.values()) {\r\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\r\n }\r\n\r\n for (let i = 0; i < simResult.deaths[monster]; i++) {\r\n for (let dropObject of dropMap.values()) {\r\n let chance = Math.random();\r\n if (chance <= dropObject.dropRate / numberOfPlayers) {\r\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\r\n dropObject.number = dropObject.number + fidDropAmount(amount);\r\n }\r\n }\r\n for (let dropObject of rareDropMap.values()) {\r\n let chance = Math.random();\r\n if (chance <= dropObject.dropRate / numberOfPlayers) {\r\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\r\n dropObject.number = dropObject.number + fidDropAmount(amount);\r\n }\r\n }\r\n }\r\n for (let [name, dropObject] of dropMap.entries()) {\r\n if (totalDropMap.has(name)) {\r\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\r\n } else {\r\n totalDropMap.set(name, dropObject.number);\r\n }\r\n if (noRngTotalDropMap.has(name)) {\r\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\r\n } else {\r\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\r\n }\r\n }\r\n for (let [name, dropObject] of rareDropMap.entries()) {\r\n if (totalDropMap.has(name)) {\r\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\r\n } else {\r\n totalDropMap.set(name, dropObject.number);\r\n }\r\n if (noRngTotalDropMap.has(name)) {\r\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\r\n } else {\r\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return { totalDropMap, noRngTotalDropMap };\r\n}\r\n\r\nfunction getDropProfit(simResult, playerToDisplay) {\r\n let { totalDropMap, noRngTotalDropMap } = calcDropMaps(simResult, playerToDisplay);\r\n\r\n let noRngTotal = 0;\r\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\r\n let price = -1;\r\n let revenueSetting = document.getElementById('selectPrices_drops').value;\r\n if (window.prices) {\r\n let item = window.prices[name];\r\n if (item) {\r\n if (revenueSetting == 'bid') {\r\n if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n } else if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n }\r\n } else if (revenueSetting == 'ask') {\r\n if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n } else if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n }\r\n }\r\n if (price == -1) {\r\n price = item['vendor'];\r\n }\r\n }\r\n }\r\n noRngTotal += price * dropAmount;\r\n }\r\n\r\n let consumablesUsed = simResult.consumablesUsed?.[playerToDisplay];\r\n\r\n if (consumablesUsed) {\r\n consumablesUsed = Object.entries(consumablesUsed).sort((a, b) => b[1] - a[1]);\r\n } else {\r\n consumablesUsed = [];\r\n }\r\n\r\n let expenses = 0;\r\n for (const [consumable, amount] of consumablesUsed) {\r\n let price = -1;\r\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\r\n if (window.prices) {\r\n let item = window.prices[consumable];\r\n if (item) {\r\n if (expensesSetting == 'bid') {\r\n if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n } else if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n }\r\n } else if (expensesSetting == 'ask') {\r\n if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n } else if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n }\r\n }\r\n if (price == -1) {\r\n price = item['vendor'];\r\n }\r\n }\r\n }\r\n expenses += price * amount;\r\n }\r\n\r\n simResult[\"noRngRevenue\"] = (noRngTotal).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n simResult[\"expenses\"] = (expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n simResult[\"noRngProfit\"] = (noRngTotal - expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n}\r\n\r\nfunction updateAllSimsModal(data) {\r\n const tableBody = document.getElementById('allZonesData').getElementsByTagName('tbody')[0];\r\n tableBody.innerHTML = '';\r\n data.forEach(item => {\r\n const row = document.createElement('tr');\r\n\r\n Object.keys(item).forEach(key => {\r\n const cell = document.createElement('td');\r\n cell.textContent = item[key];\r\n if (key === 'ZoneName') {\r\n cell.setAttribute(\"data-i18n\", \"actionNames.\" + item[key]);\r\n }\r\n row.appendChild(cell);\r\n });\r\n\r\n tableBody.appendChild(row);\r\n });\r\n\r\n const table = document.getElementById('allZonesData');\r\n const rows = table.getElementsByTagName('tr');\r\n const numCols = rows[0].cells.length;\r\n\r\n // 遍历每一列\r\n for (let col = 5; col < numCols; col++) {\r\n let max = -Infinity;\r\n let maxCell = null;\r\n\r\n // 找到最大值及其单元格\r\n for (let row = 1; row < rows.length; row++) {\r\n const cell = rows[row].cells[col];\r\n const value = parseFloat(cell.textContent.replace(/,/g, ''));\r\n if (value > max) {\r\n max = value;\r\n maxCell = cell;\r\n }\r\n }\r\n\r\n // 将最大值单元格的背景色设置为绿色\r\n if (maxCell && max != 0) {\r\n maxCell.style.backgroundColor = 'green';\r\n maxCell.style.color = 'white'; // 设置文字颜色为白色以提高可读性\r\n }\r\n }\r\n}\r\n\r\nlet currentSortColumn = null;\r\nlet currentSortDirection = 'desc';\r\n\r\nfunction sortTable(tableId, columnIndex, direction) {\r\n const table = document.getElementById(tableId);\r\n const tbody = table.querySelector('tbody');\r\n const rows = Array.from(tbody.querySelectorAll('tr'));\r\n\r\n const sortedRows = rows.sort((rowA, rowB) => {\r\n const cellA = rowA.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\r\n const cellB = rowB.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\r\n\r\n const valueA = parseFloat(cellA.replace(/,/g, ''));\r\n const valueB = parseFloat(cellB.replace(/,/g, ''));\r\n\r\n return direction === 'asc' ? valueA - valueB : valueB - valueA;\r\n });\r\n\r\n sortedRows.forEach(row => tbody.appendChild(row));\r\n updateSortIndicators(tableId, columnIndex, direction);\r\n}\r\n\r\nfunction updateSortIndicators(tableId, columnIndex, direction) {\r\n const headers = document.querySelectorAll(`#${tableId} th`);\r\n headers.forEach((header, index) => {\r\n header.classList.remove('sort-asc', 'sort-desc');\r\n if (index === columnIndex) {\r\n header.classList.add(direction === 'asc' ? 'sort-asc' : 'sort-desc');\r\n }\r\n });\r\n}\r\n\r\ndocument.querySelectorAll('#allZonesData th').forEach((header, index) => {\r\n if (index === 0) return;\r\n if (index === 1) return;\r\n if (index === 2) return;\r\n\r\n header.addEventListener('click', () => {\r\n if (currentSortColumn === index) {\r\n currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';\r\n } else {\r\n currentSortColumn = index;\r\n currentSortDirection = 'desc';\r\n }\r\n sortTable('allZonesData', currentSortColumn, currentSortDirection);\r\n });\r\n});\r\n\r\ndocument.getElementById('buttonExportResults').addEventListener('click', function () {\r\n var table = document.getElementById('allZonesData');\r\n var csv = [];\r\n var rows = table.querySelectorAll('tr');\r\n\r\n for (var i = 0; i < rows.length; i++) {\r\n var row = rows[i];\r\n var cols = row.querySelectorAll('th, td');\r\n var csvRow = [];\r\n\r\n cols.forEach(function (col) {\r\n csvRow.push('\"' + col.innerText.replace(/\"/g, '\"\"') + '\"');\r\n });\r\n\r\n csv.push(csvRow.join(','));\r\n }\r\n\r\n var csvFile = new Blob([csv.join('\\n')], { type: 'text/csv' });\r\n var downloadLink = document.createElement('a');\r\n downloadLink.download = 'simData.csv';\r\n downloadLink.href = URL.createObjectURL(csvFile);\r\n downloadLink.style.display = 'none';\r\n document.body.appendChild(downloadLink);\r\n downloadLink.click();\r\n document.body.removeChild(downloadLink);\r\n});\r\n\r\nfunction showKills(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultKills\");\r\n let dropsResultDiv = document.getElementById(\"simulationResultDrops\");\r\n let noRngDropsResultDiv = document.getElementById(\"noRngDrops\");\r\n let newChildren = [];\r\n let newDropChildren = [];\r\n let newNoRngDropChildren = [];\r\n\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let encountersPerHour = 0;\r\n let encountersRow = null;\r\n if (simResult.isDungeon) {\r\n let wavesCompletedRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Wave Reached\", simResult.maxWaveReached]);\r\n wavesCompletedRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxWaveReached\");\r\n newChildren.push(wavesCompletedRow);\r\n let completedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Completed Dungeons\", simResult.dungeonsCompleted]);\r\n completedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsCompleted\");\r\n newChildren.push(completedDungeonsRow);\r\n if (simResult.dungeonsFailed > 0) {\r\n let failedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Failed Dungeons\", simResult.dungeonsFailed]);\r\n failedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsFailed\");\r\n newChildren.push(failedDungeonsRow);\r\n }\r\n encountersPerHour = (simResult.dungeonsCompleted / hoursSimulated).toFixed(1);\r\n let averageTime = (hoursSimulated * 60 / simResult.dungeonsCompleted).toFixed(1);\r\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Average Time\", averageTime]);\r\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.averageTime\");\r\n } else {\r\n encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\r\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Encounters\", encountersPerHour]);\r\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.encounters\");\r\n }\r\n\r\n if (simResult.maxEnrageStack > 0) {\r\n let enrageRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Enrage Stack\", simResult.maxEnrageStack]);\r\n enrageRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxEnrageStack\");\r\n newChildren.push(enrageRow);\r\n }\r\n\r\n if (simResult.debuffOnLevelGap[playerToDisplay] != 0) {\r\n let debuffOnLevelGapRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Debuff on Level Gap\", Math.round(simResult.debuffOnLevelGap[playerToDisplay] * 100) + \"%\"]);\r\n debuffOnLevelGapRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.debuffOnLevelGap\");\r\n newChildren.push(debuffOnLevelGapRow);\r\n }\r\n\r\n newChildren.push(encountersRow);\r\n\r\n Object.keys(simResult.deaths)\r\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\r\n .sort()\r\n .forEach(monster => {\r\n let killsPerHour = (simResult.deaths[monster] / hoursSimulated).toFixed(1);\r\n let monsterRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [combatMonsterDetailMap[monster].name, killsPerHour]\r\n );\r\n monsterRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + monster);\r\n newChildren.push(monsterRow);\r\n });\r\n\r\n let { totalDropMap, noRngTotalDropMap } = !simResult.isDungeon ? calcDropMaps(simResult, playerToDisplay) : {totalDropMap:new Map(), noRngTotalDropMap:new Map()};\r\n\r\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\r\n let total = 0;\r\n for (let [name, dropAmount] of totalDropMap.entries()) {\r\n let dropRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [name, dropAmount.toLocaleString()]\r\n );\r\n dropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\r\n newDropChildren.push(dropRow);\r\n\r\n let tableRow = ' tbody\");\r\n let noRngTotal = 0;\r\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\r\n let noRngDropRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [name, dropAmount.toLocaleString()]\r\n );\r\n noRngDropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\r\n newNoRngDropChildren.push(noRngDropRow);\r\n\r\n let tableRow = ' prev + cur, 0);\r\n }\r\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\r\n let totalRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Total\", totalExperiencePerHour]);\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"].forEach((skill) => {\r\n let experience = simResult.experienceGained[playerToDisplay]?.[skill.toLowerCase()] ?? 0;\r\n if (experience == 0) {\r\n return;\r\n }\r\n let experiencePerHour = (experience / hoursSimulated).toFixed(0);\r\n let experienceRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [skill, experiencePerHour]);\r\n experienceRow.firstElementChild.setAttribute(\"data-i18n\", \"leaderboardCategoryNames.\" + skill.toLowerCase());\r\n newChildren.push(experienceRow);\r\n });\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showHpSpent(simResult, playerToDisplay) {\r\n let hpSpentHeadingDiv = document.getElementById(\"simulationHpSpentHeading\");\r\n hpSpentHeadingDiv.classList.add(\"d-none\");\r\n let hpSpentDiv = document.getElementById(\"simulationHpSpent\");\r\n hpSpentDiv.classList.add(\"d-none\");\r\n\r\n if (simResult.hitpointsSpent[playerToDisplay]) {\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let hpSpentSources = [];\r\n for (const source of Object.keys(simResult.hitpointsSpent[playerToDisplay])) {\r\n let hpSpentPerHour = (simResult.hitpointsSpent[playerToDisplay][source] / hoursSimulated).toFixed(2);\r\n let hpSpentRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [abilityDetailMap[source].name, hpSpentPerHour]);\r\n hpSpentRow.firstElementChild.setAttribute(\"data-i18n\", \"abilityNames.\" + source);\r\n hpSpentSources.push(hpSpentRow);\r\n }\r\n hpSpentDiv.replaceChildren(...hpSpentSources);\r\n hpSpentHeadingDiv.classList.remove(\"d-none\");\r\n hpSpentDiv.classList.remove(\"d-none\");\r\n }\r\n}\r\n\r\nfunction showConsumablesUsed(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultConsumablesUsed\");\r\n let newChildren = [];\r\n\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n\r\n if (!simResult.consumablesUsed[playerToDisplay]) {\r\n resultDiv.replaceChildren(...newChildren);\r\n window.expenses = 0;\r\n return;\r\n }\r\n\r\n let consumablesUsed = Object.entries(simResult.consumablesUsed[playerToDisplay]).sort((a, b) => b[1] - a[1]);\r\n\r\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\r\n let total = 0;\r\n for (const [consumable, amount] of consumablesUsed) {\r\n let consumablesPerHour = (amount / hoursSimulated).toFixed(0);\r\n let consumableRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [itemDetailMap[consumable].name, consumablesPerHour]\r\n );\r\n consumableRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + consumable);\r\n newChildren.push(consumableRow);\r\n\r\n let tableRow = ' b[1] - a[1]);\r\n\r\n let totalHitpointsGained = hitpointsGained.reduce((prev, cur) => prev + cur[1], 0);\r\n let totalHitpointsPerSecond = (totalHitpointsGained / secondsSimulated).toFixed(2);\r\n let totalRow = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [\"Total\", totalHitpointsPerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [source, amount] of hitpointsGained) {\r\n if (amount == 0) {\r\n continue;\r\n }\r\n\r\n let sourceText;\r\n let sourceFullHrid;\r\n switch (source) {\r\n case \"regen\":\r\n sourceText = \"Regen\";\r\n sourceFullHrid = \"combatStats.hpRegenPer10\";\r\n break;\r\n case \"lifesteal\":\r\n sourceText = \"Life Steal\";\r\n sourceFullHrid = \"combatStats.lifeSteal\";\r\n break;\r\n case \"bloom\":\r\n sourceText = \"Bloom\";\r\n sourceFullHrid = \"combatStats.bloom\";\r\n break;\r\n default:\r\n if (itemDetailMap[source]) {\r\n sourceText = itemDetailMap[source].name;\r\n sourceFullHrid = \"itemNames.\" + source;\r\n } else if (abilityDetailMap[source]) {\r\n sourceText = abilityDetailMap[source].name;\r\n sourceFullHrid = \"abilityNames.\" + source;\r\n }\r\n break;\r\n }\r\n let hitpointsPerSecond = (amount / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * amount) / totalHitpointsGained).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [sourceText, hitpointsPerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showManapointsGained(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultManaRestored\");\r\n let newChildren = [];\r\n\r\n let secondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n if (!simResult.manapointsGained[playerToDisplay]) {\r\n resultDiv.replaceChildren(...newChildren);\r\n return;\r\n }\r\n\r\n let manapointsGained = Object.entries(simResult.manapointsGained[playerToDisplay]).sort((a, b) => b[1] - a[1]);\r\n\r\n let totalManapointsGained = manapointsGained.reduce((prev, cur) => prev + cur[1], 0);\r\n let totalManapointsPerSecond = (totalManapointsGained / secondsSimulated).toFixed(2);\r\n let totalRow = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [\"Total\", totalManapointsPerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [source, amount] of manapointsGained) {\r\n if (amount == 0) {\r\n continue;\r\n }\r\n\r\n let sourceText;\r\n let sourceFullHrid;\r\n switch (source) {\r\n case \"regen\":\r\n sourceText = \"Regen\";\r\n sourceFullHrid = \"combatStats.mpRegenPer10\";\r\n break;\r\n case \"manaLeech\":\r\n sourceText = \"Mana Leech\";\r\n sourceFullHrid = \"combatStats.manaLeech\";\r\n break;\r\n case \"ripple\":\r\n sourceText = \"Ripple\";\r\n sourceFullHrid = \"combatStats.ripple\";\r\n break;\r\n default:\r\n sourceText = itemDetailMap[source].name;\r\n sourceFullHrid = \"itemNames.\" + source;\r\n break;\r\n }\r\n let manapointsPerSecond = (amount / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * amount) / totalManapointsGained).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [sourceText, manapointsPerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n let ranOutOfManaText = simResult.playerRanOutOfMana[playerToDisplay] ? \"Yes\" : \"No\";\r\n let ranOutOfManaRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Ran out of mana\", ranOutOfManaText]);\r\n ranOutOfManaRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfMana\");\r\n ranOutOfManaRow.lastElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.\" + ranOutOfManaText);\r\n newChildren.push(ranOutOfManaRow);\r\n\r\n if (simResult.playerRanOutOfMana[playerToDisplay]) {\r\n let ranOutOfManaStat = simResult.playerRanOutOfManaTime[playerToDisplay]; // {isOutOfMana: false, startTimeForOutOfMana:0, totalTimeForOutOfMana:0};\r\n let totalTimeForOut = ranOutOfManaStat.totalTimeForOutOfMana + (ranOutOfManaStat.isOutOfMana ? (simResult.simulatedTime - ranOutOfManaStat.startTimeForOutOfMana) : 0);\r\n\r\n let ranOutOfManaStatRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [\r\n \"Run Out Ratio\",\r\n (totalTimeForOut / simResult.simulatedTime * 100).toFixed(2) + \"%\"\r\n ]\r\n );\r\n ranOutOfManaStatRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfManaRatio\");\r\n newChildren.push(ranOutOfManaStatRow);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showDamageDone(simResult, playerToDisplay) {\r\n let totalDamageDone = {};\r\n let enemyIndex = 1;\r\n\r\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n for (let i = 1; i < 64; i++) {\r\n let accordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + i);\r\n hideElement(accordion);\r\n }\r\n\r\n let bossTimeHeadingDiv = document.getElementById(\"simulationBossTimeHeading\");\r\n bossTimeHeadingDiv.classList.add(\"d-none\");\r\n let bossTimeDiv = document.getElementById(\"simulationBossTime\");\r\n bossTimeDiv.classList.add(\"d-none\");\r\n\r\n if (!simResult.attacks[playerToDisplay]) {\r\n return;\r\n }\r\n\r\n for (const [target, abilities] of Object.entries(simResult.attacks[playerToDisplay])) {\r\n let targetDamageDone = {};\r\n\r\n const i = simResult.timeSpentAlive.findIndex(e => e.name === target);\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\r\n\r\n for (const [ability, abilityCasts] of Object.entries(abilities)) {\r\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\r\n let misses = abilityCasts[\"miss\"] ?? 0;\r\n let damage = Object.entries(abilityCasts)\r\n .filter((entry) => entry[0] != \"miss\")\r\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\r\n\r\n targetDamageDone[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n if (totalDamageDone[ability]) {\r\n totalDamageDone[ability].casts += casts;\r\n totalDamageDone[ability].misses += misses;\r\n totalDamageDone[ability].damage += damage;\r\n } else {\r\n totalDamageDone[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n }\r\n }\r\n\r\n let resultDiv = document.getElementById(\"simulationResultDamageDoneEnemy\" + enemyIndex);\r\n createDamageTable(resultDiv, targetDamageDone, aliveSecondsSimulated);\r\n\r\n let resultAccordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + enemyIndex);\r\n showElement(resultAccordion);\r\n\r\n let resultAccordionButton = document.getElementById(\r\n \"buttonSimulationResultDamageDoneAccordionEnemy\" + enemyIndex\r\n );\r\n let targetName = combatMonsterDetailMap[target].name;\r\n resultAccordionButton.innerHTML = \"Damage Done (\" + \"\" + targetName + \"\" + \")\";\r\n\r\n if (simResult.bossSpawns.includes(target)) {\r\n let hoursSpentOnBoss = (aliveSecondsSimulated / 60 / 60).toFixed(2);\r\n let percentSpentOnBoss = (aliveSecondsSimulated / totalSecondsSimulated * 100).toFixed(2);\r\n\r\n let bossRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [targetName, hoursSpentOnBoss + \"h(\" + percentSpentOnBoss + \"%)\"]);\r\n bossRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + target);\r\n bossTimeDiv.replaceChildren(bossRow);\r\n\r\n bossTimeHeadingDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.classList.remove(\"d-none\");\r\n }\r\n\r\n enemyIndex++;\r\n }\r\n\r\n if (simResult.isDungeon) {\r\n let newChildren = [];\r\n for (const waveName of simResult.bossSpawns) {\r\n // waveName is something like \"#15,/monsters/jackalope,/monsters/butterjerry\"\r\n let waveNumber = waveName.split(\",\")[0];\r\n const idx = simResult.timeSpentAlive.findIndex(e => e.name === waveNumber);\r\n if (idx == -1 || simResult.timeSpentAlive[idx].count == 0) {\r\n continue;\r\n }\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[idx].timeSpentAlive / ONE_SECOND / simResult.timeSpentAlive[idx].count;\r\n let bossRow = createRow([\"col-md-6\", \"col-md-2\", \"col-md-4 text-end\"], [waveNumber, simResult.timeSpentAlive[idx].count, aliveSecondsSimulated.toFixed(1) + \"s\"]);\r\n newChildren.push(bossRow);\r\n }\r\n if (newChildren.length > 0) {\r\n bossTimeHeadingDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.replaceChildren(...newChildren);\r\n }\r\n }\r\n\r\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageDone\");\r\n createDamageTable(totalResultDiv, totalDamageDone, totalSecondsSimulated);\r\n}\r\n\r\nfunction showDamageTaken(simResult, playerToDisplay) {\r\n let totalDamageTaken = {};\r\n let enemyIndex = 1;\r\n\r\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n for (let i = 1; i < 64; i++) {\r\n let accordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + i);\r\n hideElement(accordion);\r\n }\r\n\r\n for (const [source, targets] of Object.entries(simResult.attacks)) {\r\n const validSources = [\"player1\", \"player2\", \"player3\", \"player4\", \"player5\"];\r\n if (validSources.includes(source)) {\r\n continue;\r\n }\r\n const i = simResult.timeSpentAlive.findIndex(e => e.name === source);\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\r\n let sourceDamageTaken = {};\r\n if (targets[playerToDisplay] && Object.keys(targets[playerToDisplay]).length > 0) {\r\n for (const [ability, abilityCasts] of Object.entries(targets[playerToDisplay])) {\r\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\r\n let misses = abilityCasts[\"miss\"] ?? 0;\r\n let damage = Object.entries(abilityCasts)\r\n .filter((entry) => entry[0] != \"miss\")\r\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\r\n\r\n sourceDamageTaken[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n if (totalDamageTaken[ability]) {\r\n totalDamageTaken[ability].casts += casts;\r\n totalDamageTaken[ability].misses += misses;\r\n totalDamageTaken[ability].damage += damage;\r\n } else {\r\n totalDamageTaken[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n }\r\n }\r\n }\r\n\r\n let resultDiv = document.getElementById(\"simulationResultDamageTakenEnemy\" + enemyIndex);\r\n createDamageTable(resultDiv, sourceDamageTaken, aliveSecondsSimulated);\r\n\r\n let resultAccordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + enemyIndex);\r\n showElement(resultAccordion);\r\n\r\n let resultAccordionButton = document.getElementById(\r\n \"buttonSimulationResultDamageTakenAccordionEnemy\" + enemyIndex\r\n );\r\n let sourceName = combatMonsterDetailMap[source].name;\r\n resultAccordionButton.innerHTML = \"Damage Taken (\" + \"\" + sourceName + \"\" + \")\";\r\n\r\n enemyIndex++;\r\n }\r\n\r\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageTaken\");\r\n createDamageTable(totalResultDiv, totalDamageTaken, totalSecondsSimulated);\r\n}\r\n\r\nfunction createDamageTable(resultDiv, damageDone, secondsSimulated) {\r\n let newChildren = [];\r\n\r\n let sortedDamageDone = Object.entries(damageDone).sort((a, b) => b[1].damage - a[1].damage);\r\n\r\n let totalCasts = sortedDamageDone.reduce((prev, cur) => prev + cur[1].casts, 0);\r\n let totalMisses = sortedDamageDone.reduce((prev, cur) => prev + cur[1].misses, 0);\r\n let totalDamage = sortedDamageDone.reduce((prev, cur) => prev + cur[1].damage, 0);\r\n let totalHitChance = ((100 * (totalCasts - totalMisses)) / totalCasts).toFixed(1);\r\n let totalDamagePerSecond = (totalDamage / secondsSimulated).toFixed(2);\r\n\r\n let totalRow = createRow(\r\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\r\n [\"Total\", totalHitChance + \"%\", totalDamagePerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [ability, damageInfo] of sortedDamageDone) {\r\n let abilityText;\r\n let abilityFullHrid;\r\n switch (ability) {\r\n case \"autoAttack\":\r\n abilityText = \"Auto Attack\";\r\n abilityFullHrid = \"combatUnit.autoAttack\";\r\n break;\r\n case \"parry\":\r\n abilityText = \"Parry Attack\";\r\n abilityFullHrid = \"common:simulationResults.parryAttack\";\r\n break;\r\n case \"damageOverTime\":\r\n abilityText = \"Damage Over Time\";\r\n abilityFullHrid = \"common:simulationResults.damageOverTime\";\r\n break;\r\n case \"physicalThorns\":\r\n abilityText = \"Physical Thorns\";\r\n abilityFullHrid = \"combatStats.physicalThorns\";\r\n break;\r\n case \"elementalThorns\":\r\n abilityText = \"Elemental Thorns\";\r\n abilityFullHrid = \"combatStats.elementalThorns\";\r\n break;\r\n case \"retaliation\":\r\n abilityText = \"Retaliation\";\r\n abilityFullHrid = \"combatStats.retaliation\";\r\n break;\r\n case 'blaze':\r\n abilityText = \"Blaze\";\r\n abilityFullHrid = \"combatStats.blaze\";\r\n break;\r\n default:\r\n abilityText = abilityDetailMap[ability].name;\r\n abilityFullHrid = \"abilityNames.\" + ability;\r\n break;\r\n }\r\n\r\n let hitChance = ((100 * (damageInfo.casts - damageInfo.misses)) / damageInfo.casts).toFixed(1);\r\n let damagePerSecond = (damageInfo.damage / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * damageInfo.damage) / totalDamage).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\r\n [abilityText, hitChance + \"%\", damagePerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", abilityFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction createRow(columnClassNames, columnValues) {\r\n let row = createElement(\"div\", \"row\");\r\n\r\n for (let i = 0; i < columnClassNames.length; i++) {\r\n let column = createElement(\"div\", columnClassNames[i], columnValues[i]);\r\n row.appendChild(column);\r\n }\r\n\r\n return row;\r\n}\r\n\r\nfunction createElement(tagName, className, innerHTML = \"\", id = \"\") {\r\n let element = document.createElement(tagName);\r\n element.className = className;\r\n element.innerHTML = innerHTML;\r\n if (id) element.id = id;\r\n return element;\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Simulation Controls\r\n\r\ndocument.addEventListener('DOMContentLoaded', function () {\r\n const simDungeonToggle = document.getElementById('simDungeonToggle');\r\n const playerContainer = document.getElementById('playerCheckBox');\r\n\r\n function addPlayers() {\r\n const player4 = document.createElement('div');\r\n player4.classList.add('form-check');\r\n player4.innerHTML = `\r\n \r\n \r\n `;\r\n\r\n const player5 = document.createElement('div');\r\n player5.classList.add('form-check');\r\n player5.innerHTML = `\r\n \r\n \r\n `;\r\n\r\n playerContainer.appendChild(player4);\r\n playerContainer.appendChild(player5);\r\n }\r\n\r\n function removePlayers() {\r\n const player4 = document.getElementById('player4');\r\n const player5 = document.getElementById('player5');\r\n if (player4) player4.parentElement.remove();\r\n if (player5) player5.parentElement.remove();\r\n }\r\n\r\n function updatePlayerNames() {\r\n const tabLinks = document.querySelectorAll('#playerTab .nav-link');\r\n tabLinks.forEach((tabLink, index) => {\r\n const label = document.querySelector(`label[for=\"player${index + 1}\"]`);\r\n if (label) {\r\n label.textContent = tabLink.textContent.trim();\r\n }\r\n });\r\n }\r\n\r\n function updatePlayersCheckbox(isCheck) {\r\n const boxes = playerContainer.querySelectorAll('.player-checkbox');\r\n boxes.forEach((checkBox) => { checkBox.checked = isCheck });\r\n }\r\n\r\n function updateDifficultySelect(isCheck) {\r\n const difficultySelect = document.getElementById('selectDifficulty');\r\n // disable last four option\r\n if (isCheck && Number(difficultySelect.value) >= 3) {\r\n difficultySelect.value = 0;\r\n }\r\n for (let i = 3; i < difficultySelect.options.length; i++) {\r\n difficultySelect.options[i].disabled = isCheck;\r\n }\r\n }\r\n\r\n simDungeonToggle.addEventListener('change', function () {\r\n if (simDungeonToggle.checked) {\r\n addPlayers();\r\n updatePlayersCheckbox(true);\r\n updateDifficultySelect(true);\r\n } else {\r\n removePlayers();\r\n updatePlayersCheckbox(false);\r\n updateDifficultySelect(false);\r\n }\r\n updatePlayerNames();\r\n });\r\n\r\n document.getElementById('buttonSimulationSetup').addEventListener('click', function () {\r\n updatePlayerNames();\r\n });\r\n});\r\n\r\nfunction onTabChange(event) {\r\n const nextPlayerTabId = event.target.getAttribute('href').substring(7);\r\n savePreviousPlayer(currentPlayerTabId);\r\n updateNextPlayer(nextPlayerTabId);\r\n currentPlayerTabId = nextPlayerTabId;\r\n updateState();\r\n updateUI();\r\n if (Object.keys(currentSimResults).length !== 0) {\r\n showSimulationResult(currentSimResults);\r\n }\r\n\r\n updateContent();\r\n}\r\n\r\ndocument.querySelectorAll('#playerTab .nav-link').forEach(tab => {\r\n tab.addEventListener('shown.bs.tab', onTabChange);\r\n});\r\n\r\nfunction initSimulationControls() {\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n simulationTimeInput.value = 24;\r\n\r\n buttonStartSimulation.addEventListener(\"click\", (event) => {\r\n let invalidElements = document.querySelectorAll(\":invalid\");\r\n if (invalidElements.length > 0) {\r\n invalidElements.forEach((element) => element.reportValidity());\r\n return;\r\n }\r\n savePreviousPlayer(currentPlayerTabId);\r\n\r\n const simDungeonToggle = document.getElementById(\"simDungeonToggle\");\r\n const checkboxes = document.querySelectorAll('.player-checkbox');\r\n selectedPlayers = [];\r\n checkboxes.forEach(checkbox => {\r\n if (checkbox.checked) {\r\n const playerNumber = parseInt(checkbox.id.replace('player', ''));\r\n selectedPlayers.push(playerNumber);\r\n }\r\n });\r\n\r\n if (selectedPlayers.length === 0) {\r\n alert(\"You need to select at least one player to sim.\");\r\n return;\r\n }\r\n // buttonStartSimulation.disabled = true;\r\n buttonStopSimulation.style.display = 'block';\r\n startSimulation(selectedPlayers);\r\n });\r\n\r\n buttonStopSimulation.style.display = 'none';\r\n buttonStopSimulation.addEventListener(\"click\", (event) => {\r\n progressbar.style.width = \"0%\";\r\n progressbar.innerHTML = \"0%\";\r\n if (worker) {\r\n worker.terminate();\r\n }\r\n worker = new Worker(new URL(\"worker.js\", import.meta.url));\r\n\r\n if (multiWorker) {\r\n multiWorker.terminate();\r\n }\r\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n\r\n for (let worker of workerPool) {\r\n worker.worker.terminate();\r\n }\r\n\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n });\r\n}\r\n\r\nfunction startSimulation(selectedPlayers) {\r\n let playersToSim = [];\r\n for (let j = 1; j < 6; j++) {\r\n if (selectedPlayers.includes(j)) {\r\n updateNextPlayer(j);\r\n updateState();\r\n updateUI();\r\n player.hrid = \"player\" + j.toString();\r\n for (let i = 0; i < 3; i++) {\r\n if (food[i] && i < player.combatDetails.combatStats.foodSlots) {\r\n let consumable = new Consumable(food[i], triggerMap[food[i]]);\r\n player.food[i] = consumable;\r\n } else {\r\n player.food[i] = null;\r\n }\r\n\r\n if (drinks[i] && i < player.combatDetails.combatStats.drinkSlots) {\r\n let consumable = new Consumable(drinks[i], triggerMap[drinks[i]]);\r\n player.drinks[i] = consumable;\r\n } else {\r\n player.drinks[i] = null;\r\n }\r\n }\r\n\r\n for (let i = 0; i < 5; i++) {\r\n if (abilities[i] && player.intelligenceLevel >= abilitySlotsLevelRequirementList[i + 1]) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let ability = new Ability(abilities[i], Number(abilityLevelInput.value), triggerMap[abilities[i]]);\r\n player.abilities[i] = ability;\r\n } else {\r\n player.abilities[i] = null;\r\n }\r\n }\r\n\r\n playersToSim.push(structuredClone(player));\r\n }\r\n }\r\n updateNextPlayer(currentPlayerTabId);\r\n updateState();\r\n updateUI();\r\n\r\n let maxPlayerCombatLevel = 1;\r\n for (let player of playersToSim) {\r\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\r\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\r\n }\r\n\r\n for (let player of playersToSim) {\r\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\r\n const maxDebuffOnLevelGap = 0.9;\r\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\r\n\r\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\r\n\r\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\r\n }\r\n else {\r\n player.debuffOnLevelGap = 0;\r\n }\r\n }\r\n\r\n let extra = {};\r\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\r\n extra.comExp = 0;\r\n if (document.getElementById(\"comExpToggle\").checked) {\r\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\r\n }\r\n extra.comDrop = 0;\r\n if (document.getElementById(\"comDropToggle\").checked) {\r\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\r\n }\r\n\r\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\r\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\r\n let simDungeonToggle = document.getElementById(\"simDungeonToggle\");\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let dungeonSelect = document.getElementById(\"selectDungeon\");\r\n let difficultySelect = document.getElementById(\"selectDifficulty\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let simulationTimeLimit = Number(simulationTimeInput.value) * ONE_HOUR;\r\n buttonStopSimulation.style.display = 'block';\r\n if (!simAllZonesToggle.checked && !simAllSoloToggle.checked) {\r\n let zoneHrid = zoneSelect.value;\r\n let difficultyTier = Number(difficultySelect.value);\r\n if (simDungeonToggle.checked) {\r\n zoneHrid = dungeonSelect.value;\r\n }\r\n let workerMessage = {\r\n type: \"start_simulation\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n simStartTime = Date.now();\r\n if (!worker) {\r\n worker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n }\r\n worker.onmessage = onWorkerMessage;\r\n worker.postMessage(workerMessage);\r\n } else {\r\n let targetHrids = {};\r\n\r\n if (simAllZonesToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1 &&\r\n document.getElementById(a.hrid)?.checked\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n if (simAllSoloToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount === 1 &&\r\n document.getElementById(a.hrid)?.checked\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n let simHrids = Object.values(targetHrids)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .map(action => {\r\n let result = [];\r\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\r\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\r\n }\r\n return result;\r\n })\r\n .flat();\r\n\r\n let workerMessage = {\r\n type: \"start_simulation_all_zones\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zones: simHrids,\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra: extra\r\n };\r\n simStartTime = Date.now();\r\n if (!multiWorker) {\r\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n }\r\n multiWorker.onmessage = onMultiWorkerMessage;\r\n multiWorker.postMessage(workerMessage);\r\n }\r\n}\r\n\r\nfunction parsePlayerJson(playerJson, hrid) {\r\n let playerData = {\r\n hrid: hrid,\r\n food: [],\r\n drinks: [],\r\n abilities: [],\r\n ...playerJson.player,\r\n houseRooms: playerJson.houseRooms,\r\n };\r\n playerData.equipment = {};\r\n const triggerMap = playerJson.triggerMap;\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"main_hand\", \"two_hand\", \"charm\"].forEach((type) => {\r\n let currentEquipment = playerJson.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment){\r\n playerData.equipment[`/equipment_types/${type}`] = new Equipment(currentEquipment.itemHrid, currentEquipment.enhancementLevel);\r\n }\r\n });\r\n\r\n for (const foodHrid of playerJson.food[\"/action_types/combat\"]) {\r\n if (foodHrid.itemHrid === \"\") continue;\r\n const food = new Consumable(foodHrid.itemHrid, triggerMap[foodHrid.itemHrid]);\r\n playerData.food.push(food);\r\n }\r\n for (const drinkHrid of playerJson.drinks[\"/action_types/combat\"]) {\r\n if (drinkHrid.itemHrid === \"\") continue;\r\n const drink = new Consumable(drinkHrid.itemHrid, triggerMap[drinkHrid.itemHrid]);\r\n playerData.drinks.push(drink);\r\n }\r\n for (const ability of playerJson.abilities) {\r\n if (ability.abilityHrid === \"\") continue;\r\n const abilityLevel = Number(ability.level);\r\n const abilityHrid = ability.abilityHrid;\r\n if (abilityLevel > 0) {\r\n const abilityObj = new Ability(abilityHrid, abilityLevel, triggerMap[abilityHrid]);\r\n playerData.abilities.push(abilityObj);\r\n }\r\n }\r\n const player = Player.createFromDTO(playerData)\r\n player.updateCombatDetails();\r\n player.houseRooms = playerJson.houseRooms;\r\n player.achievements = playerJson.achievements ?? {};\r\n return player;\r\n}\r\n// read JSON file to simulate\r\ndocument.getElementById(\"buttonUploadJSONSimulate\").addEventListener(\"click\", (event) => {\r\n let extra = {};\r\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\r\n extra.comExp = 0;\r\n if (document.getElementById(\"comExpToggle\").checked) {\r\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\r\n }\r\n extra.comDrop = 0;\r\n if (document.getElementById(\"comDropToggle\").checked) {\r\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\r\n }\r\n\r\n let fileInput = document.getElementById(\"inputUploadJSONSimulation\");\r\n let file = fileInput.files[0];\r\n if (!file) {\r\n alert(\"Please select a file to upload.\");\r\n return;\r\n }\r\n\r\n let reader = new FileReader();\r\n reader.onload = function (event) {\r\n let fileContent = event.target.result;\r\n const jsonDataList = JSON.parse(fileContent);\r\n try {\r\n const simDataList = [];\r\n for (const key in jsonDataList) {\r\n if (jsonDataList[key].cases) {\r\n const cases = getProductCases(jsonDataList[key], jsonDataList[key].cases);\r\n simDataList.push(...cases);\r\n } else {\r\n simDataList.push(jsonDataList[key]);\r\n }\r\n }\r\n for (const key in simDataList) {\r\n const jsonData = simDataList[key];\r\n if (!jsonData || !jsonData.zone || !jsonData.players) {\r\n alert(\"Invalid JSON file format. Please ensure it contains a 'simulationResult' property.\");\r\n return;\r\n }\r\n const playersToSim = Object.values(jsonData.players).map(\r\n (player, index) => parsePlayerJson(player, `player${index + 1}`)\r\n );\r\n\r\n let maxPlayerCombatLevel = 1;\r\n for (let player of playersToSim) {\r\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\r\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\r\n }\r\n\r\n for (let player of playersToSim) {\r\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\r\n const maxDebuffOnLevelGap = 0.9;\r\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\r\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\r\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\r\n }\r\n else {\r\n player.debuffOnLevelGap = 0;\r\n }\r\n }\r\n\r\n const simulationTimeLimit = (jsonData.simulationTimeLimit || 24) * ONE_HOUR;\r\n const simName = jsonData.name || `Json ${key}`;\r\n const zoneHrid = jsonData.zone;\r\n if (zoneHrid === \"all\") {\r\n let targetHrids = {};\r\n\r\n if (simAllZonesToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n let simHrids = Object.values(targetHrids)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .map(action => {\r\n let result = [];\r\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\r\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\r\n }\r\n return result;\r\n })\r\n .flat();\r\n\r\n let workerMessage = {\r\n simulationName: simName,\r\n type: \"start_simulation_all_zones\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zones: simHrids,\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \r\n worker.onmessage = mainWorkerOnMessage;\r\n worker.postMessage(workerMessage);\r\n customAlert(\"Simulation task Created\", \"info\")\r\n workerPool.push({\r\n workerId: workerMessage.workerId,\r\n worker: worker,\r\n });\r\n } else {\r\n let difficultyTier = jsonData.difficultyTier || 0;\r\n let workerMessage = {\r\n simulationName: simName,\r\n type: \"start_simulation\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \r\n worker.onmessage = mainWorkerOnMessage;\r\n worker.postMessage(workerMessage);\r\n customAlert(\"Simulation task Created\", \"info\")\r\n workerPool.push({\r\n workerId: workerMessage.workerId,\r\n worker: worker,\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n // alert(\"Error parsing JSON file: \" + error.message);\r\n customAlert(\"Error parsing JSON file: \" + error.message, \"danger\");\r\n }\r\n }\r\n reader.readAsText(file);\r\n});\r\n\r\n\r\n// #endregion\r\n\r\n// #region WipeEvents\r\n\r\nfunction renderWipeEvents(simResult) {\r\n const selector = document.getElementById('wipeEventSelector');\r\n const logsContainer = document.getElementById('wipeLogsContainer');\r\n const waveBadge = document.getElementById('wipeWaveBadge');\r\n const timeInfo = document.getElementById('wipeTimeInfo');\r\n\r\n selector.innerHTML = '';\r\n logsContainer.innerHTML = '';\r\n\r\n if (!simResult.wipeEvents || simResult.wipeEvents.length === 0) {\r\n selector.innerHTML = ``;\r\n logsContainer.innerHTML = `
No Wipe Events Detected
`;\r\n waveBadge.textContent = '';\r\n timeInfo.textContent = '';\r\n return;\r\n }\r\n\r\n simResult.wipeEvents.forEach((event, index) => {\r\n const wave = event.wave || '?';\r\n // const time = (event.simulationTime / 1e9).toFixed(2);\r\n // const timestamp = new Date(event.timestamp).toLocaleTimeString();\r\n\r\n const option = document.createElement('option');\r\n option.value = index;\r\n option.textContent = `#${index + 1} - 波次: ${wave}`;\r\n selector.appendChild(option);\r\n });\r\n\r\n selector.value = 0;\r\n renderSelectedWipeEvent(0, simResult);\r\n\r\n selector.addEventListener('change', () => {\r\n renderSelectedWipeEvent(selector.value, simResult);\r\n });\r\n}\r\n\r\n// 渲染选中的团灭事件\r\nfunction renderSelectedWipeEvent(index, simResult) {\r\n const logsContainer = document.getElementById('wipeLogsContainer');\r\n const waveBadge = document.getElementById('wipeWaveBadge');\r\n const timeInfo = document.getElementById('wipeTimeInfo');\r\n\r\n logsContainer.innerHTML = '';\r\n\r\n if (index < 0 || index >= simResult.wipeEvents.length) {\r\n logsContainer.innerHTML = `
No Wipe Events
`;\r\n waveBadge.textContent = '';\r\n timeInfo.textContent = '';\r\n return;\r\n }\r\n\r\n const wipeEvent = simResult.wipeEvents[index];\r\n const wave = wipeEvent.wave || '?';\r\n const time = (wipeEvent.simulationTime / 1e9).toFixed(2);\r\n const timestamp = new Date(wipeEvent.timestamp).toLocaleString();\r\n\r\n waveBadge.textContent = `波次: ${wave}`;\r\n timeInfo.textContent = `模拟时间: ${time}s | 记录时间: ${timestamp}`;\r\n\r\n const logsByTime = groupLogsByTime(wipeEvent.logs);\r\n\r\n const baseTime = logsByTime.length > 0 ? logsByTime[0].time : 0;\r\n\r\n logsByTime.forEach(group => {\r\n const timeGroupElement = document.createElement('div');\r\n timeGroupElement.className = 'log-time-group';\r\n\r\n const relativeTime = (group.time - baseTime) / 1e9;\r\n\r\n // 时间标题\r\n const timeHeader = document.createElement('div');\r\n timeHeader.className = 'log-time-header';\r\n timeHeader.textContent = `[${relativeTime.toFixed(2)}s] [Wave#${group.wave}]`;\r\n timeGroupElement.appendChild(timeHeader);\r\n\r\n // 事件列表\r\n const eventsList = document.createElement('div');\r\n eventsList.className = 'log-events';\r\n\r\n const damagedPlayers = new Set();\r\n\r\n group.logs.forEach(log => {\r\n const eventElement = document.createElement('div');\r\n eventElement.className = 'log-event';\r\n\r\n damagedPlayers.add(log.target);\r\n\r\n const sourceSpan = document.createElement('span');\r\n sourceSpan.className = 'log-source';\r\n if (log.ability === \"damageOverTime\") {\r\n sourceSpan.textContent = log.target;\r\n } else if(log.source == 'UNKNOWN_SOURCE') {\r\n sourceSpan.textContent = 'UNKNOWN';\r\n } else {\r\n sourceSpan.setAttribute('data-i18n', `monsterNames.${log.source}`);\r\n sourceSpan.textContent = log.source;\r\n }\r\n\r\n const castSpan = document.createElement('span');\r\n castSpan.className = 'log-cast';\r\n castSpan.setAttribute('data-i18n', `common:cast`);\r\n castSpan.textContent = ' cast ';\r\n\r\n const abilitySpan = document.createElement('span');\r\n abilitySpan.className = 'log-ability';\r\n if (log.ability === \"autoAttack\") {\r\n abilitySpan.setAttribute('data-i18n', 'combatUnit.autoAttack');\r\n abilitySpan.textContent = 'Auto Attack';\r\n } else if (log.ability === \"physicalThorns\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.physicalThorns`);\r\n abilitySpan.textContent = 'Physical Thorns';\r\n } else if (log.ability === \"elementalThorns\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.elementalThorns`);\r\n abilitySpan.textContent = 'Elemental Thorns';\r\n } else if (log.ability === \"retaliation\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.retaliation`);\r\n abilitySpan.textContent = 'Retaliation';\r\n } else if (log.ability === \"damageOverTime\") {\r\n abilitySpan.setAttribute('data-i18n', `common:simulationResults.damageOverTime`);\r\n abilitySpan.textContent = 'Damage Over Time';\r\n } else {\r\n abilitySpan.setAttribute('data-i18n', `abilityNames.${log.ability}`);\r\n abilitySpan.textContent = log.ability;\r\n }\r\n\r\n const toSpan = document.createElement('span');\r\n toSpan.className = 'log-to';\r\n toSpan.setAttribute('data-i18n', `common:to`);\r\n toSpan.textContent = ' to ';\r\n\r\n const targetSpan = document.createElement('span');\r\n targetSpan.className = 'log-target';\r\n targetSpan.textContent = log.target;\r\n\r\n const dealDamageSpan = document.createElement('span');\r\n dealDamageSpan.className = 'log-deal-damage';\r\n dealDamageSpan.setAttribute('data-i18n', `common:dealDamage`);\r\n dealDamageSpan.textContent = ' deal damage ';\r\n\r\n const damageDoneSpan = document.createElement('span');\r\n damageDoneSpan.className = 'log-damage-done';\r\n damageDoneSpan.textContent = log.damage;\r\n if (log.isCrit) {\r\n damageDoneSpan.style.fontWeight = 'bold';\r\n damageDoneSpan.textContent += '!!!';\r\n }\r\n\r\n eventElement.appendChild(sourceSpan);\r\n eventElement.appendChild(castSpan);\r\n eventElement.appendChild(abilitySpan);\r\n eventElement.appendChild(toSpan);\r\n eventElement.appendChild(targetSpan);\r\n eventElement.appendChild(dealDamageSpan);\r\n eventElement.appendChild(damageDoneSpan);\r\n eventElement.appendChild(document.createTextNode(` , HP ${log.beforeHp} → ${log.afterHp}`));\r\n\r\n eventsList.appendChild(eventElement);\r\n });\r\n\r\n timeGroupElement.appendChild(eventsList);\r\n\r\n const lastLog = group.logs[group.logs.length - 1];\r\n const playersHpElement = document.createElement('div');\r\n\r\n const playerHpTitle = document.createElement('span');\r\n playerHpTitle.className = 'log-players-hp';\r\n playerHpTitle.setAttribute('data-i18n', `common:playersHp`);\r\n playerHpTitle.textContent = 'Players HP: ';\r\n playersHpElement.appendChild(playerHpTitle);\r\n\r\n lastLog.playersHp.forEach((player, idx) => {\r\n const playerElement = document.createElement('span');\r\n playerElement.className = 'log-player-hp';\r\n playerElement.textContent = `${player.hrid}: ${player.current}/${player.max}`;\r\n\r\n if (player.current <= 0) {\r\n playerElement.style.color = darkModeToggle.checked ? '#FF6347' : '#CC0000';\r\n } else if (damagedPlayers.has(player.hrid)) {\r\n playerElement.style.color = darkModeToggle.checked ? '#00BFFF' : '#007BFF';\r\n }\r\n\r\n if (idx > 0) {\r\n playersHpElement.appendChild(document.createTextNode(' | '));\r\n }\r\n playersHpElement.appendChild(playerElement);\r\n });\r\n const spacer = document.createElement('div');\r\n spacer.style.height = '15px';\r\n logsContainer.appendChild(spacer);\r\n timeGroupElement.appendChild(playersHpElement);\r\n logsContainer.appendChild(timeGroupElement);\r\n });\r\n\r\n // 更新汉化\r\n updateContent()\r\n}\r\n\r\n// 按时间分组日志\r\nfunction groupLogsByTime(logs) {\r\n const groups = [];\r\n let currentGroup = null;\r\n\r\n logs.forEach(log => {\r\n if (!currentGroup || currentGroup.time !== log.time) {\r\n currentGroup = {\r\n time: log.time,\r\n wave: log.wave,\r\n logs: [log]\r\n };\r\n groups.push(currentGroup);\r\n } else {\r\n currentGroup.logs.push(log);\r\n }\r\n });\r\n\r\n groups.forEach(group => {\r\n let hpMap = {};\r\n if (group.logs.length > 0) {\r\n group.logs[0].playersHp.forEach(p => {\r\n hpMap[p.hrid] = { current: p.current, max: p.max };\r\n });\r\n }\r\n group.logs.forEach(log => {\r\n if (hpMap[log.target]) {\r\n hpMap[log.target].current = log.afterHp;\r\n }\r\n });\r\n group.logs.forEach(log => {\r\n log.playersHp = Object.entries(hpMap).map(([hrid, val]) => ({\r\n hrid,\r\n current: val.current,\r\n max: val.max\r\n }));\r\n });\r\n });\r\n\r\n return groups;\r\n}\r\n\r\n// #endregion\r\n\r\n\r\n// #region Equipment Sets\r\n\r\nfunction initEquipmentSetsModal() {\r\n let equipmentSetsModal = document.getElementById(\"equipmentSetsModal\");\r\n equipmentSetsModal.addEventListener(\"show.bs.modal\", equipmentSetsModalShownHandler);\r\n\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n equipmentSetNameInput.addEventListener(\"input\", (event) => equipmentSetNameChangedHandler(event));\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.addEventListener(\"click\", createNewEquipmentSetHandler);\r\n}\r\n\r\nfunction equipmentSetsModalShownHandler() {\r\n resetNewEquipmentSetControls();\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction resetNewEquipmentSetControls() {\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n equipmentSetNameInput.value = \"\";\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.disabled = true;\r\n}\r\n\r\nfunction updateEquipmentSetList() {\r\n let newChildren = [];\r\n let equipmentSets = loadEquipmentSets();\r\n\r\n for (const equipmentSetName of Object.keys(equipmentSets)) {\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let nameCol = createElement(\"div\", \"col align-self-center\", equipmentSetName);\r\n row.appendChild(nameCol);\r\n\r\n let loadButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let loadButton = createElement(\"button\", \"btn btn-primary\", \"Load\");\r\n loadButton.setAttribute(\"data-i18n\", \"common:controls.load\");\r\n loadButton.setAttribute(\"type\", \"button\");\r\n loadButton.addEventListener(\"click\", (_) => loadEquipmentSetHandler(equipmentSetName));\r\n loadButtonCol.appendChild(loadButton);\r\n row.appendChild(loadButtonCol);\r\n\r\n let saveButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let saveButton = createElement(\"button\", \"btn btn-primary\", \"Save\");\r\n saveButton.setAttribute(\"data-i18n\", \"common:controls.save\");\r\n saveButton.setAttribute(\"type\", \"button\");\r\n saveButton.addEventListener(\"click\", (_) => updateEquipmentSetHandler(equipmentSetName));\r\n saveButtonCol.appendChild(saveButton);\r\n row.appendChild(saveButtonCol);\r\n\r\n let deleteButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let deleteButton = createElement(\"button\", \"btn btn-danger\", \"Delete\");\r\n deleteButton.setAttribute(\"data-i18n\", \"common:controls.delete\");\r\n deleteButton.setAttribute(\"type\", \"button\");\r\n deleteButton.addEventListener(\"click\", (_) => deleteEquipmentSetHandler(equipmentSetName));\r\n deleteButtonCol.appendChild(deleteButton);\r\n row.appendChild(deleteButtonCol);\r\n\r\n newChildren.push(row);\r\n }\r\n\r\n let equipmentSetList = document.getElementById(\"equipmentSetList\");\r\n equipmentSetList.replaceChildren(...newChildren);\r\n\r\n updateContent();\r\n}\r\n\r\nfunction equipmentSetNameChangedHandler(event) {\r\n let invalid = false;\r\n\r\n if (event.target.value.length == 0) {\r\n invalid = true;\r\n }\r\n\r\n let equipmentSets = loadEquipmentSets();\r\n if (equipmentSets[event.target.value]) {\r\n invalid = true;\r\n }\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.disabled = invalid;\r\n}\r\n\r\nfunction createNewEquipmentSetHandler() {\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n let equipmentSetName = equipmentSetNameInput.value;\r\n\r\n let equipmentSet = getEquipmentSetFromUI();\r\n let equipmentSets = loadEquipmentSets();\r\n equipmentSets[equipmentSetName] = equipmentSet;\r\n saveEquipmentSets(equipmentSets);\r\n\r\n resetNewEquipmentSetControls();\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction loadEquipmentSetHandler(name) {\r\n let equipmentSets = loadEquipmentSets();\r\n loadEquipmentSetIntoUI(equipmentSets[name]);\r\n}\r\n\r\nfunction updateEquipmentSetHandler(name) {\r\n let equipmentSet = getEquipmentSetFromUI();\r\n let equipmentSets = loadEquipmentSets();\r\n equipmentSets[name] = equipmentSet;\r\n saveEquipmentSets(equipmentSets);\r\n}\r\n\r\nfunction deleteEquipmentSetHandler(name) {\r\n let equipmentSets = loadEquipmentSets();\r\n delete equipmentSets[name];\r\n saveEquipmentSets(equipmentSets);\r\n\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction loadEquipmentSets() {\r\n return JSON.parse(localStorage.getItem(\"equipmentSets\")) ?? {};\r\n}\r\n\r\nfunction saveEquipmentSets(equipmentSets) {\r\n localStorage.setItem(\"equipmentSets\", JSON.stringify(equipmentSets));\r\n}\r\n\r\nfunction getEquipmentSetFromUI() {\r\n let equipmentSet = {\r\n levels: {},\r\n equipment: {},\r\n food: {},\r\n drinks: {},\r\n abilities: {},\r\n triggerMap: {},\r\n houseRooms: {},\r\n achievements: {},\r\n };\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n equipmentSet.levels[skill] = Number(levelInput.value);\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n\r\n equipmentSet.equipment[type] = {\r\n equipment: equipmentSelect.value,\r\n enhancementLevel: Number(enhancementLevelInput.value),\r\n };\r\n });\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n equipmentSet.food[i] = foodSelect.value;\r\n }\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n equipmentSet.drinks[i] = drinkSelect.value;\r\n }\r\n\r\n for (let i = 0; i < 5; i++) {\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n equipmentSet.abilities[i] = {\r\n ability: abilitySelect.value,\r\n level: Number(abilityLevelInput.value),\r\n };\r\n }\r\n\r\n equipmentSet.triggerMap = triggerMap;\r\n\r\n equipmentSet.houseRooms = player.houseRooms;\r\n equipmentSet.achievements = player.achievements;\r\n\r\n return equipmentSet;\r\n}\r\n\r\nfunction fixTriggerMap(triggerMap) {\r\n let delKeys = []\r\n for (const key of Object.keys(triggerMap)) {\r\n let err = false;\r\n if (null == triggerMap[key]) {\r\n triggerMap[key] = [];\r\n }\r\n for (const trigger of triggerMap[key]) {\r\n if (!combatTriggerConditionDetailMap[trigger.conditionHrid]) {\r\n err = true;\r\n break;\r\n }\r\n }\r\n if (err) {\r\n delKeys.push(key);\r\n }\r\n }\r\n for (const key of delKeys) {\r\n delete triggerMap[key];\r\n }\r\n}\r\n\r\nfunction loadEquipmentSetIntoUI(equipmentSet) {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !equipmentSet.levels[\"meleeLevel\"] && equipmentSet.levels[\"powerLevel\"]) {\r\n equipmentSet.levels[\"meleeLevel\"] = equipmentSet.levels[\"powerLevel\"];\r\n }\r\n levelInput.value = equipmentSet.levels[skill] ?? 1;\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n\r\n let currentEquipment = equipmentSet.equipment[type];\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.equipment;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n foodSelect.value = equipmentSet.food[i];\r\n }\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n drinkSelect.value = equipmentSet.drinks[i].replace(\"power\", \"melee\");\r\n }\r\n\r\n let hasSpecial = false;\r\n if (equipmentSet.abilities && Object.keys(equipmentSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n equipmentSet.abilities[i].ability == \"/abilities/aqua_aura\" ||\r\n equipmentSet.abilities[i].ability == \"/abilities/flame_aura\" ||\r\n equipmentSet.abilities[i].ability == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n equipmentSet.abilities[i].ability = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (equipmentSet.abilities[i].ability == \"/abilities/arcane_reflection\") {\r\n equipmentSet.abilities[i].ability = \"/abilities/retribution\";\r\n }\r\n\r\n abilitySelect.value = equipmentSet.abilities[i].ability;\r\n abilityLevelInput.value = equipmentSet.abilities[i].level;\r\n }\r\n\r\n triggerMap = equipmentSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n\r\n if (equipmentSet.houseRooms) {\r\n for (const room in equipmentSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (equipmentSet.houseRooms[room]) {\r\n field.value = equipmentSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = equipmentSet.houseRooms;\r\n } else {\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n\r\n if (equipmentSet.achievements) {\r\n for (const achievement in equipmentSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (equipmentSet.achievements[achievement]) {\r\n field.checked = true;\r\n } else {\r\n field.checked = false;\r\n }\r\n player.achievements[achievement] = field.checked;\r\n }\r\n } else {\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n refreshAchievementStatics();\r\n\r\n updateState();\r\n updateUI();\r\n\r\n updateContent();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Error Handling\r\n\r\nfunction initErrorHandling() {\r\n window.addEventListener(\"error\", (event) => {\r\n showErrorModal(event.message);\r\n });\r\n\r\n let copyErrorButton = document.getElementById(\"buttonCopyError\");\r\n copyErrorButton.addEventListener(\"click\", (event) => {\r\n let errorInput = document.getElementById(\"inputError\");\r\n navigator.clipboard.writeText(errorInput.value);\r\n });\r\n}\r\n\r\nfunction initImportExportModal() {\r\n let exportSetButton = document.getElementById(\"buttonExportSet\");\r\n exportSetButton.addEventListener(\"click\", (event) => {\r\n savePreviousPlayer(currentPlayerTabId);\r\n const activeTab = document.querySelector('#importTab .nav-link.active');\r\n if (activeTab.id === 'group-combat-tab') {\r\n doGroupExport();\r\n } else if (activeTab.id === 'solo-tab') {\r\n doSoloExport();\r\n }\r\n });\r\n\r\n let importSetButton = document.getElementById(\"buttonImportSet\");\r\n importSetButton.addEventListener(\"click\", (event) => {\r\n const activeTab = document.querySelector('#importTab .nav-link.active');\r\n if (activeTab.id === 'group-combat-tab') {\r\n doGroupImport();\r\n } else if (activeTab.id === 'solo-tab') {\r\n doSoloImport();\r\n }\r\n updateState();\r\n updateUI();\r\n resetImportInputs();\r\n });\r\n}\r\n\r\nfunction resetImportInputs() {\r\n document.getElementById('inputSetGroupCombatAll').value = '';\r\n document.getElementById('inputSetGroupCombatplayer1').value = '';\r\n document.getElementById('inputSetGroupCombatplayer2').value = '';\r\n document.getElementById('inputSetGroupCombatplayer3').value = '';\r\n document.getElementById('inputSetGroupCombatplayer4').value = '';\r\n document.getElementById('inputSetGroupCombatplayer5').value = '';\r\n document.getElementById('inputSetSolo').value = '';\r\n}\r\n\r\nfunction doGroupExport() {\r\n try {\r\n navigator.clipboard.writeText(JSON.stringify(playerDataMap)).then(() => alert(\"Current Group has been copied to clipboard.\"));\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction doSoloExport() {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let equipmentArray = [];\r\n for (const item in player.equipment) {\r\n if (player.equipment[item] != null) {\r\n equipmentArray.push({\r\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\r\n \"itemHrid\": player.equipment[item].hrid,\r\n \"enhancementLevel\": player.equipment[item].enhancementLevel\r\n });\r\n }\r\n }\r\n let playerArray = {\r\n \"attackLevel\": player.attackLevel,\r\n \"magicLevel\": player.magicLevel,\r\n \"meleeLevel\": player.meleeLevel,\r\n \"rangedLevel\": player.rangedLevel,\r\n \"defenseLevel\": player.defenseLevel,\r\n \"staminaLevel\": player.staminaLevel,\r\n \"intelligenceLevel\": player.intelligenceLevel,\r\n \"equipment\": equipmentArray\r\n };\r\n let abilitiesArray = [];\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let abilityName = document.getElementById(\"selectAbility_\" + i);\r\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\r\n }\r\n let drinksArray = [];\r\n for (let i = 0; i < drinks?.length; i++) {\r\n drinksArray.push({ \"itemHrid\": drinks[i] });\r\n }\r\n let foodArray = [];\r\n for (let i = 0; i < food?.length; i++) {\r\n foodArray.push({ \"itemHrid\": food[i] });\r\n }\r\n let state = {\r\n player: playerArray,\r\n food: { \"/action_types/combat\": foodArray },\r\n drinks: { \"/action_types/combat\": drinksArray },\r\n abilities: abilitiesArray,\r\n triggerMap: triggerMap,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n houseRooms: player.houseRooms,\r\n achievements: player.achievements\r\n };\r\n try {\r\n navigator.clipboard.writeText(JSON.stringify(state)).then(() => alert(\"Current set has been copied to clipboard.\"));\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction setPlayerData(playerId, inputElementId) {\r\n const inputElement = document.getElementById(inputElementId);\r\n const value = inputElement ? inputElement.value.trim() : \"\";\r\n\r\n // Only set the value in the map if it's not null, undefined, or empty\r\n if (value) {\r\n playerDataMap[playerId] = value;\r\n return true;\r\n }\r\n return false;\r\n}\r\n\r\nfunction doGroupImport() {\r\n let needUpdateCurrentTab = false;\r\n const value = document.getElementById(\"inputSetGroupCombatAll\")?.value || \"\";\r\n if (!value.trim()) {\r\n for (let i of ['1', '2', '3', '4', '5']) {\r\n if (setPlayerData(i, \"inputSetGroupCombatplayer\" + i) && currentPlayerTabId == i) {\r\n needUpdateCurrentTab = true;\r\n }\r\n }\r\n } else {\r\n playerDataMap = JSON.parse(value);\r\n needUpdateCurrentTab = true;\r\n }\r\n\r\n if (needUpdateCurrentTab) {\r\n updateNextPlayer(currentPlayerTabId);\r\n }\r\n}\r\n\r\nfunction doSoloImport() {\r\n let importSet = document.getElementById(\"inputSetSolo\").value;\r\n importSet = JSON.parse(importSet);\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\r\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\r\n }\r\n levelInput.value = importSet.player[skill + \"Level\"];\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.itemHrid;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\r\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\r\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\r\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\r\n if (mainhandWeapon !== undefined) {\r\n weaponSelect.value = mainhandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\r\n } else if (twohandWeapon !== undefined) {\r\n weaponSelect.value = twohandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\r\n } else {\r\n weaponSelect.value = \"\";\r\n weaponEnhancementLevelInput.value = 0;\r\n }\r\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\r\n importSet.food = importSet.food[\"/action_types/combat\"];\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n if (importSet.drinks[i] != null) {\r\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\r\n } else {\r\n drinkSelect.value = \"\";\r\n }\r\n if (importSet.food[i] != null) {\r\n foodSelect.value = importSet.food[i].itemHrid;\r\n } else {\r\n foodSelect.value = \"\";\r\n }\r\n }\r\n\r\n let hasSpecial = false;\r\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\r\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\r\n }\r\n\r\n if (importSet.abilities[i] != null) {\r\n abilitySelect.value = importSet.abilities[i].abilityHrid;\r\n abilityLevelInput.value = String(importSet.abilities[i].level);\r\n } else {\r\n abilitySelect.value = \"\";\r\n abilityLevelInput.value = \"1\";\r\n }\r\n }\r\n\r\n if (importSet.triggerMap) {\r\n triggerMap = importSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n }\r\n\r\n if (importSet.houseRooms) {\r\n for (const room in importSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (importSet.houseRooms[room]) {\r\n field.value = importSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = importSet.houseRooms;\r\n } else {\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n\r\n if (importSet.achievements) {\r\n for (const achievement in importSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (importSet.achievements[achievement]) {\r\n field.checked = true;\r\n } else {\r\n field.checked = false;\r\n }\r\n player.achievements[achievement] = field.checked;\r\n }\r\n } else {\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n refreshAchievementStatics();\r\n\r\n if (\"zone\" in importSet) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n zoneSelect.value = importSet[\"zone\"];\r\n }\r\n\r\n if (\"simulationTime\" in importSet) {\r\n let simulationDuration = document.getElementById(\"inputSimulationTime\");\r\n simulationDuration.value = importSet[\"simulationTime\"];\r\n }\r\n}\r\n\r\nfunction savePreviousPlayer(playerId) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let equipmentArray = [];\r\n for (const item in player.equipment) {\r\n if (player.equipment[item] != null) {\r\n equipmentArray.push({\r\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\r\n \"itemHrid\": player.equipment[item].hrid,\r\n \"enhancementLevel\": player.equipment[item].enhancementLevel\r\n });\r\n }\r\n }\r\n let playerArray = {\r\n \"attackLevel\": player.attackLevel,\r\n \"magicLevel\": player.magicLevel,\r\n \"meleeLevel\": player.meleeLevel,\r\n \"rangedLevel\": player.rangedLevel,\r\n \"defenseLevel\": player.defenseLevel,\r\n \"staminaLevel\": player.staminaLevel,\r\n \"intelligenceLevel\": player.intelligenceLevel,\r\n \"equipment\": equipmentArray\r\n };\r\n let abilitiesArray = [];\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let abilityName = document.getElementById(\"selectAbility_\" + i);\r\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\r\n }\r\n let drinksArray = [];\r\n for (let i = 0; i < drinks?.length; i++) {\r\n drinksArray.push({ \"itemHrid\": drinks[i] });\r\n }\r\n let foodArray = [];\r\n for (let i = 0; i < food?.length; i++) {\r\n foodArray.push({ \"itemHrid\": food[i] });\r\n }\r\n let state = {\r\n player: playerArray,\r\n food: { \"/action_types/combat\": foodArray },\r\n drinks: { \"/action_types/combat\": drinksArray },\r\n abilities: abilitiesArray,\r\n triggerMap: triggerMap,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n houseRooms: player.houseRooms,\r\n achievements: player.achievements\r\n };\r\n try {\r\n playerDataMap[playerId] = JSON.stringify(state);\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction updateNextPlayer(currentPlayerNumber) {\r\n let playerImportData = playerDataMap[currentPlayerNumber];\r\n let importSet = JSON.parse(playerImportData);\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\r\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\r\n }\r\n levelInput.value = importSet.player[skill + \"Level\"];\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.itemHrid;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\r\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\r\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\r\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\r\n if (mainhandWeapon !== undefined) {\r\n weaponSelect.value = mainhandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\r\n } else if (twohandWeapon !== undefined) {\r\n weaponSelect.value = twohandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\r\n } else {\r\n weaponSelect.value = \"\";\r\n weaponEnhancementLevelInput.value = 0;\r\n }\r\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\r\n importSet.food = importSet.food[\"/action_types/combat\"];\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n if (importSet.drinks[i] != null) {\r\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\r\n } else {\r\n drinkSelect.value = \"\";\r\n }\r\n if (importSet.food[i] != null) {\r\n foodSelect.value = importSet.food[i].itemHrid;\r\n } else {\r\n foodSelect.value = \"\";\r\n }\r\n }\r\n\r\n let hasSpecial = false;\r\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\r\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\r\n }\r\n\r\n if (importSet.abilities[i] != null) {\r\n abilitySelect.value = importSet.abilities[i].abilityHrid;\r\n abilityLevelInput.value = String(importSet.abilities[i].level);\r\n } else {\r\n abilitySelect.value = \"\";\r\n abilityLevelInput.value = \"1\";\r\n }\r\n }\r\n\r\n if (importSet.triggerMap) {\r\n triggerMap = importSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n }\r\n\r\n { // reset all houseRooms\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n if (importSet.houseRooms) {\r\n for (const room in importSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (importSet.houseRooms[room]) {\r\n field.value = importSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = importSet.houseRooms;\r\n }\r\n\r\n { // reset all achievements\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n if (importSet.achievements) {\r\n for (const achievement in importSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (importSet.achievements[achievement]) {\r\n field.checked = true;\r\n player.achievements[achievement] = true;\r\n } else {\r\n field.checked = false;\r\n player.achievements[achievement] = false;\r\n }\r\n }\r\n }\r\n refreshAchievementStatics();\r\n}\r\n\r\nfunction showErrorModal(error) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n\r\n let state = {\r\n error: error,\r\n player: player,\r\n food: food,\r\n drinks: drinks,\r\n abilities: abilities,\r\n triggerMap: triggerMap,\r\n modalTriggers: modalTriggers,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n };\r\n\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n state[\"abilityLevel\" + i] = abilityLevelInput.value;\r\n }\r\n\r\n let errorInput = document.getElementById(\"inputError\");\r\n errorInput.value = JSON.stringify(state);\r\n\r\n let errorModal = new bootstrap.Modal(document.getElementById(\"errorModal\"));\r\n errorModal.show();\r\n}\r\n\r\nwindow.prices;\r\n\r\nasync function fetchPrices() {\r\n let response = null;\r\n try {\r\n response = await fetch('https://www.milkywayidle.com/game_data/marketplace.json'\r\n , {\r\n mode: 'cors'\r\n }\r\n );\r\n if (!response.ok) {\r\n console.log('Error fetching prices');\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n\r\n if (response == null) {\r\n try {\r\n response = await fetch('https://www.milkywayidlecn.com/game_data/marketplace.json'\r\n , {\r\n mode: 'cors'\r\n }\r\n );\r\n if (!response.ok) {\r\n console.log('Error fetching prices');\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n }\r\n\r\n if (!response || !response.ok) {\r\n return;\r\n }\r\n\r\n try {\r\n\r\n let btn = document.querySelector('#buttonGetPrices');\r\n btn.style.backgroundColor = 'green';\r\n\r\n const pricesJson = await response.json();\r\n\r\n const priceTmp = pricesJson['marketData'];\r\n window.prices = {};\r\n for (const item in itemDetailMap) {\r\n const hrid = itemDetailMap[item].hrid;\r\n if (hrid in priceTmp) {\r\n window.prices[hrid] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (priceTmp[hrid]['0']) {\r\n window.prices[hrid].ask = priceTmp[hrid]['0'].a;\r\n window.prices[hrid].bid = priceTmp[hrid]['0'].b;\r\n }\r\n }\r\n } \r\n\r\n window.prices[\"/items/coin\"] = { \"ask\": 1, \"bid\": 1, \"vendor\": 1 };\r\n\r\n window.prices[\"/items/small_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n window.prices[\"/items/medium_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n window.prices[\"/items/large_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n}\r\n\r\ndocument.getElementById(\"buttonGetPrices\").onclick = async () => {\r\n await fetchPrices();\r\n};\r\n\r\ndocument.addEventListener(\"input\", (e) => {\r\n let element = e.target;\r\n if (element.tagName == \"TD\" && element.parentNode.parentNode.parentNode.classList.value.includes('profit-table')) {\r\n let tableId = element.parentNode.parentNode.parentNode.id;\r\n let row = element.parentNode.querySelectorAll('td');\r\n let item = row[0].getAttribute('data-i18n').split('.')[1];\r\n let newPrice = element.innerText;\r\n\r\n let revenueSetting = document.getElementById('selectPrices_drops').value;\r\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\r\n\r\n let expensesDifference = 0;\r\n let revenueDifference = 0;\r\n let noRngRevenueDifference = 0;\r\n\r\n if (tableId == 'expensesTable') {\r\n expensesDifference = updateTable('expensesTable', item, newPrice);\r\n if (revenueSetting == expensesSetting) {\r\n revenueDifference = updateTable('revenueTable', item, newPrice);\r\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\r\n }\r\n if (window.prices) {\r\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (expensesSetting == 'bid') {\r\n window.prices[item]['bid'] = newPrice;\r\n } else {\r\n window.prices[item]['ask'] = newPrice;\r\n }\r\n }\r\n } else {\r\n revenueDifference = updateTable('revenueTable', item, newPrice);\r\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\r\n if (revenueSetting == expensesSetting) {\r\n expensesDifference = updateTable('expensesTable', item, newPrice);\r\n }\r\n if (window.prices) {\r\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (revenueSetting == 'bid') {\r\n window.prices[item]['bid'] = newPrice;\r\n } else {\r\n window.prices[item]['ask'] = newPrice;\r\n }\r\n }\r\n }\r\n\r\n window.expenses += expensesDifference;\r\n document.getElementById('expensesSpan').innerText = window.expenses.toLocaleString();\r\n window.revenue += revenueDifference;\r\n document.getElementById('revenueSpan').innerText = window.revenue.toLocaleString();\r\n window.noRngRevenue += noRngRevenueDifference;\r\n document.getElementById('noRngRevenueSpan').innerText = window.noRngRevenue.toLocaleString();\r\n\r\n window.profit = window.revenue - window.expenses;\r\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\r\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\r\n window.noRngProfit = window.noRngRevenue - window.expenses;\r\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\r\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\r\n }\r\n});\r\n\r\nfunction updateTable(tableId, item, price) {\r\n let row = document.querySelector('#' + tableId + ' .' + CSS.escape(item));\r\n if (row == null) {\r\n return 0;\r\n }\r\n\r\n row = row.querySelectorAll('td');\r\n let priceTd = row[1];\r\n let amountTd = row[2];\r\n let totalTd = row[3];\r\n let oldTotal = totalTd.innerText;\r\n let newTotal = price * amountTd.innerText;\r\n\r\n if (priceTd.innerText != price) {\r\n priceTd.innerText = price;\r\n }\r\n totalTd.innerText = newTotal;\r\n\r\n return newTotal - oldTotal;\r\n}\r\n\r\n// #endregion\r\n\r\nfunction initPatchNotes() {\r\n const patchNotesRows = document.getElementById(\"patchNotes\");\r\n for (const pn in patchNote) {\r\n const patchNoteContainer = document.createElement(\"div\");\r\n patchNotesRows.setAttribute('class', 'col-12 mb-4');\r\n\r\n const patchNoteElement = document.createElement(\"h6\");\r\n patchNoteElement.innerHTML = pn;\r\n const patchNoteList = document.createElement(\"ul\");\r\n for (const note of patchNote[pn]) {\r\n const noteElement = document.createElement(\"li\");\r\n noteElement.innerHTML = note;\r\n patchNoteList.appendChild(noteElement);\r\n }\r\n patchNoteContainer.appendChild(patchNoteElement);\r\n patchNoteContainer.appendChild(patchNoteList);\r\n\r\n patchNotesRows.appendChild(patchNoteContainer);\r\n }\r\n}\r\n\r\nfunction initExtraBuffSection() {\r\n // mooPass\r\n let mooPassToggle = document.getElementById(\"mooPassToggle\");\r\n let mooPass = localStorage.getItem('mooPass');\r\n if (mooPass) {\r\n mooPassToggle.checked = Boolean(mooPass);\r\n }\r\n mooPassToggle.onchange = () => {\r\n localStorage.setItem('mooPass', mooPassToggle.checked);\r\n }\r\n \r\n // comExp\r\n let comExpToggle = document.getElementById(\"comExpToggle\");\r\n let comExpInput = document.getElementById(\"comExpInput\");\r\n let comExp = localStorage.getItem('comExp');\r\n if (comExp) {\r\n let comExpNumber = Number(comExp);\r\n if (comExpNumber > 0) {\r\n comExpToggle.checked = true;\r\n comExpInput.value = comExpNumber;\r\n } else {\r\n comExpToggle.checked = false;\r\n comExpInput.disabled = true;\r\n }\r\n }\r\n const updateComExp = () => {\r\n if (comExpToggle.checked) {\r\n let comExp = Number(comExpInput.value);\r\n localStorage.setItem('comExp', comExp); \r\n comExpInput.disabled = false;\r\n } else {\r\n localStorage.setItem('comExp', 0);\r\n comExpInput.disabled = true;\r\n }\r\n }\r\n comExpToggle.onchange = updateComExp;\r\n comExpInput.onchange = updateComExp;\r\n\r\n // comDrop\r\n let comDropToggle = document.getElementById(\"comDropToggle\");\r\n let comDropInput = document.getElementById(\"comDropInput\");\r\n let comDrop = localStorage.getItem('comDrop');\r\n if (comDrop) {\r\n let comDropNumber = Number(comDrop);\r\n if (comDropNumber > 0) {\r\n comDropToggle.checked = true;\r\n comDropInput.value = comDropNumber;\r\n } else {\r\n comDropToggle.checked = false;\r\n comDropInput.disabled = true;\r\n }\r\n }\r\n const updateComDrop = () => {\r\n if (comDropToggle.checked) {\r\n let comDrop = Number(comDropInput.value);\r\n localStorage.setItem('comDrop', comDrop); \r\n comDropInput.disabled = false;\r\n } else {\r\n localStorage.setItem('comDrop', 0);\r\n comDropInput.disabled = true;\r\n }\r\n }\r\n comDropToggle.onchange = updateComDrop;\r\n comDropInput.onchange = updateComDrop;\r\n}\r\n\r\n\r\nfunction updateState() {\r\n updateEquipmentState();\r\n updateLevels();\r\n updateFoodState();\r\n updateDrinksState();\r\n updateAbilityState();\r\n}\r\n\r\nfunction updateUI() {\r\n updateCombatStatsUI();\r\n updateFoodUI();\r\n updateDrinksUI();\r\n updateAbilityUI();\r\n\r\n updateContent();\r\n}\r\n\r\nconst darkModeToggle = document.getElementById('darkModeToggle');\r\nconst body = document.body;\r\n\r\nif (localStorage.getItem('darkModeEnabled') === 'true') {\r\n body.classList.add('dark-mode');\r\n const tables = document.getElementsByClassName('profit-table');\r\n for (const table of tables) {\r\n table.classList.toggle('table-striped');\r\n }\r\n darkModeToggle.checked = true;\r\n}\r\n\r\ndarkModeToggle.addEventListener('change', () => {\r\n body.classList.toggle('dark-mode');\r\n const tables = document.getElementsByClassName('profit-table');\r\n for (const table of tables) {\r\n table.classList.toggle('table-striped');\r\n }\r\n localStorage.setItem('darkModeEnabled', darkModeToggle.checked);\r\n});\r\n\r\nfunction updateContent() {\r\n document.querySelectorAll('[data-i18n]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n');\r\n if (key) {\r\n element.textContent = i18next.t(key);\r\n }\r\n });\r\n\r\n document.querySelectorAll('[data-i18n-placeholder]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n-placeholder');\r\n if (key) {\r\n element.placeholder = i18next.t(key);\r\n }\r\n });\r\n\r\n document.querySelectorAll('option[data-i18n]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n');\r\n if (key) {\r\n element.textContent = i18next.t(key);\r\n }\r\n });\r\n}\r\n\r\ninitEquipmentSection();\r\ninitHouseRoomsModal();\r\ninitAchievementsModal();\r\ninitLevelSection();\r\ninitFoodSection();\r\ninitDrinksSection();\r\ninitAbilitiesSection();\r\ninitZones();\r\ninitDungeons();\r\ninitTriggerModal();\r\ninitSimulationControls();\r\ninitEquipmentSetsModal();\r\ninitErrorHandling();\r\ninitImportExportModal();\r\ninitDamageDoneTaken();\r\ninitPatchNotes();\r\ninitExtraBuffSection();\r\n\r\nupdateState();\r\nupdateUI();\r\n"],"names":[],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"bundle.js","mappings":";;;;;;;;;;;;;;;;;AAA0B;AACkC;AAC5B;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,wDAAgB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,6CAAI;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;ACxNG;AACkD;AACR;AACpE;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,gEAAwB;AAChE;AACA,0CAA0C,4DAAoB;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,WAAW;;;;;;;;;;;;;;AC1B1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,IAAI,EAAC;;;;;;;;;;;;;;;ACdpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wDAAwD;AACtF,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,mBAAmB;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;;AC/gBA;AAC4B;AACtB;AAChC;AACA;AACA;AACA;AACA;AACA,6BAA6B,qDAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;ACtF4B;AACmD;AACzG;AACA;AACA;AACA;AACA,uBAAuB,qDAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,iFAAoC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS,EAAC;;;;;;;;;;;;;;;;;AC/CC;AACsC;AAChE;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,0DAAkB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS;;;;;;;;;;;;;;;;;;;;AC7BQ;AACM;AACA;AACF;AACA;AACI;AACxC;AACA,qBAAqB,mDAAU;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,kDAAS;AACrD;AACA;AACA,qDAAqD,mDAAU;AAC/D,2DAA2D,mDAAU;AACrE,qEAAqE,gDAAO;AAC5E;AACA;AACA,2CAA2C,kDAAS;AACpD;AACA,SAAS;AACT;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,MAAM,EAAC;;;;;;;;;;;;;;;;ACvLsE;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,wEAAgC;AAC5C;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UCjLvB;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;UAEA;UACA;;;;;WCzBA;WACA;WACA;WACA;WACA,yCAAyC,wCAAwC;WACjF;WACA;WACA;;;;;WCPA;WACA;WACA;WACA;WACA;;;;;WCJA;WACA;WACA;WACA;WACA,GAAG;WACH;WACA;WACA,CAAC;;;;;WCPD;;;;;WCAA;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D;;;;;WCNA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;;;;WClBA;;WAEA;WACA;WACA;WACA;WACA;WACA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrBuD;AACN;AAC2B;AACN;AACU;AAC7B;AACM;AACN;AACyD;AACF;AACE;AACA;AAClC;AACc;AACN;AACE;AACF;AACG;AACF;AACnF;AAC0C;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,2FAA4B;AAC5D,qCAAqC,qHAAiC;AACtE;AACA;AACA;AACA,iBAAiB,kEAAM;AACvB;AACA,IAAI,SAAI;AACR,IAAI,WAAM;AACV,IAAI,cAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,iBAAiB;AACxpC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,iBAAiB;AACxpC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,iBAAiB;AACxpC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,iBAAiB;AACxpC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,iBAAiB;AACxpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA,sCAAsC,qEAAa;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,mCAAmC,0EAAkB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,iFAAkB;AAClD;AACA,4FAA4F,eAAe;AAC3G;AACA;AACA;AACA,uDAAuD,eAAe;AACtE,6BAA6B,KAAK,GAAG,MAAM;AAC3C;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,iFAAkB;AAClD;AACA,sCAAsC,6EAAoB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,eAAe;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oEAAoE,eAAe;AACnF,mCAAmC,eAAe;AAClD;AACA;AACA,gGAAgG,eAAe;AAC/G;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,gCAAgC,eAAe;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,iBAAiB;AACpD;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,iDAAiD,iBAAiB;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,qEAAa;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,qEAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,qEAAS;AACvD,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,6EAAoB;AACvD;AACA;AACA,qBAAqB,4EAAmB;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,sCAAsC,qEAAa;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,SAAI;AACZ,YAAY,SAAI,mBAAmB,SAAI;AACvC,2BAA2B,qEAAa,CAAC,SAAI;AAC7C,uBAAuB,SAAI;AAC3B;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,qFAAqF,SAAI;AACzF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,uCAAuC,qEAAa;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,WAAM;AACd,YAAY,WAAM,mBAAmB,WAAM;AAC3C,2BAA2B,qEAAa,CAAC,WAAM;AAC/C,uBAAuB,WAAM;AAC7B;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,sFAAsF,WAAM;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D,UAAU;AACV,0CAA0C,wEAAgB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,cAAS;AACjB,YAAY,cAAS,mBAAmB,cAAS;AACjD,8BAA8B,wEAAgB,CAAC,cAAS;AACxD,uBAAuB,cAAS;AAChC;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,4DAA4D,yFAAgC;AAC5F,2DAA2D,yFAAgC;AAC3F,4DAA4D,yFAAgC,YAAY,cAAS;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAS;AACjC,IAAI,cAAS,iBAAiB,cAAS;AACvC,IAAI,cAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,cAAS;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,SAAI;AAChC;AACA;AACA,4BAA4B,WAAM;AAClC;AACA;AACA,4BAA4B,cAAS;AACrC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,qEAAa;AACrD,MAAM;AACN,wCAAwC,wEAAgB;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,yFAAgC;AAC5C;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,wFAAgC;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,wFAAgC;AACrD;AACA;AACA;AACA,mCAAmC,uFAA+B;AAClE,MAAM;AACN,mCAAmC,uFAA+B;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uFAA+B;AACnD;AACA,qEAAqE,yFAAgC;AACrG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,wEAAe;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2GAA2G,WAAW;AACtH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,WAAW;AAC5C,yDAAyD,WAAW;AACpE;AACA;AACA;AACA,gEAAgE,WAAW;AAC3E;AACA;AACA,qGAAqG,WAAW;AAChH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qFAAqF,WAAW;AAChG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4GAA4G,WAAW;AACvH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,WAAW;AAC7C,0DAA0D,WAAW;AACrE;AACA;AACA;AACA,iEAAiE,WAAW;AAC5E;AACA;AACA,sGAAsG,WAAW;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sFAAsF,WAAW;AACjG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,OAAO;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uBAAuB;AAC3C,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,+EAAsB;AAClC,+BAA+B,+EAAsB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,iJAAiJ;AAC9L;AACA,gBAAgB,+EAAsB;AACtC,mCAAmC,+EAAsB;AACzD;AACA;AACA;AACA,qDAAqD,uIAAuI;AAC5L;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,+BAA+B;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,UAAU,kCAAkC;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uEAAuE,oDAAoD;AAC3H,iEAAiE,oDAAoD;AACrH,iFAAiF,oDAAoD;AACrI;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,eAAe;AACrC;AACA;AACA;AACA;AACA,0BAA0B,mBAAmB;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,SAAS;AAC3D;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,KAAK;AACL,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,iBAAiB;AACrC;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,+CAA+C,kBAAkB;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,+EAAsB;AACvC;AACA;AACA;AACA,SAAS;AACT;AACA,UAAU,kCAAkC,qEAAqE;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2EAA2E,wEAAgB;AAC3F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa,qEAAa;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,qEAAa;AACjC,iCAAiC,qEAAa;AAC9C;AACA,kBAAkB,SAAS,wEAAgB;AAC3C,iCAAiC,wEAAgB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,qEAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kFAAkF,IAAI;AACtF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,QAAQ;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,QAAQ;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wEAAgB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,6BAA6B;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qEAAqE,UAAU;AAC/E;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,sCAAsC,4BAA4B;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,qCAAqC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC,2FAA4B;AAChE;AACA;AACA;AACA;AACA,yCAAyC,qHAAiC;AAC1E;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,SAAI;AACxB,yCAAyC,sEAAU,CAAC,SAAI,gBAAgB,SAAI;AAC5E;AACA,kBAAkB;AAClB;AACA;AACA;AACA,oBAAoB,WAAM;AAC1B,yCAAyC,sEAAU,CAAC,WAAM,gBAAgB,WAAM;AAChF;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,cAAS,mCAAmC,yFAAgC;AAChG;AACA,sCAAsC,mEAAO,CAAC,cAAS,iDAAiD,cAAS;AACjH;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,oDAAoD;AACxE;AACA;AACA;AACA;AACA;AACA,wCAAwC,qHAAiC;AACzE;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;AACA;AACA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,wCAAwC;AACrF,kCAAkC,uDAAuD;AACzF;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,qHAAiC;AAC9E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,KAAK,SAAS,qEAAS;AAC5E;AACA,KAAK;AACL;AACA;AACA;AACA,yBAAyB,sEAAU;AACnC;AACA;AACA;AACA;AACA,0BAA0B,sEAAU;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,mEAAO;AAC1C;AACA;AACA;AACA,mBAAmB,kEAAM;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wEAAwE,UAAU;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,IAAI;AAC7D;AACA;AACA;AACA;AACA;AACA,sCAAsC,wEAAe;AACrD;AACA;AACA;AACA;AACA;AACA,4CAA4C,0BAA0B;AACtE;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,wCAAwC;AACjG,8CAA8C,uDAAuD;AACrG;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,oDAAoD;AACpF;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,WAAW,QAAQ,KAAK;AACzD;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,KAAK;AACxC,oCAAoC,KAAK,YAAY,UAAU;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,wBAAwB,WAAW,WAAW;AACnF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd,qEAAqE,WAAW;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd,sEAAsE,YAAY;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sEAAsE,cAAc,IAAI,YAAY;AACpG;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,YAAY,IAAI,eAAe,GAAG,WAAW;AACxF;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,kCAAkC;AAClC,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB,qBAAqB;AACrB,gBAAgB;AAChB,kBAAkB;AAClB,qBAAqB;AACrB,sBAAsB;AACtB,sBAAsB;AACtB,wBAAwB;AACxB;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,uFAA+B;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,SAAI;AAClB,gBAAgB,WAAM;AACtB,mBAAmB,cAAS;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,qEAAa;AACxC,yBAAyB,qEAAa;AACtC;AACA,wCAAwC,gCAAgC,qEAAa;AACrF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yCAAyC;AACzC;AACA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;AACA;AACA;AACA,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,sBAAsB,4GAAmD;AACzE;AACA,aAAa;AACb;AACA;AACA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,6CAAS;AAC9B;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,6CAAS;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sources":["webpack://mwicombatsimulator/./src/combatsimulator/ability.js","webpack://mwicombatsimulator/./src/combatsimulator/achievement.js","webpack://mwicombatsimulator/./src/combatsimulator/buff.js","webpack://mwicombatsimulator/./src/combatsimulator/combatUnit.js","webpack://mwicombatsimulator/./src/combatsimulator/consumable.js","webpack://mwicombatsimulator/./src/combatsimulator/equipment.js","webpack://mwicombatsimulator/./src/combatsimulator/houseRoom.js","webpack://mwicombatsimulator/./src/combatsimulator/player.js","webpack://mwicombatsimulator/./src/combatsimulator/trigger.js","webpack://mwicombatsimulator/webpack/bootstrap","webpack://mwicombatsimulator/webpack/runtime/define property getters","webpack://mwicombatsimulator/webpack/runtime/get javascript chunk filename","webpack://mwicombatsimulator/webpack/runtime/global","webpack://mwicombatsimulator/webpack/runtime/hasOwnProperty shorthand","webpack://mwicombatsimulator/webpack/runtime/make namespace object","webpack://mwicombatsimulator/webpack/runtime/publicPath","webpack://mwicombatsimulator/webpack/runtime/jsonp chunk loading","webpack://mwicombatsimulator/./src/main.js"],"sourcesContent":["import Buff from \"./buff\";\r\nimport abilityDetailMap from \"./data/abilityDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nconst abilityFromCombatStat = {\r\n \"blaze\":\r\n {\r\n \"hrid\": \"/abilities/blaze\",\r\n \"name\": \"Blaze\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"allEnemies\",\r\n \"effectType\": \"/ability_effect_types/damage\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"/damage_types/fire\",\r\n \"baseDamageFlat\": 0,\r\n \"baseDamageFlatLevelBonus\": 0.0,\r\n \"baseDamageRatio\": 0.3,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/number_of_active_units\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n },\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/current_hp\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n }\r\n ],\r\n },\r\n \"bloom\":\r\n {\r\n \"hrid\": \"/abilities/bloom\",\r\n \"name\": \"Bloom\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"lowestHpAlly\",\r\n \"effectType\": \"/ability_effect_types/heal\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"\",\r\n \"baseDamageFlat\": 10,\r\n \"baseDamageFlatLevelBonus\": 0,\r\n \"baseDamageRatio\": 0.15,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_allies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/lowest_hp_percentage\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/less_than_equal\",\r\n \"value\": 100\r\n }\r\n ],\r\n }\r\n}\r\n\r\nclass Ability {\r\n constructor(hrid, level = 1, triggers = null) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameAbility = abilityDetailMap[hrid];\r\n if (!gameAbility) {\r\n gameAbility = abilityFromCombatStat[hrid];\r\n }\r\n if (!gameAbility) {\r\n throw new Error(\"No ability found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.manaCost = gameAbility.manaCost;\r\n this.cooldownDuration = gameAbility.cooldownDuration;\r\n this.castDuration = gameAbility.castDuration;\r\n this.isSpecialAbility = gameAbility.isSpecialAbility;\r\n\r\n this.abilityEffects = [];\r\n\r\n for (const effect of gameAbility.abilityEffects) {\r\n let abilityEffect = {\r\n targetType: effect.targetType,\r\n effectType: effect.effectType,\r\n combatStyleHrid: effect.combatStyleHrid,\r\n damageType: effect.damageType,\r\n damageFlat: effect.baseDamageFlat + (this.level - 1) * effect.baseDamageFlatLevelBonus,\r\n damageRatio: effect.baseDamageRatio + (this.level - 1) * effect.baseDamageRatioLevelBonus,\r\n bonusAccuracyRatio: effect.bonusAccuracyRatio + (this.level - 1) * effect.bonusAccuracyRatioLevelBonus,\r\n damageOverTimeRatio: effect.damageOverTimeRatio,\r\n damageOverTimeDuration: effect.damageOverTimeDuration,\r\n armorDamageRatio: effect.armorDamageRatio + (this.level - 1) * effect.armorDamageRatioLevelBonus,\r\n hpDrainRatio: effect.hpDrainRatio,\r\n pierceChance: effect.pierceChance,\r\n blindChance: effect.blindChance,\r\n blindDuration: effect.blindDuration,\r\n silenceChance: effect.silenceChance,\r\n silenceDuration: effect.silenceDuration,\r\n stunChance: effect.stunChance,\r\n stunDuration: effect.stunDuration,\r\n spendHpRatio: effect.spendHpRatio,\r\n buffs: null,\r\n };\r\n if (effect.buffs) {\r\n abilityEffect.buffs = [];\r\n for (const buff of effect.buffs) {\r\n abilityEffect.buffs.push(new Buff(buff, this.level));\r\n }\r\n }\r\n this.abilityEffects.push(abilityEffect);\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameAbility.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let ability = new Ability(dto.hrid, dto.level, triggers);\r\n\r\n return ability;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n\r\n if (source.isSilenced) {\r\n return false;\r\n }\r\n\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = this.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Ability;\r\n","import Buff from \"./buff\";\r\nimport achievementTierDetailMap from \"./data/achievementTierDetailMap.json\";\r\nimport achievementDetailMap from \"./data/achievementDetailMap.json\";\r\n\r\nclass Achievement {\r\n constructor(achievements) {\r\n this.achievements = achievements;\r\n this.buffs = [];\r\n\r\n for(const tier of Object.values(achievementTierDetailMap)) {\r\n let isGetAll = true;\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid)\r\n for(const achievement of Object.values(detailMap)) {\r\n if(!this.achievements[achievement.hrid] || this.achievements[achievement.hrid] == false) {\r\n isGetAll = false;\r\n break;\r\n }\r\n }\r\n if(isGetAll) {\r\n let buff = new Buff(tier.buff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default Achievement;","class Buff {\r\n startTime;\r\n\r\n constructor(buff, level = 1) {\r\n this.uniqueHrid = buff.uniqueHrid;\r\n this.typeHrid = buff.typeHrid;\r\n this.ratioBoost = buff.ratioBoost + (level - 1) * buff.ratioBoostLevelBonus;\r\n this.flatBoost = buff.flatBoost + (level - 1) * buff.flatBoostLevelBonus;\r\n this.duration = buff.duration;\r\n this.multiplierForSkillHrid = buff.multiplierForSkillHrid ?? \"\";\r\n this.multiplierPerSkillLevel = buff.multiplierPerSkillLevel ?? 0;\r\n }\r\n}\r\n\r\nexport default Buff;\r\n","class CombatUnit {\r\n isPlayer;\r\n isStunned = false;\r\n stunExpireTime = null;\r\n isBlinded = false;\r\n blindExpireTime = null;\r\n isSilenced = false;\r\n silenceExpireTime = null;\r\n\r\n isOutOfMana = false;\r\n\r\n // Base levels which don't change after initialization\r\n staminaLevel = 1;\r\n intelligenceLevel = 1;\r\n attackLevel = 1;\r\n meleeLevel = 1;\r\n defenseLevel = 1;\r\n rangedLevel = 1;\r\n magicLevel = 1;\r\n\r\n experience = 0;\r\n experienceRate = 0;\r\n enrageTime = 0;\r\n\r\n abilities = [null, null, null, null];\r\n food = [null, null, null];\r\n drinks = [null, null, null];\r\n houseRooms = [];\r\n achievements = null;\r\n dropTable = [];\r\n rareDropTable = [];\r\n abilityManaCosts = new Map();\r\n\r\n // Calculated combat stats including temporary buffs\r\n combatDetails = {\r\n staminaLevel: 1,\r\n intelligenceLevel: 1,\r\n attackLevel: 1,\r\n meleeLevel: 1,\r\n defenseLevel: 1,\r\n rangedLevel: 1,\r\n magicLevel: 1,\r\n maxHitpoints: 110,\r\n currentHitpoints: 110,\r\n maxManapoints: 110,\r\n currentManapoints: 110,\r\n stabAccuracyRating: 11,\r\n slashAccuracyRating: 11,\r\n smashAccuracyRating: 11,\r\n rangedAccuracyRating: 11,\r\n magicAccuracyRating: 11,\r\n stabMaxDamage: 11,\r\n slashMaxDamage: 11,\r\n smashMaxDamage: 11,\r\n rangedMaxDamage: 11,\r\n magicMaxDamage: 11,\r\n stabEvasionRating: 11,\r\n slashEvasionRating: 11,\r\n smashEvasionRating: 11,\r\n rangedEvasionRating: 11,\r\n magicEvasionRating: 11,\r\n defensiveMaxDamage: 0,\r\n totalArmor: 0.2,\r\n totalWaterResistance: 0.4,\r\n totalNatureResistance: 0.4,\r\n totalFireResistance: 0.4,\r\n abilityHaste: 0,\r\n tenacity: 0,\r\n totalThreat: 100,\r\n combatStats: {\r\n combatStyleHrid: \"/combat_styles/smash\",\r\n damageType: \"/damage_types/physical\",\r\n attackInterval: 3000000000,\r\n autoAttackDamage: 0,\r\n abilityDamage: 0,\r\n criticalRate: 0,\r\n criticalDamage: 0,\r\n stabAccuracy: 0,\r\n slashAccuracy: 0,\r\n smashAccuracy: 0,\r\n rangedAccuracy: 0,\r\n magicAccuracy: 0,\r\n stabDamage: 0,\r\n slashDamage: 0,\r\n smashDamage: 0,\r\n rangedDamage: 0,\r\n magicDamage: 0,\r\n defensiveDamage: 0,\r\n taskDamage: 0,\r\n physicalAmplify: 0,\r\n waterAmplify: 0,\r\n natureAmplify: 0,\r\n fireAmplify: 0,\r\n healingAmplify: 0,\r\n physicalThorns: 0,\r\n elementalThorns: 0,\r\n maxHitpoints: 0,\r\n maxManapoints: 0,\r\n stabEvasion: 0,\r\n slashEvasion: 0,\r\n smashEvasion: 0,\r\n rangedEvasion: 0,\r\n magicEvasion: 0,\r\n armor: 0,\r\n waterResistance: 0,\r\n natureResistance: 0,\r\n fireResistance: 0,\r\n lifeSteal: 0,\r\n hpRegenPer10: 0.01,\r\n mpRegenPer10: 0.01,\r\n combatDropRate: 0,\r\n combatDropQuantity: 0,\r\n combatRareFind: 0,\r\n combatExperience: 0,\r\n foodSlots: 1,\r\n drinkSlots: 1,\r\n armorPenetration: 0,\r\n waterPenetration: 0,\r\n naturePenetration: 0,\r\n firePenetration: 0,\r\n manaLeech: 0,\r\n castSpeed: 0,\r\n threat: 100,\r\n parry: 0,\r\n mayhem: 0,\r\n pierce: 0,\r\n curse: 0,\r\n ripple: 0,\r\n bloom: 0,\r\n blaze: 0,\r\n weaken: 0,\r\n fury: 0,\r\n foodHaste: 0,\r\n drinkConcentration: 0,\r\n damageTaken: 0,\r\n attackSpeed: 0,\r\n armorDamageRatio: 0,\r\n hpDrainRatio: 0,\r\n primaryTraining: \"\",\r\n focusTraining: \"\",\r\n staminaExperience: 0,\r\n intelligenceExperience: 0,\r\n attackExperience: 0,\r\n defenseExperience: 0,\r\n meleeExperience: 0,\r\n rangedExperience: 0,\r\n magicExperience: 0,\r\n retaliation: 0,\r\n },\r\n };\r\n combatBuffs = {};\r\n permanentBuffs = {};\r\n zoneBuffs = {};\r\n extraBuffs = {};\r\n\r\n constructor() { }\r\n\r\n updateCombatDetails() {\r\n if (this.isPlayer) {\r\n if (this.combatDetails.combatStats.hpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01 + this.combatDetails.combatStats.hpRegenPer10;\r\n }\r\n if (this.combatDetails.combatStats.mpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01 + this.combatDetails.combatStats.mpRegenPer10;\r\n }\r\n }\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((stat) => {\r\n this.combatDetails[stat + \"Level\"] = this[stat + \"Level\"];\r\n let boosts = this.getBuffBoosts(\"/buff_types/\" + stat + \"_level\");\r\n boosts.forEach((buff) => {\r\n this.combatDetails[stat + \"Level\"] += (this[stat + \"Level\"] * buff.ratioBoost);\r\n this.combatDetails[stat + \"Level\"] += buff.flatBoost;\r\n });\r\n });\r\n\r\n this.combatDetails.maxHitpoints = Math.floor\r\n (10 * (10 + this.combatDetails.staminaLevel) + this.combatDetails.combatStats.maxHitpoints);\r\n this.combatDetails.maxManapoints = Math.floor\r\n (10 * (10 + this.combatDetails.intelligenceLevel) + this.combatDetails.combatStats.maxManapoints);\r\n\r\n let accuracyRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_accuracy\").ratioBoost;\r\n let damageRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_damage\").ratioBoost;\r\n // if (accuracyRatioBoostFromFury > 0) {\r\n // console.log(\"Fury Boost: \" + accuracyRatioBoostFromFury);\r\n // }\r\n\r\n let accuracyRatioBoost = this.getBuffBoost(\"/buff_types/accuracy\").ratioBoost;\r\n let damageRatioBoost = this.getBuffBoost(\"/buff_types/damage\").ratioBoost;\r\n\r\n [\"stab\", \"slash\", \"smash\"].forEach((style) => {\r\n this.combatDetails[style + \"AccuracyRating\"] =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Accuracy\"]) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails[style + \"MaxDamage\"] =\r\n (10 + this.combatDetails.meleeLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Damage\"]) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n let baseEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats[style + \"Evasion\"]);\r\n this.combatDetails[style + \"EvasionRating\"] = baseEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails[style + \"EvasionRating\"] += boost.flatBoost;\r\n this.combatDetails[style + \"EvasionRating\"] += baseEvasion * boost.ratioBoost;\r\n }\r\n });\r\n\r\n this.combatDetails.defensiveMaxDamage = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.defensiveDamage);\r\n\r\n // when equiped bulwark\r\n if (this.equipment?.['/equipment_types/two_hand']?.hrid.includes(\"bulwark\")) {\r\n this.combatDetails.smashMaxDamage += this.combatDetails.defensiveMaxDamage;\r\n }\r\n\r\n this.combatDetails.rangedAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.rangedAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.rangedMaxDamage =\r\n (10 + this.combatDetails.rangedLevel) *\r\n (1 + this.combatDetails.combatStats.rangedDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseRangedEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.rangedEvasion);\r\n this.combatDetails.rangedEvasionRating = baseRangedEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.rangedEvasionRating += boost.flatBoost;\r\n this.combatDetails.rangedEvasionRating += baseRangedEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.damageTaken = this.getBuffBoost(\"/buff_types/damage_taken\").flatBoost;\r\n // if (this.combatDetails.combatStats.damageTaken > 0) {\r\n // console.log(\"Damage taken: \" + this.combatDetails.combatStats.damageTaken);\r\n // }\r\n\r\n this.combatDetails.magicAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.magicAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.magicMaxDamage =\r\n (10 + this.combatDetails.magicLevel) *\r\n (1 + this.combatDetails.combatStats.magicDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseMagicEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.magicEvasion);\r\n this.combatDetails.magicEvasionRating = baseMagicEvasion;\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.magicEvasionRating += boost.flatBoost;\r\n this.combatDetails.magicEvasionRating += baseMagicEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.physicalAmplify += this.getBuffBoost(\"/buff_types/physical_amplify\").flatBoost;\r\n this.combatDetails.combatStats.waterAmplify += this.getBuffBoost(\"/buff_types/water_amplify\").flatBoost;\r\n this.combatDetails.combatStats.natureAmplify += this.getBuffBoost(\"/buff_types/nature_amplify\").flatBoost;\r\n this.combatDetails.combatStats.fireAmplify += this.getBuffBoost(\"/buff_types/fire_amplify\").flatBoost;\r\n\r\n this.combatDetails.combatStats.attackInterval /= (1 + (this.combatDetails.attackLevel / 2000));\r\n\r\n let baseAttackSpeed = this.combatDetails.combatStats.attackSpeed;\r\n this.combatDetails.combatStats.attackInterval /= (1 + baseAttackSpeed);\r\n let attackIntervalBoosts = this.getBuffBoosts(\"/buff_types/attack_speed\");\r\n let attackIntervalRatioBoost = attackIntervalBoosts\r\n .map((boost) => boost.ratioBoost)\r\n .reduce((prev, cur) => prev + cur, 0);\r\n this.combatDetails.combatStats.attackInterval /= (1 + attackIntervalRatioBoost);\r\n\r\n let baseArmor = 0.2 * this.combatDetails.defenseLevel + this.combatDetails.combatStats.armor;\r\n this.combatDetails.totalArmor = baseArmor;\r\n let armorBoosts = this.getBuffBoosts(\"/buff_types/armor\");\r\n for (const boost of armorBoosts) {\r\n this.combatDetails.totalArmor += boost.flatBoost;\r\n this.combatDetails.totalArmor += baseArmor * boost.ratioBoost;\r\n }\r\n\r\n let baseWaterResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.waterResistance;\r\n this.combatDetails.totalWaterResistance = baseWaterResistance;\r\n let waterResistanceBoosts = this.getBuffBoosts(\"/buff_types/water_resistance\");\r\n for (const boost of waterResistanceBoosts) {\r\n this.combatDetails.totalWaterResistance += boost.flatBoost;\r\n this.combatDetails.totalWaterResistance += baseWaterResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseNatureResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.natureResistance;\r\n this.combatDetails.totalNatureResistance = baseNatureResistance;\r\n let natureResistanceBoosts = this.getBuffBoosts(\"/buff_types/nature_resistance\");\r\n for (const boost of natureResistanceBoosts) {\r\n this.combatDetails.totalNatureResistance += boost.flatBoost;\r\n this.combatDetails.totalNatureResistance += baseNatureResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseFireResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.fireResistance;\r\n this.combatDetails.totalFireResistance = baseFireResistance;\r\n let fireResistanceBoosts = this.getBuffBoosts(\"/buff_types/fire_resistance\");\r\n for (const boost of fireResistanceBoosts) {\r\n this.combatDetails.totalFireResistance += boost.flatBoost;\r\n this.combatDetails.totalFireResistance += baseFireResistance * boost.ratioBoost;\r\n }\r\n\r\n let hpRegenBoosts = this.getBuffBoost(\"/buff_types/hp_regen\");\r\n this.combatDetails.combatStats.hpRegenPer10 += this.combatDetails.combatStats.hpRegenPer10 * hpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.hpRegenPer10 += hpRegenBoosts.flatBoost;\r\n\r\n let mpRegenBoosts = this.getBuffBoost(\"/buff_types/mp_regen\");\r\n this.combatDetails.combatStats.mpRegenPer10 += this.combatDetails.combatStats.mpRegenPer10 * mpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.mpRegenPer10 += mpRegenBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.lifeSteal += this.getBuffBoost(\"/buff_types/life_steal\").flatBoost;\r\n this.combatDetails.combatStats.physicalThorns += this.getBuffBoost(\r\n \"/buff_types/physical_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.elementalThorns += this.getBuffBoost(\r\n \"/buff_types/elemental_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.combatExperience += this.getBuffBoost(\"/buff_types/wisdom\").flatBoost;\r\n this.combatDetails.combatStats.criticalRate += this.getBuffBoost(\"/buff_types/critical_rate\").flatBoost;\r\n this.combatDetails.combatStats.criticalDamage += this.getBuffBoost(\"/buff_types/critical_damage\").flatBoost;\r\n\r\n this.combatDetails.combatStats.castSpeed += this.getBuffBoost(\"/buff_types/cast_speed\").flatBoost;\r\n this.combatDetails.combatStats.castSpeed += this.combatDetails[\"attackLevel\"] / 2000;\r\n\r\n let combatDropRateBoosts = this.getBuffBoost(\"/buff_types/combat_drop_rate\");\r\n this.combatDetails.combatStats.combatDropRate += (1 + this.combatDetails.combatStats.combatDropRate) * combatDropRateBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropRate += combatDropRateBoosts.flatBoost;\r\n let combatRareFindBoosts = this.getBuffBoost(\"/buff_types/rare_find\");\r\n this.combatDetails.combatStats.combatRareFind += (1 + this.combatDetails.combatStats.combatRareFind) * combatRareFindBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatRareFind += combatRareFindBoosts.flatBoost;\r\n let combatDropQuantityBoosts = this.getBuffBoost(\"/buff_types/combat_drop_quantity\");\r\n this.combatDetails.combatStats.combatDropQuantity += (1 + this.combatDetails.combatStats.combatDropQuantity) * combatDropQuantityBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropQuantity += combatDropQuantityBoosts.flatBoost;\r\n\r\n let baseThreat = 100 + this.combatDetails.combatStats.threat;\r\n this.combatDetails.totalThreat = baseThreat;\r\n let threatBoosts = this.getBuffBoost(\"/buff_types/threat\");\r\n if (threatBoosts.ratioBoost !== 0) {\r\n this.combatDetails.combatStats.threat += baseThreat * threatBoosts.ratioBoost;\r\n } else {\r\n this.combatDetails.combatStats.threat = baseThreat;\r\n }\r\n this.combatDetails.combatStats.threat += threatBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.retaliation += this.getBuffBoost(\"/buff_types/retaliation\").flatBoost;\r\n }\r\n\r\n addBuff(buff, currentTime) {\r\n buff.startTime = currentTime;\r\n this.combatBuffs[buff.uniqueHrid] = buff;\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n removeBuff(buff) {\r\n if (!this.combatBuffs[buff.uniqueHrid]) {\r\n return;\r\n }\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n addPermanentBuff(buff) {\r\n if (this.permanentBuffs[buff.typeHrid]) {\r\n this.permanentBuffs[buff.typeHrid].flatBoost += buff.flatBoost;\r\n this.permanentBuffs[buff.typeHrid].ratioBoost += buff.ratioBoost;\r\n } else {\r\n this.permanentBuffs[buff.typeHrid] = buff;\r\n }\r\n }\r\n\r\n generatePermanentBuffs() {\r\n for (let i = 0; i < this.houseRooms.length; i++) {\r\n const houseRoom = this.houseRooms[i];\r\n houseRoom.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n\r\n if (this.achievements) {\r\n this.achievements.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.zoneBuffs) {\r\n this.zoneBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.extraBuffs) {\r\n this.extraBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n }\r\n\r\n removeExpiredBuffs(currentTime) {\r\n let expiredBuffs = Object.values(this.combatBuffs).filter(\r\n (buff) => buff.startTime + buff.duration <= currentTime\r\n );\r\n expiredBuffs.forEach((buff) => {\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n });\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearBuffs() {\r\n this.combatBuffs = structuredClone(this.permanentBuffs);\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearCCs() {\r\n this.isStunned = false;\r\n this.stunExpireTime = null;\r\n this.isSilenced = false;\r\n this.silenceExpireTime = null;\r\n this.isBlinded = false;\r\n this.blindExpireTime = null;\r\n this.combatDetails.combatStats.damageTaken = 0;\r\n }\r\n\r\n getBuffBoosts(type) {\r\n let boosts = [];\r\n Object.values(this.combatBuffs)\r\n .filter((buff) => buff.typeHrid == type)\r\n .forEach((buff) => {\r\n boosts.push({ ratioBoost: buff.ratioBoost, flatBoost: buff.flatBoost });\r\n });\r\n\r\n return boosts;\r\n }\r\n\r\n getBuffBoost(type) {\r\n let boosts = this.getBuffBoosts(type);\r\n\r\n let boost = {\r\n ratioBoost: 0,\r\n flatBoost: 0,\r\n };\r\n\r\n for (let i = 0; i < boosts.length; i++) {\r\n boost.ratioBoost += boosts[i]?.ratioBoost ?? 0;\r\n boost.flatBoost += boosts[i]?.flatBoost ?? 0;\r\n }\r\n\r\n return boost;\r\n }\r\n\r\n reset(currentTime = 0) {\r\n this.clearCCs();\r\n this.clearBuffs();\r\n this.updateCombatDetails();\r\n this.resetCooldowns(currentTime);\r\n\r\n this.combatDetails.currentHitpoints = this.combatDetails.maxHitpoints;\r\n this.combatDetails.currentManapoints = this.combatDetails.maxManapoints;\r\n }\r\n\r\n resetCooldowns(currentTime = 0) {\r\n this.food.filter((food) => food != null).forEach((food) => (food.lastUsed = Number.MIN_SAFE_INTEGER));\r\n this.drinks.filter((drink) => drink != null).forEach((drink) => (drink.lastUsed = Number.MIN_SAFE_INTEGER));\r\n\r\n let haste = this.combatDetails.combatStats.abilityHaste;\r\n\r\n this.abilities\r\n .filter((ability) => ability != null)\r\n .forEach((ability) => {\r\n if (this.isPlayer) {\r\n ability.lastUsed = Number.MIN_SAFE_INTEGER;\r\n } else {\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n ability.lastUsed = currentTime - Math.floor(cooldownDuration * 0.5) + Math.floor(Math.random() * cooldownDuration * 0.5);\r\n }\r\n });\r\n }\r\n\r\n addHitpoints(hitpoints) {\r\n let hitpointsAdded = 0;\r\n\r\n if (this.combatDetails.currentHitpoints >= this.combatDetails.maxHitpoints) {\r\n return hitpointsAdded;\r\n }\r\n\r\n let newHitpoints = Math.min(this.combatDetails.currentHitpoints + hitpoints, this.combatDetails.maxHitpoints);\r\n hitpointsAdded = newHitpoints - this.combatDetails.currentHitpoints;\r\n this.combatDetails.currentHitpoints = newHitpoints;\r\n\r\n return hitpointsAdded;\r\n }\r\n\r\n addManapoints(manapoints) {\r\n let manapointsAdded = 0;\r\n\r\n if (this.combatDetails.currentManapoints >= this.combatDetails.maxManapoints) {\r\n return manapointsAdded;\r\n }\r\n\r\n let newManapoints = Math.min(\r\n this.combatDetails.currentManapoints + manapoints,\r\n this.combatDetails.maxManapoints\r\n );\r\n manapointsAdded = newManapoints - this.combatDetails.currentManapoints;\r\n this.combatDetails.currentManapoints = newManapoints;\r\n\r\n return manapointsAdded;\r\n }\r\n}\r\n\r\nexport default CombatUnit;\r\n","import Buff from \"./buff\";\r\nimport itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nclass Consumable {\r\n constructor(hrid, triggers = null) {\r\n this.hrid = hrid;\r\n\r\n let gameConsumable = itemDetailMap[this.hrid];\r\n if (!gameConsumable) {\r\n throw new Error(\"No consumable found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.cooldownDuration = gameConsumable.consumableDetail.cooldownDuration;\r\n this.hitpointRestore = gameConsumable.consumableDetail.hitpointRestore;\r\n this.manapointRestore = gameConsumable.consumableDetail.manapointRestore;\r\n this.recoveryDuration = gameConsumable.consumableDetail.recoveryDuration;\r\n this.catagoryHrid = gameConsumable.categoryHrid;\r\n\r\n this.buffs = [];\r\n if (gameConsumable.consumableDetail.buffs) {\r\n for (const consumableBuff of gameConsumable.consumableDetail.buffs) {\r\n let buff = new Buff(consumableBuff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameConsumable.consumableDetail.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let consumable = new Consumable(dto.hrid, triggers);\r\n\r\n return consumable;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n let consumableHaste;\r\n if (this.catagoryHrid.includes(\"food\")) {\r\n consumableHaste = source.combatDetails.combatStats.foodHaste\r\n } else {\r\n consumableHaste = source.combatDetails.combatStats.drinkConcentration;\r\n }\r\n let cooldownDuration = this.cooldownDuration;\r\n if (consumableHaste > 0) {\r\n cooldownDuration = cooldownDuration / (1 + consumableHaste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Consumable;\r\n","import itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport enhancementLevelTotalMultiplierTable from \"./data/enhancementLevelTotalBonusMultiplierTable.json\";\r\n\r\nclass Equipment {\r\n constructor(hrid, enhancementLevel) {\r\n this.hrid = hrid;\r\n let gameItem = itemDetailMap[this.hrid];\r\n if (!gameItem) {\r\n throw new Error(\"No equipment found for hrid: \" + this.hrid);\r\n }\r\n this.gameItem = gameItem;\r\n this.enhancementLevel = enhancementLevel;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let equipment = new Equipment(dto.hrid, dto.enhancementLevel);\r\n\r\n return equipment;\r\n }\r\n\r\n getCombatStat(combatStat) {\r\n let multiplier = enhancementLevelTotalMultiplierTable[this.enhancementLevel];\r\n if(this.gameItem.equipmentDetail.combatStats[combatStat]) {\r\n let enhancementBonus = this.gameItem.equipmentDetail.combatEnhancementBonuses[combatStat] || 0;\r\n let stat = this.gameItem.equipmentDetail.combatStats[combatStat] + multiplier * enhancementBonus;\r\n return stat;\r\n }\r\n return 0;\r\n }\r\n\r\n getCombatStyle() {\r\n return this.gameItem.equipmentDetail.combatStats.combatStyleHrids[0];\r\n }\r\n\r\n getDamageType() {\r\n return this.gameItem.equipmentDetail.combatStats.damageType;\r\n }\r\n\r\n getPrimaryTraining() {\r\n return this.gameItem.equipmentDetail.combatStats.primaryTraining;\r\n }\r\n\r\n getFocusTraining(){\r\n return this.gameItem.equipmentDetail.combatStats.focusTraining;\r\n }\r\n}\r\n\r\nexport default Equipment;\r\n","import Buff from \"./buff\";\r\nimport houseRoomDetailMap from \"./data/houseRoomDetailMap.json\";\r\n\r\nclass HouseRoom {\r\n constructor(hrid, level) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameHouseRoom = houseRoomDetailMap[this.hrid];\r\n if (!gameHouseRoom) {\r\n throw new Error(\"No house room found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.buffs = [];\r\n if (gameHouseRoom.actionBuffs) {\r\n for (const actionBuff of gameHouseRoom.actionBuffs) {\r\n let buff = new Buff(actionBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n if (gameHouseRoom.globalBuffs) {\r\n for (const globalBuff of gameHouseRoom.globalBuffs) {\r\n let buff = new Buff(globalBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default HouseRoom;","import Ability from \"./ability\";\r\nimport CombatUnit from \"./combatUnit\";\r\nimport Consumable from \"./consumable\";\r\nimport Equipment from \"./equipment\";\r\nimport HouseRoom from \"./houseRoom\";\r\nimport Achievement from \"./achievement\";\r\n\r\nclass Player extends CombatUnit {\r\n equipment = {\r\n \"/equipment_types/head\": null,\r\n \"/equipment_types/body\": null,\r\n \"/equipment_types/legs\": null,\r\n \"/equipment_types/feet\": null,\r\n \"/equipment_types/hands\": null,\r\n \"/equipment_types/main_hand\": null,\r\n \"/equipment_types/two_hand\": null,\r\n \"/equipment_types/off_hand\": null,\r\n \"/equipment_types/pouch\": null,\r\n \"/equipment_types/back\": null,\r\n };\r\n\r\n constructor() {\r\n super();\r\n\r\n this.isPlayer = true;\r\n this.hrid = \"player\";\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let player = new Player();\r\n\r\n player.staminaLevel = dto.staminaLevel;\r\n player.intelligenceLevel = dto.intelligenceLevel;\r\n player.attackLevel = dto.attackLevel;\r\n player.meleeLevel = dto.meleeLevel;\r\n player.defenseLevel = dto.defenseLevel;\r\n player.rangedLevel = dto.rangedLevel;\r\n player.magicLevel = dto.magicLevel;\r\n\r\n player.hrid = dto.hrid;\r\n\r\n for (const [key, value] of Object.entries(dto.equipment)) {\r\n player.equipment[key] = value ? Equipment.createFromDTO(value) : null;\r\n }\r\n\r\n player.food = dto.food.map((food) => (food ? Consumable.createFromDTO(food) : null));\r\n player.drinks = dto.drinks.map((drink) => (drink ? Consumable.createFromDTO(drink) : null));\r\n player.abilities = dto.abilities.map((ability) => (ability ? Ability.createFromDTO(ability) : null));\r\n Object.entries(dto.houseRooms).forEach(houseRoom => {\r\n if (houseRoom[1] > 0) {\r\n player.houseRooms.push(new HouseRoom(houseRoom[0], houseRoom[1]))\r\n }\r\n });\r\n\r\n player.achievements = new Achievement(dto.achievements);\r\n\r\n player.debuffOnLevelGap = dto.debuffOnLevelGap;\r\n\r\n return player;\r\n }\r\n\r\n updateCombatDetails() {\r\n if (this.equipment[\"/equipment_types/main_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/main_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/main_hand\"].getPrimaryTraining();\r\n } else if (this.equipment[\"/equipment_types/two_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/two_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/two_hand\"].getPrimaryTraining();\r\n } else {\r\n this.combatDetails.combatStats.combatStyleHrid = \"/combat_styles/smash\";\r\n this.combatDetails.combatStats.damageType = \"/damage_types/physical\";\r\n this.combatDetails.combatStats.attackInterval = 3000000000;\r\n this.combatDetails.combatStats.primaryTraining = \"/skills/melee\";\r\n }\r\n\r\n if (this.equipment[\"/equipment_types/charm\"]) {\r\n this.combatDetails.combatStats.focusTraining = this.equipment[\"/equipment_types/charm\"].getFocusTraining();\r\n } else {\r\n this.combatDetails.combatStats.focusTraining = \"\";\r\n }\r\n\r\n [\r\n \"stabAccuracy\",\r\n \"slashAccuracy\",\r\n \"smashAccuracy\",\r\n \"rangedAccuracy\",\r\n \"magicAccuracy\",\r\n \"stabDamage\",\r\n \"slashDamage\",\r\n \"smashDamage\",\r\n \"rangedDamage\",\r\n \"magicDamage\",\r\n \"defensiveDamage\",\r\n \"taskDamage\",\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"stabEvasion\",\r\n \"slashEvasion\",\r\n \"smashEvasion\",\r\n \"rangedEvasion\",\r\n \"magicEvasion\",\r\n \"armor\",\r\n \"waterResistance\",\r\n \"natureResistance\",\r\n \"fireResistance\",\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"combatDropRate\",\r\n \"combatRareFind\",\r\n \"combatDropQuantity\",\r\n \"combatExperience\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"abilityHaste\",\r\n \"tenacity\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"threat\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"foodHaste\",\r\n \"drinkConcentration\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\",\r\n \"retaliation\"\r\n ].forEach((stat) => {\r\n this.combatDetails.combatStats[stat] = Object.values(this.equipment)\r\n .filter((equipment) => equipment != null)\r\n .map((equipment) => equipment.getCombatStat(stat))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n });\r\n\r\n if (this.equipment[\"/equipment_types/pouch\"]) {\r\n this.combatDetails.combatStats.foodSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"foodSlots\");\r\n this.combatDetails.combatStats.drinkSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"drinkSlots\");\r\n } else {\r\n this.combatDetails.combatStats.foodSlots = 1;\r\n this.combatDetails.combatStats.drinkSlots = 1;\r\n }\r\n\r\n super.updateCombatDetails();\r\n }\r\n}\r\n\r\nexport default Player;\r\n","import combatTriggerDependencyDetailMap from \"./data/combatTriggerDependencyDetailMap.json\";\r\n\r\nclass Trigger {\r\n constructor(dependencyHrid, conditionHrid, comparatorHrid, value = 0) {\r\n this.dependencyHrid = dependencyHrid;\r\n this.conditionHrid = conditionHrid;\r\n this.comparatorHrid = comparatorHrid;\r\n this.value = value;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let trigger = new Trigger(dto.dependencyHrid, dto.conditionHrid, dto.comparatorHrid, dto.value);\r\n\r\n return trigger;\r\n }\r\n\r\n isActive(source, target, friendlies, enemies, currentTime) {\r\n if (combatTriggerDependencyDetailMap[this.dependencyHrid].isSingleTarget) {\r\n return this.isActiveSingleTarget(source, target, currentTime);\r\n } else {\r\n return this.isActiveMultiTarget(friendlies, enemies, currentTime);\r\n }\r\n }\r\n\r\n isActiveSingleTarget(source, target, currentTime) {\r\n let dependencyValue;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/self\":\r\n dependencyValue = this.getDependencyValue(source, currentTime);\r\n break;\r\n case \"/combat_trigger_dependencies/targeted_enemy\":\r\n if (!target) {\r\n return false;\r\n }\r\n dependencyValue = this.getDependencyValue(target, currentTime);\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n isActiveMultiTarget(friendlies, enemies, currentTime) {\r\n let dependency;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/all_allies\":\r\n dependency = friendlies;\r\n break;\r\n case \"/combat_trigger_dependencies/all_enemies\":\r\n if (!enemies) {\r\n return false;\r\n }\r\n dependency = enemies;\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n let dependencyValue;\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/number_of_active_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints > 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/number_of_dead_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints <= 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/lowest_hp_percentage\":\r\n dependencyValue = dependency.reduce((prev, curr) => {\r\n let currentHpPercentage = curr.combatDetails.currentHitpoints / curr.combatDetails.maxHitpoints;\r\n return currentHpPercentage < prev ? currentHpPercentage : prev;\r\n }, 2) * 100;\r\n break;\r\n default:\r\n dependencyValue = dependency\r\n .map((unit) => this.getDependencyValue(unit, currentTime))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n break;\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n getDependencyValue(source, currentTime) {\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/berserk\":\r\n case \"/combat_trigger_conditions/frenzy\":\r\n case \"/combat_trigger_conditions/precision\":\r\n case \"/combat_trigger_conditions/vampirism\":\r\n case \"/combat_trigger_conditions/attack_coffee\":\r\n case \"/combat_trigger_conditions/defense_coffee\":\r\n case \"/combat_trigger_conditions/lucky_coffee\":\r\n case \"/combat_trigger_conditions/magic_coffee\":\r\n case \"/combat_trigger_conditions/melee_coffee\":\r\n case \"/combat_trigger_conditions/ranged_coffee\":\r\n case \"/combat_trigger_conditions/swiftness_coffee\":\r\n case \"/combat_trigger_conditions/wisdom_coffee\":\r\n case \"/combat_trigger_conditions/ice_spear\":\r\n case \"/combat_trigger_conditions/puncture\":\r\n case \"/combat_trigger_conditions/frost_surge\":\r\n case \"/combat_trigger_conditions/elusiveness\":\r\n case \"/combat_trigger_conditions/channeling_coffee\":\r\n case \"/combat_trigger_conditions/fierce_aura\":\r\n case \"/combat_trigger_conditions/invincible_armor\":\r\n case \"/combat_trigger_conditions/invincible_fire_resistance\":\r\n case \"/combat_trigger_conditions/invincible_nature_resistance\":\r\n case \"/combat_trigger_conditions/invincible_water_resistance\":\r\n case \"/combat_trigger_conditions/provoke\":\r\n case \"/combat_trigger_conditions/taunt\":\r\n case \"/combat_trigger_conditions/crippling_slash\":\r\n case \"/combat_trigger_conditions/mana_spring\":\r\n case \"/combat_trigger_conditions/retribution\":\r\n case \"/combat_trigger_conditions/fracturing_impact\":\r\n case \"/combat_trigger_conditions/maim\":\r\n case \"/combat_trigger_conditions/curse\":\r\n case \"/combat_trigger_conditions/weaken\":\r\n let buffHrid = \"/buff_uniques\";\r\n buffHrid += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n return source.combatBuffs[buffHrid];\r\n case \"/combat_trigger_conditions/critical_aura\":\r\n case \"/combat_trigger_conditions/critical_coffee\":\r\n case \"/combat_trigger_conditions/intelligence_coffee\":\r\n case \"/combat_trigger_conditions/stamina_coffee\":\r\n case \"/combat_trigger_conditions/elemental_affinity\":\r\n case \"/combat_trigger_conditions/fury\":\r\n case \"/combat_trigger_conditions/guardian_aura\":\r\n case \"/combat_trigger_conditions/insanity\":\r\n case \"/combat_trigger_conditions/spike_shell\":\r\n case \"/combat_trigger_conditions/toxic_pollen\":\r\n case \"/combat_trigger_conditions/invincible\":\r\n case \"/combat_trigger_conditions/mystic_aura\":\r\n case \"/combat_trigger_conditions/pestilent_shot\":\r\n case \"/combat_trigger_conditions/smoke_burst\":\r\n case \"/combat_trigger_conditions/speed_aura\":\r\n case \"/combat_trigger_conditions/toughness\":\r\n case \"/combat_trigger_conditions/enrage\":\r\n let buffPrefix = \"/buff_uniques\";\r\n buffPrefix += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n let buffs = Object.keys(source.combatBuffs).filter(buff => buff.startsWith(buffPrefix));\r\n return source.combatBuffs[buffs?.[0]];\r\n case \"/combat_trigger_conditions/current_hp\":\r\n return source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/current_mp\":\r\n return source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/missing_hp\":\r\n return source.combatDetails.maxHitpoints - source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/missing_mp\":\r\n return source.combatDetails.maxManapoints - source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/stun_status\":\r\n // Replicate the game's behaviour of \"stun status active\" triggers activating\r\n // immediately after the stun has worn off\r\n return source.isStunned || source.stunExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/blind_status\":\r\n return source.isBlinded || source.blindExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/silence_status\":\r\n return source.isSilenced || source.silenceExpireTime == currentTime;\r\n default:\r\n throw new Error(\"Unknown conditionHrid in trigger: \" + this.conditionHrid);\r\n }\r\n }\r\n\r\n compareValue(dependencyValue) {\r\n switch (this.comparatorHrid) {\r\n case \"/combat_trigger_comparators/greater_than_equal\":\r\n return dependencyValue >= this.value;\r\n case \"/combat_trigger_comparators/less_than_equal\":\r\n return dependencyValue <= this.value;\r\n case \"/combat_trigger_comparators/is_active\":\r\n return !!dependencyValue;\r\n case \"/combat_trigger_comparators/is_inactive\":\r\n return !dependencyValue;\r\n default:\r\n throw new Error(\"Unknown comparatorHrid in trigger: \" + this.comparatorHrid);\r\n }\r\n }\r\n}\r\n\r\nexport default Trigger;\r\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","// This function allow to reference async chunks\n__webpack_require__.u = (chunkId) => {\n\t// return url for filenames based on template\n\treturn \"\" + chunkId + \".bundle.js\";\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","var scriptUrl;\nif (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + \"\";\nvar document = __webpack_require__.g.document;\nif (!scriptUrl && document) {\n\tif (document.currentScript)\n\t\tscriptUrl = document.currentScript.src;\n\tif (!scriptUrl) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tif(scripts.length) {\n\t\t\tvar i = scripts.length - 1;\n\t\t\twhile (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;\n\t\t}\n\t}\n}\n// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration\n// or pass an empty string (\"\") and set the __webpack_public_path__ variable from your code to use your own logic.\nif (!scriptUrl) throw new Error(\"Automatic publicPath is not supported in this browser\");\nscriptUrl = scriptUrl.replace(/#.*$/, \"\").replace(/\\?.*$/, \"\").replace(/\\/[^\\/]+$/, \"/\");\n__webpack_require__.p = scriptUrl;","__webpack_require__.b = document.baseURI || self.location.href;\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t\"main\": 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n// no on chunks loaded\n\n// no jsonp function","import Equipment from \"./combatsimulator/equipment.js\";\r\nimport Player from \"./combatsimulator/player.js\";\r\nimport abilityDetailMap from \"./combatsimulator/data/abilityDetailMap.json\";\r\nimport itemDetailMap from \"./combatsimulator/data/itemDetailMap.json\";\r\nimport houseRoomDetailMap from \"./combatsimulator/data/houseRoomDetailMap.json\";\r\nimport Ability from \"./combatsimulator/ability.js\";\r\nimport Consumable from \"./combatsimulator/consumable.js\";\r\nimport HouseRoom from \"./combatsimulator/houseRoom\"\r\nimport combatTriggerDependencyDetailMap from \"./combatsimulator/data/combatTriggerDependencyDetailMap.json\";\r\nimport combatTriggerConditionDetailMap from \"./combatsimulator/data/combatTriggerConditionDetailMap.json\";\r\nimport combatTriggerComparatorDetailMap from \"./combatsimulator/data/combatTriggerComparatorDetailMap.json\";\r\nimport abilitySlotsLevelRequirementList from \"./combatsimulator/data/abilitySlotsLevelRequirementList.json\";\r\nimport actionDetailMap from \"./combatsimulator/data/actionDetailMap.json\";\r\nimport combatMonsterDetailMap from \"./combatsimulator/data/combatMonsterDetailMap.json\";\r\nimport damageTypeDetailMap from \"./combatsimulator/data/damageTypeDetailMap.json\";\r\nimport combatStyleDetailMap from \"./combatsimulator/data/combatStyleDetailMap.json\";\r\nimport openableLootDropMap from \"./combatsimulator/data/openableLootDropMap.json\";\r\nimport achievementTierMap from \"./combatsimulator/data/achievementTierDetailMap.json\"\r\nimport achievementDetailMap from \"./combatsimulator/data/achievementDetailMap.json\"\r\n\r\nimport patchNote from \"../patchNote.json\";\r\n\r\nconst ONE_SECOND = 1e9;\r\nconst ONE_HOUR = 60 * 60 * ONE_SECOND;\r\n\r\nlet buttonStartSimulation = document.getElementById(\"buttonStartSimulation\");\r\nlet buttonStopSimulation = document.getElementById(\"buttonStopSimulation\");\r\nlet progressbar = document.getElementById(\"simulationProgressBar\");\r\nlet simStartTime = 0;\r\n\r\nlet worker = new Worker(new URL(\"worker.js\", import.meta.url));\r\nlet multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n\r\n\r\n\r\nlet player = new Player();\r\nlet selectedPlayers = [];\r\nlet food = [null, null, null];\r\nlet drinks = [null, null, null];\r\nlet abilities = [null, null, null, null];\r\nlet triggerMap = {};\r\nlet modalTriggers = [];\r\nlet currentSimResults = {};\r\n\r\nlet currentPlayerTabId = '1';\r\nlet playerDataMap = {\r\n \"1\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},achievements:{}}\",\r\n \"2\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},achievements:{}}\",\r\n \"3\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},achievements:{}}\",\r\n \"4\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},achievements:{}}\",\r\n \"5\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},achievements:{}}\"\r\n};\r\nwindow.revenue = 0;\r\nwindow.noRngRevenue = 0;\r\nwindow.expenses = 0;\r\nwindow.profit = 0;\r\nwindow.noRngProfit = 0;\r\n\r\n// #region Worker\r\n\r\nfunction onWorkerMessage(event) {\r\n switch (event.data.type) {\r\n case \"simulation_result\":\r\n progressbar.style.width = \"100%\";\r\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n //console.log(\"SIM RESULTS: \", event.data.simResult);\r\n showSimulationResult(event.data.simResult);\r\n updateContent();\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n document.getElementById('buttonShowAllSimData').style.display = 'none';\r\n break;\r\n case \"simulation_progress\":\r\n let progress = Math.floor(100 * event.data.progress);\r\n progressbar.style.width = progress + \"%\";\r\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n break;\r\n case \"simulation_error\":\r\n showErrorModal(event.data.error.toString());\r\n break;\r\n }\r\n};\r\n\r\nfunction onMultiWorkerMessage(event) {\r\n switch (event.data.type) {\r\n case \"simulation_result_allZones\":\r\n progressbar.style.width = \"100%\";\r\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n showAllSimulationResults(event.data.simResults);\r\n updateContent();\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n document.getElementById('buttonShowAllSimData').style.display = 'block';\r\n break;\r\n case \"simulation_progress\":\r\n let progress = Math.floor(100 * event.data.progress);\r\n progressbar.style.width = progress + \"%\";\r\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n break;\r\n case \"simulation_error\":\r\n showErrorModal(event.data.error.toString());\r\n break;\r\n }\r\n};\r\n\r\n// #endregion\r\n\r\n// #region Equipment\r\n\r\nfunction initEquipmentSection() {\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n initEquipmentSelect(type);\r\n initEnhancementLevelInput(type);\r\n });\r\n}\r\n\r\nfunction initEquipmentSelect(equipmentType) {\r\n let selectId = \"selectEquipment_\";\r\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\r\n selectId += \"weapon\";\r\n } else {\r\n selectId += equipmentType;\r\n }\r\n let selectElement = document.getElementById(selectId);\r\n\r\n let gameEquipment = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/equipment\")\r\n .filter((item) => item.equipmentDetail.type == \"/equipment_types/\" + equipmentType)\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const equipment of Object.values(gameEquipment)) {\r\n let opt = new Option(equipment.name, equipment.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + equipment.hrid);\r\n selectElement.add(opt);\r\n }\r\n\r\n selectElement.addEventListener(\"change\", (event) => {\r\n equipmentSelectHandler(event, equipmentType);\r\n });\r\n}\r\n\r\nfunction initHouseRoomsModal() {\r\n let houseRoomsList = document.getElementById(\"houseRoomsList\");\r\n let newChildren = [];\r\n let houseRooms = Object.values(houseRoomDetailMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n player.houseRooms = {};\r\n\r\n for (const room of Object.values(houseRooms)) {\r\n player.houseRooms[room.hrid] = 0;\r\n\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let nameCol = createElement(\"div\", \"col-md-4 offset-md-3 align-self-center\", room.name);\r\n nameCol.setAttribute(\"data-i18n\", \"houseRoomNames.\" + room.hrid);\r\n row.appendChild(nameCol);\r\n\r\n let levelCol = createElement(\"div\", \"col-md-2\");\r\n let levelInput = createHouseInput(room.hrid);\r\n\r\n levelInput.addEventListener(\"input\", function (e) {\r\n let inputValue = e.target.value;\r\n const hrid = e.target.dataset.houseHrid;\r\n player.houseRooms[hrid] = parseInt(inputValue);\r\n });\r\n\r\n levelCol.appendChild(levelInput);\r\n row.appendChild(levelCol);\r\n\r\n newChildren.push(row);\r\n }\r\n\r\n houseRoomsList.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction createHouseInput(hrid) {\r\n let levelInput = document.createElement(\"input\");\r\n levelInput.className = \"form-control\";\r\n levelInput.type = \"number\";\r\n levelInput.placeholder = 0;\r\n levelInput.min = 0;\r\n levelInput.max = 8;\r\n levelInput.step = 1;\r\n levelInput.dataset.houseHrid = hrid;\r\n\r\n return levelInput;\r\n}\r\n\r\nfunction refreshAchievementStatics() {\r\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n for(const tier of Object.values(tierMap)) {\r\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\r\n const done = Array.from(checks).filter(cb => cb.checked).length;\r\n const total = checks.length;\r\n\r\n const stat = document.getElementById(`AchTier${tier.sortIndex}Statics`);\r\n stat.innerText = `(${done}/${total})`;\r\n if (done == total) {\r\n // set to green\r\n stat.classList.remove(\"text-secondary\");\r\n stat.classList.add(\"text-success\");\r\n } else {\r\n // set to secondary\r\n stat.classList.remove(\"text-success\");\r\n stat.classList.add(\"text-secondary\");\r\n }\r\n }\r\n}\r\n\r\nfunction initAchievementsModal(){\r\n let achievementsList = document.getElementById(\"achievementsList\");\r\n let newChildren = [];\r\n player.achievements = {};\r\n\r\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n for(const tier of Object.values(tierMap)) {\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid).sort((a, b) => a.sortIndex - b.sortIndex);\r\n let detailMapCount = detailMap.length;\r\n if (detailMapCount <= 0) continue;\r\n\r\n let card = createElement(\"div\", \"card\");\r\n let cardHeader = createElement(\"div\", \"card-header d-flex align-items-center\");\r\n\r\n let cardTitle = createElement(\"a\", \"btn\", tier.name);\r\n cardTitle.setAttribute(\"data-bs-toggle\",\"collapse\");\r\n cardTitle.setAttribute(\"href\", `#AchTier${tier.sortIndex}`);\r\n cardTitle.setAttribute(\"data-i18n\", \"achievementTierNames.\"+tier.hrid);\r\n cardHeader.appendChild(cardTitle);\r\n\r\n let bufDesc = createElement(\"div\", \"small text-secondary\");\r\n let buffName = createElement(\"i\", \"\");\r\n buffName.setAttribute(\"data-i18n\", \"buffTypeNames.\"+tier[\"buff\"].typeHrid);\r\n bufDesc.appendChild(buffName);\r\n let buffValue = createElement(\"i\", \"\");\r\n buffValue.innerText = \":+\" + parseFloat(tier[\"buff\"].ratioBoost==0?tier[\"buff\"].flatBoost:tier[\"buff\"].ratioBoost)*100 + \"%\";\r\n bufDesc.appendChild(buffValue);\r\n cardHeader.appendChild(bufDesc);\r\n\r\n let cardStatics = createElement(\"div\", \"ms-auto btn\", `(0/${detailMapCount})`);\r\n cardStatics.id = `AchTier${tier.sortIndex}Statics`;\r\n cardStatics.dataset.checked = \"true\";\r\n cardStatics.addEventListener(\"click\", function (e) {\r\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\r\n for (const check of checks) {\r\n check.checked = cardStatics.dataset.checked == \"true\";\r\n const hrid = check.dataset.achievementHrid;\r\n player.achievements[hrid] = check.checked;\r\n }\r\n cardStatics.dataset.checked = cardStatics.dataset.checked == \"true\" ? \"false\" : \"true\";\r\n refreshAchievementStatics();\r\n });\r\n cardHeader.appendChild(cardStatics);\r\n\r\n card.appendChild(cardHeader);\r\n\r\n let cardMain = createElement(\"div\", \"collapse\");\r\n cardMain.id = `AchTier${tier.sortIndex}`;\r\n let cardBody = createElement(\"div\", \"card-body\");\r\n\r\n for (const detail of Object.values(detailMap)) {\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let formCheck = createElement(\"div\", \"form-check\");\r\n let input = createElement(\"input\", \"form-check-input\");\r\n input.setAttribute(\"type\", \"checkbox\");\r\n input.setAttribute(\"data-tier\", tier.sortIndex);\r\n input.id = `AchDetail${detail.sortIndex}`;\r\n input.dataset.achievementHrid = detail.hrid;\r\n input.addEventListener(\"change\", function (e) {\r\n const hrid = e.target.dataset.achievementHrid;\r\n player.achievements[hrid] = e.target.checked;\r\n\r\n refreshAchievementStatics();\r\n });\r\n formCheck.appendChild(input);\r\n\r\n let name = createElement(\"label\", \"form-check-label\", detail.name);\r\n name.setAttribute(\"data-i18n\", \"achievementNames.\" + detail.hrid);\r\n name.setAttribute(\"for\", `AchDetail${detail.sortIndex}`);\r\n formCheck.appendChild(name);\r\n row.appendChild(formCheck);\r\n cardBody.appendChild(row);\r\n }\r\n cardMain.appendChild(cardBody);\r\n card.appendChild(cardMain);\r\n\r\n newChildren.push(card);\r\n }\r\n\r\n achievementsList.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction initEnhancementLevelInput(equipmentType) {\r\n let inputId = \"inputEquipmentEnhancementLevel_\";\r\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\r\n inputId += \"weapon\";\r\n } else {\r\n inputId += equipmentType;\r\n }\r\n\r\n let inputElement = document.getElementById(inputId);\r\n inputElement.value = 0;\r\n inputElement.addEventListener(\"change\", enhancementLevelInputHandler);\r\n}\r\n\r\nfunction equipmentSelectHandler(event, type) {\r\n let equipmentType = \"/equipment_types/\" + type;\r\n\r\n if (!event.target.value) {\r\n updateEquipmentState();\r\n updateUI();\r\n return;\r\n }\r\n\r\n let gameItem = itemDetailMap[event.target.value];\r\n\r\n // Weapon select has two handlers because of mainhand and twohand weapons. Ignore the handler with the wrong type\r\n if (gameItem.equipmentDetail.type != equipmentType) {\r\n return;\r\n }\r\n\r\n if (type == \"two_hand\") {\r\n document.getElementById(\"selectEquipment_off_hand\").value = \"\";\r\n document.getElementById(\"inputEquipmentEnhancementLevel_off_hand\").value = 0;\r\n }\r\n if (type == \"off_hand\" && player.equipment[\"/equipment_types/two_hand\"]) {\r\n document.getElementById(\"selectEquipment_weapon\").value = \"\";\r\n document.getElementById(\"inputEquipmentEnhancementLevel_weapon\").value = 0;\r\n }\r\n\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\nfunction enhancementLevelInputHandler() {\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\nfunction updateEquipmentState() {\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentType = \"/equipment_types/\" + type;\r\n let selectType = type;\r\n if (type == \"main_hand\" || type == \"two_hand\") {\r\n selectType = \"weapon\";\r\n }\r\n\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + selectType);\r\n let equipmentHrid = equipmentSelect.value;\r\n\r\n if (!equipmentHrid) {\r\n player.equipment[equipmentType] = null;\r\n return;\r\n }\r\n\r\n let gameItem = itemDetailMap[equipmentHrid];\r\n\r\n // Clear old weapon if a weapon of a different type is equipped\r\n if (gameItem.equipmentDetail.type != equipmentType) {\r\n player.equipment[equipmentType] = null;\r\n return;\r\n }\r\n\r\n let enhancementLevel = Number(document.getElementById(\"inputEquipmentEnhancementLevel_\" + selectType).value);\r\n player.equipment[equipmentType] = new Equipment(gameItem.hrid, enhancementLevel);\r\n });\r\n}\r\n\r\ndocument.getElementById(\"selectEquipment_set\").onchange = changeEquipmentSetListener;\r\n\r\nfunction changeEquipmentSetListener() {\r\n let value = this.value\r\n let optgroupType = this.options[this.selectedIndex].parentNode.label;\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\"].forEach((type) => {\r\n let selectType = type;\r\n\r\n let currentEquipment = document.getElementById(\"selectEquipment_\" + selectType);\r\n if (type === \"feet\") {\r\n type = \"_boots\";\r\n }\r\n if (type === \"hands\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_bracers\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_gloves\";\r\n } else {\r\n type = \"_gauntlets\";\r\n }\r\n }\r\n if (type === \"head\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_hood\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_hat\";\r\n } else {\r\n type = \"_helmet\";\r\n }\r\n }\r\n if (type === \"legs\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_chaps\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_robe_bottoms\";\r\n } else {\r\n type = \"_plate_legs\";\r\n }\r\n }\r\n if (type === \"body\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_tunic\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_robe_top\";\r\n } else {\r\n type = \"_plate_body\";\r\n }\r\n }\r\n currentEquipment.value = \"/items/\" + value.toLowerCase() + type;\r\n });\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Combat Stats\r\n\r\nfunction updateCombatStatsUI() {\r\n player.updateCombatDetails();\r\n\r\n let combatStyleElement = document.getElementById(\"combatStat_combatStyleHrid\");\r\n let combatStyle = player.combatDetails.combatStats.combatStyleHrid;\r\n combatStyleElement.setAttribute(\"data-i18n\", \"combatStyleNames.\" + combatStyle);\r\n combatStyleElement.innerHTML = combatStyleDetailMap[combatStyle].name;\r\n\r\n let damageTypeElement = document.getElementById(\"combatStat_damageType\");\r\n let damageType = damageTypeDetailMap[player.combatDetails.combatStats.damageType];\r\n damageTypeElement.setAttribute(\"data-i18n\", \"damageTypeNames.\" + damageType.hrid);\r\n damageTypeElement.innerHTML = damageType.name;\r\n\r\n let attackIntervalElement = document.getElementById(\"combatStat_attackInterval\");\r\n attackIntervalElement.innerHTML = (player.combatDetails.combatStats.attackInterval / 1e9).toLocaleString() + \"s\";\r\n\r\n let primaryTrainingElement = document.getElementById(\"combatStat_primaryTraining\");\r\n let primaryTraining = player.combatDetails.combatStats.primaryTraining;\r\n primaryTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + primaryTraining);\r\n primaryTrainingElement.innerHTML = primaryTraining;\r\n\r\n let focusTrainingElement = document.getElementById(\"combatStat_focusTraining\");\r\n let focusTraining = player.combatDetails.combatStats.focusTraining;\r\n if (focusTraining) {\r\n focusTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + focusTraining);\r\n } else {\r\n focusTrainingElement.setAttribute(\"data-i18n\", \"characterSelectPage.slots.empty\");\r\n }\r\n focusTrainingElement.innerHTML = focusTraining;\r\n\r\n [\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"stabAccuracyRating\",\r\n \"stabMaxDamage\",\r\n \"slashAccuracyRating\",\r\n \"slashMaxDamage\",\r\n \"smashAccuracyRating\",\r\n \"smashMaxDamage\",\r\n \"rangedAccuracyRating\",\r\n \"rangedMaxDamage\",\r\n \"magicAccuracyRating\",\r\n \"magicMaxDamage\",\r\n \"defensiveMaxDamage\",\r\n \"stabEvasionRating\",\r\n \"slashEvasionRating\",\r\n \"smashEvasionRating\",\r\n \"rangedEvasionRating\",\r\n \"magicEvasionRating\",\r\n \"totalArmor\",\r\n \"totalWaterResistance\",\r\n \"totalNatureResistance\",\r\n \"totalFireResistance\",\r\n \"totalThreat\"\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n element.innerHTML = Math.floor(player.combatDetails[stat]);\r\n });\r\n\r\n [\r\n \"abilityHaste\",\r\n \"tenacity\"\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n element.innerHTML = Math.floor(player.combatDetails.combatStats[stat]);\r\n });\r\n\r\n [\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"combatExperience\",\r\n \"taskDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"drinkConcentration\",\r\n \"foodHaste\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\"\r\n\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n let value = (100 * player.combatDetails.combatStats[stat]).toLocaleString([], {\r\n minimumFractionDigits: 0,\r\n maximumFractionDigits: 4,\r\n });\r\n element.innerHTML = value + \"%\";\r\n });\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Level\r\n\r\nfunction initLevelSection() {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n levelInput.value = 1;\r\n levelInput.addEventListener(\"change\", levelInputHandler);\r\n });\r\n}\r\n\r\nfunction levelInputHandler() {\r\n updateLevels();\r\n updateUI();\r\n}\r\n\r\nfunction updateLevels() {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n player[skill + \"Level\"] = Number(levelInput.value);\r\n });\r\n updateCombatLevel();\r\n}\r\n\r\nfunction calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel) {\r\n return Math.floor(\r\n 0.1 * (staminaLevel + intelligenceLevel + attackLevel + defenseLevel + Math.max(meleeLevel, rangedLevel, magicLevel))\r\n + 0.5 * Math.max(attackLevel, defenseLevel, meleeLevel, rangedLevel, magicLevel)\r\n );\r\n}\r\n\r\n\r\nfunction updateCombatLevel() {\r\n let staminaLevel = player[\"staminaLevel\"];\r\n let intelligenceLevel = player[\"intelligenceLevel\"];\r\n let defenseLevel = player[\"defenseLevel\"];\r\n let attackLevel = player[\"attackLevel\"];\r\n let meleeLevel = player[\"meleeLevel\"];\r\n let rangedLevel = player[\"rangedLevel\"];\r\n let magicLevel = player[\"magicLevel\"];\r\n\r\n let levelInput = document.getElementById(\"inputLevel_combat\");\r\n levelInput.value = calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel);;\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Food\r\n\r\nfunction initFoodSection() {\r\n for (let i = 0; i < 3; i++) {\r\n let element = document.getElementById(\"selectFood_\" + i);\r\n\r\n let gameFoods = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/food\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const food of Object.values(gameFoods)) {\r\n let opt = new Option(food.name, food.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + food.hrid);\r\n element.add(opt);\r\n }\r\n\r\n element.addEventListener(\"change\", foodSelectHandler);\r\n }\r\n}\r\n\r\nfunction foodSelectHandler() {\r\n updateFoodState();\r\n updateUI();\r\n}\r\n\r\nfunction updateFoodState() {\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n food[i] = foodSelect.value;\r\n if (food[i] && !triggerMap[food[i]]) {\r\n let gameItem = itemDetailMap[food[i]];\r\n triggerMap[food[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateFoodUI() {\r\n for (let i = 0; i < 3; i++) {\r\n let selectElement = document.getElementById(\"selectFood_\" + i);\r\n let triggerButton = document.getElementById(\"buttonFoodTrigger_\" + i);\r\n\r\n selectElement.disabled = i >= player.combatDetails.combatStats.foodSlots;\r\n triggerButton.disabled = i >= player.combatDetails.combatStats.foodSlots || !food[i];\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Drinks\r\n\r\nfunction initDrinksSection() {\r\n for (let i = 0; i < 3; i++) {\r\n let element = document.getElementById(\"selectDrink_\" + i);\r\n\r\n let gameDrinks = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/drink\")\r\n .filter((item) => item.consumableDetail.usableInActionTypeMap[\"/action_types/combat\"])\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const drink of Object.values(gameDrinks)) {\r\n let opt = new Option(drink.name, drink.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + drink.hrid);\r\n element.add(opt);\r\n }\r\n\r\n element.addEventListener(\"change\", drinkSelectHandler);\r\n }\r\n}\r\n\r\nfunction drinkSelectHandler() {\r\n updateDrinksState();\r\n updateDrinksUI();\r\n}\r\n\r\nfunction updateDrinksState() {\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n drinks[i] = drinkSelect.value;\r\n if (drinks[i] && !triggerMap[drinks[i]]) {\r\n let gameItem = itemDetailMap[drinks[i]];\r\n triggerMap[drinks[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateDrinksUI() {\r\n for (let i = 0; i < 3; i++) {\r\n let selectElement = document.getElementById(\"selectDrink_\" + i);\r\n let triggerButton = document.getElementById(\"buttonDrinkTrigger_\" + i);\r\n\r\n selectElement.disabled = i >= player.combatDetails.combatStats.drinkSlots;\r\n triggerButton.disabled = i >= player.combatDetails.combatStats.drinkSlots || !drinks[i];\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Abilities\r\n\r\nfunction initAbilitiesSection() {\r\n for (let i = 0; i < 5; i++) {\r\n let selectElement = document.getElementById(\"selectAbility_\" + i);\r\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\r\n\r\n inputElement.value = 1;\r\n\r\n let gameAbilities;\r\n if (i == 0) {\r\n gameAbilities = Object.values(abilityDetailMap).filter(x => x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\r\n } else {\r\n gameAbilities = Object.values(abilityDetailMap).filter(x => !x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\r\n }\r\n\r\n\r\n for (const ability of Object.values(gameAbilities)) {\r\n let opt = new Option(ability.name, ability.hrid);\r\n opt.setAttribute(\"data-i18n\", \"abilityNames.\" + ability.hrid);\r\n selectElement.add(opt);\r\n }\r\n\r\n selectElement.addEventListener(\"change\", abilitySelectHandler);\r\n }\r\n}\r\n\r\nfunction abilitySelectHandler() {\r\n updateAbilityState();\r\n updateAbilityUI();\r\n}\r\n\r\nfunction updateAbilityState() {\r\n for (let i = 0; i < 5; i++) {\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\r\n abilities[i] = abilitySelect.value;\r\n if (abilities[i] && !triggerMap[abilities[i]]) {\r\n let gameAbility = abilityDetailMap[abilities[i]];\r\n triggerMap[abilities[i]] = structuredClone(gameAbility.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateAbilityUI() {\r\n for (let i = 0; i < 5; i++) {\r\n let selectElement = document.getElementById(\"selectAbility_\" + i);\r\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let triggerButton = document.getElementById(\"buttonAbilityTrigger_\" + i);\r\n\r\n selectElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\r\n inputElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\r\n triggerButton.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1] || !abilities[i];\r\n let moveUpButton = document.getElementById(\"selectAbilityMoveUp_\" + i);\r\n moveUpButton.onclick = () => swapAbilityOrder(i, -1);\r\n }\r\n}\r\n\r\nfunction swapAbilityOrder(abilityIndex, step) {\r\n const swapIndex = abilityIndex + step;\r\n if (swapIndex < 0 || swapIndex > 4) {\r\n return;\r\n }\r\n\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilityIndex);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilityIndex);\r\n\r\n const tempAbility = abilities[abilityIndex];\r\n abilities[abilityIndex] = abilities[swapIndex];\r\n abilities[swapIndex] = tempAbility;\r\n\r\n const tempLevel = abilityLevelInput.value;\r\n abilityLevelInput.value = document.getElementById(\"inputAbilityLevel_\" + swapIndex).value;\r\n document.getElementById(\"inputAbilityLevel_\" + swapIndex).value = tempLevel;\r\n\r\n abilitySelect.value = document.getElementById(\"selectAbility_\" + (swapIndex)).value;\r\n document.getElementById(\"selectAbility_\" + swapIndex).value = abilities[swapIndex];\r\n\r\n updateAbilityState();\r\n updateAbilityUI();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Trigger\r\n\r\nfunction initTriggerModal() {\r\n let modal = document.getElementById(\"triggerModal\");\r\n modal.addEventListener(\"show.bs.modal\", (event) => triggerModalShownHandler(event));\r\n\r\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\r\n triggerSaveButton.addEventListener(\"click\", (event) => triggerModalSaveHandler(event));\r\n\r\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\r\n triggerAddButton.addEventListener(\"click\", (event) => triggerAddButtonHandler(event));\r\n\r\n let triggerDefaultButton = document.getElementById(\"buttonDefaultTrigger\");\r\n triggerDefaultButton.addEventListener(\"click\", (event) => triggerDefaultButtonHandler(event));\r\n\r\n for (let i = 0; i < 4; i++) {\r\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\r\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\r\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\r\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\r\n let triggerRemoveButton = document.getElementById(\"buttonRemoveTrigger_\" + i);\r\n\r\n triggerDependencySelect.addEventListener(\"change\", (event) => triggerDependencySelectHandler(event, i));\r\n triggerConditionSelect.addEventListener(\"change\", (event) => triggerConditionSelectHandler(event, i));\r\n triggerComparatorSelect.addEventListener(\"change\", (event) => triggerComparatorSelectHander(event, i));\r\n triggerValueInput.addEventListener(\"change\", (event) => triggerValueInputHandler(event, i));\r\n triggerRemoveButton.addEventListener(\"click\", (event) => triggerRemoveButtonHandler(event, i));\r\n }\r\n}\r\n\r\nfunction triggerModalShownHandler(event) {\r\n let triggerButton = event.relatedTarget;\r\n\r\n let triggerType = triggerButton.getAttribute(\"data-bs-triggertype\");\r\n let triggerIndex = Number(triggerButton.getAttribute(\"data-bs-triggerindex\"));\r\n\r\n let triggerTarget;\r\n switch (triggerType) {\r\n case \"food\":\r\n triggerTarget = food[triggerIndex];\r\n break;\r\n case \"drink\":\r\n triggerTarget = drinks[triggerIndex];\r\n break;\r\n case \"ability\":\r\n triggerTarget = abilities[triggerIndex];\r\n break;\r\n }\r\n\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n triggerTargetnput.value = triggerTarget;\r\n modalTriggers = triggerMap[triggerTarget];\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerModalSaveHandler(event) {\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n let triggerTarget = triggerTargetnput.value;\r\n\r\n triggerMap[triggerTarget] = modalTriggers;\r\n}\r\n\r\nfunction triggerDependencySelectHandler(event, index) {\r\n modalTriggers[index].dependencyHrid = event.target.value;\r\n modalTriggers[index].conditionHrid = \"\";\r\n modalTriggers[index].comparatorHrid = \"\";\r\n modalTriggers[index].value = 0;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerConditionSelectHandler(event, index) {\r\n modalTriggers[index].conditionHrid = event.target.value;\r\n modalTriggers[index].comparatorHrid = \"\";\r\n modalTriggers[index].value = 0;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerComparatorSelectHander(event, index) {\r\n modalTriggers[index].comparatorHrid = event.target.value;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerValueInputHandler(event, index) {\r\n modalTriggers[index].value = Number(event.target.value);\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerRemoveButtonHandler(event, index) {\r\n modalTriggers.splice(index, 1);\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerAddButtonHandler(event) {\r\n if (modalTriggers.length == 4) {\r\n return;\r\n }\r\n\r\n modalTriggers.push({\r\n dependencyHrid: \"\",\r\n conditionHrid: \"\",\r\n comparatorHrid: \"\",\r\n value: 0,\r\n });\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerDefaultButtonHandler(event) {\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n let triggerTarget = triggerTargetnput.value;\r\n\r\n if (triggerTarget.startsWith(\"/items/\")) {\r\n modalTriggers = structuredClone(itemDetailMap[triggerTarget].consumableDetail.defaultCombatTriggers);\r\n } else {\r\n modalTriggers = structuredClone(abilityDetailMap[triggerTarget].defaultCombatTriggers);\r\n }\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction updateTriggerModal() {\r\n let triggerStartTextElement = document.getElementById(\"triggerStartText\");\r\n if (modalTriggers.length == 0) {\r\n triggerStartTextElement.innerHTML = \"Activate as soon as it's off cooldown\";\r\n } else {\r\n triggerStartTextElement.innerHTML = \"Activate when:\";\r\n }\r\n\r\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\r\n triggerAddButton.disabled = modalTriggers.length == 4;\r\n\r\n let triggersValid = true;\r\n\r\n for (let i = 0; i < 4; i++) {\r\n let triggerElement = document.getElementById(\"modalTrigger_\" + i);\r\n\r\n if (!modalTriggers[i]) {\r\n hideElement(triggerElement);\r\n continue;\r\n }\r\n\r\n showElement(triggerElement);\r\n\r\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\r\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\r\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\r\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\r\n\r\n showElement(triggerDependencySelect);\r\n fillTriggerDependencySelect(triggerDependencySelect);\r\n\r\n if (modalTriggers[i].dependencyHrid == \"\") {\r\n hideElement(triggerConditionSelect);\r\n hideElement(triggerComparatorSelect);\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerDependencySelect.value = modalTriggers[i].dependencyHrid;\r\n showElement(triggerConditionSelect);\r\n fillTriggerConditionSelect(triggerConditionSelect, modalTriggers[i].dependencyHrid);\r\n\r\n if (modalTriggers[i].conditionHrid == \"\") {\r\n hideElement(triggerComparatorSelect);\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerConditionSelect.value = modalTriggers[i].conditionHrid;\r\n showElement(triggerComparatorSelect);\r\n fillTriggerComparatorSelect(triggerComparatorSelect, modalTriggers[i].conditionHrid);\r\n\r\n if (modalTriggers[i].comparatorHrid == \"\") {\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerComparatorSelect.value = modalTriggers[i].comparatorHrid;\r\n\r\n if (combatTriggerComparatorDetailMap[modalTriggers[i].comparatorHrid].allowValue) {\r\n showElement(triggerValueInput);\r\n triggerValueInput.value = modalTriggers[i].value;\r\n } else {\r\n hideElement(triggerValueInput);\r\n }\r\n }\r\n\r\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\r\n triggerSaveButton.disabled = !triggersValid;\r\n\r\n updateContent();\r\n}\r\n\r\nfunction fillTriggerDependencySelect(element) {\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const dependency of Object.values(combatTriggerDependencyDetailMap).sort(\r\n (a, b) => a.sortIndex - b.sortIndex\r\n )) {\r\n let opt = new Option(dependency.name, dependency.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerDependencyNames.\" + dependency.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction fillTriggerConditionSelect(element, dependencyHrid) {\r\n let dependency = combatTriggerDependencyDetailMap[dependencyHrid];\r\n\r\n let conditions;\r\n if (dependency.isSingleTarget) {\r\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isSingleTarget);\r\n } else {\r\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isMultiTarget);\r\n }\r\n\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const condition of Object.values(conditions).sort((a, b) => a.sortIndex - b.sortIndex)) {\r\n let opt = new Option(condition.name, condition.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerConditionNames.\" + condition.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction fillTriggerComparatorSelect(element, conditionHrid) {\r\n let condition = combatTriggerConditionDetailMap[conditionHrid];\r\n\r\n let comparators = condition.allowedComparatorHrids.map((hrid) => combatTriggerComparatorDetailMap[hrid]);\r\n\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const comparator of Object.values(comparators).sort((a, b) => a.sortIndex - b.sortIndex)) {\r\n let opt = new Option(comparator.name, comparator.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerComparatorNames.\" + comparator.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction hideElement(element) {\r\n element.classList.remove(\"d-flex\");\r\n element.classList.add(\"d-none\");\r\n}\r\n\r\nfunction showElement(element) {\r\n element.classList.remove(\"d-none\");\r\n element.classList.add(\"d-flex\");\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Zones\r\n\r\nfunction initZones() {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n\r\n // TOOD dungeon wave spawns\r\n let gameZones = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const zone of Object.values(gameZones)) {\r\n let opt = new Option(zone.name, zone.hrid);\r\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + zone.hrid);\r\n zoneSelect.add(opt);\r\n }\r\n\r\n\r\n let zoneCheckBox = document.getElementById(\"zoneCheckBox\");\r\n let checkAllZonesToggle = document.getElementById('checkAllZones');\r\n\r\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\r\n simAllZonesToggle.addEventListener(\"change\", (event) => {\r\n if (simAllZonesToggle.checked) {\r\n zoneCheckBox.classList.remove(\"d-none\");\r\n zoneCheckBox.querySelectorAll(\".zone-checkbox\").forEach(checkbox => checkbox.checked = true);\r\n checkAllZonesToggle.checked = true;\r\n } else {\r\n zoneCheckBox.classList.add(\"d-none\");\r\n }\r\n });\r\n\r\n let zoneHrids = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .flat();\r\n\r\n for (const zoneHrid of zoneHrids) {\r\n const newZone = document.createElement('div');\r\n newZone.classList.add('form-check');\r\n newZone.innerHTML = `\r\n \r\n \r\n `;\r\n zoneCheckBox.append(newZone);\r\n }\r\n\r\n let checkZoneToggles = document.querySelectorAll('.zone-checkbox');\r\n checkAllZonesToggle.addEventListener('change', () => {\r\n checkZoneToggles.forEach(cb => cb.checked = checkAllZonesToggle.checked);\r\n });\r\n\r\n checkZoneToggles.forEach(cb =>\r\n cb.addEventListener('change', () => {\r\n checkAllZonesToggle.checked = [...checkZoneToggles].every(x => x.checked);\r\n })\r\n );\r\n\r\n\r\n let soloCheckBox = document.getElementById(\"soloCheckBox\");\r\n let checkAllSolosToggle = document.getElementById('checkAllSolos');\r\n\r\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\r\n simAllSoloToggle.addEventListener(\"change\", (event) => {\r\n if (simAllSoloToggle.checked) {\r\n soloCheckBox.classList.remove(\"d-none\");\r\n soloCheckBox.querySelectorAll(\".solo-checkbox\").forEach(checkbox => checkbox.checked = true);\r\n checkAllSolosToggle.checked = true;\r\n } else {\r\n soloCheckBox.classList.add(\"d-none\");\r\n }\r\n });\r\n\r\n let soloHrids = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount == 1)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .flat();\r\n\r\n for (const zoneHrid of soloHrids) {\r\n const newZone = document.createElement('div');\r\n newZone.classList.add('form-check');\r\n newZone.innerHTML = `\r\n \r\n \r\n `;\r\n soloCheckBox.append(newZone);\r\n }\r\n\r\n let checkSoloToggles = document.querySelectorAll('.solo-checkbox');\r\n checkAllSolosToggle.addEventListener('change', () => {\r\n checkSoloToggles.forEach(cb => cb.checked = checkAllSolosToggle.checked);\r\n });\r\n\r\n checkSoloToggles.forEach(cb =>\r\n cb.addEventListener('change', () => {\r\n checkAllSolosToggle.checked = [...checkSoloToggles].every(x => x.checked);\r\n })\r\n );\r\n}\r\n\r\nfunction initDungeons() {\r\n let dungeonSelect = document.getElementById(\"selectDungeon\");\r\n\r\n let gameDungeons = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category == \"/action_categories/combat/dungeons\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const dungeon of Object.values(gameDungeons)) {\r\n let opt = new Option(dungeon.name, dungeon.hrid);\r\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + dungeon.hrid);\r\n dungeonSelect.add(opt);\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Simulation Result\r\n\r\nfunction createDamageDoneAccordion(enemyIndex) {\r\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageDoneAccordionEnemy${enemyIndex}`);\r\n\r\n const colDiv = createElement('div', 'col');\r\n const accordionMainDiv = createElement('div', 'accordion');\r\n const accordionItemDiv = createElement('div', 'accordion-item');\r\n\r\n const headerH2 = createElement('h2', 'accordion-header');\r\n const button = createElement('button', 'accordion-button collapsed',\r\n `Damage Done (Enemy ${enemyIndex})`,\r\n `buttonSimulationResultDamageDoneAccordionEnemy${enemyIndex}`\r\n );\r\n button.setAttribute('type', 'button');\r\n button.setAttribute('data-bs-toggle', 'collapse');\r\n button.setAttribute('data-bs-target', `#collapseDamageDone${enemyIndex}`);\r\n button.style.padding = '0.5em';\r\n\r\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageDone${enemyIndex}`);\r\n const accordionBodyDiv = createElement('div', 'accordion-body');\r\n\r\n const headerRow = createElement('div', 'row');\r\n headerRow.innerHTML = `\r\n
Source
\r\n
Hitchance
\r\n
DPS
\r\n
%
\r\n `;\r\n\r\n const resultDiv = createElement('div', '', '', `simulationResultDamageDoneEnemy${enemyIndex}`);\r\n\r\n accordionBodyDiv.appendChild(headerRow);\r\n accordionBodyDiv.appendChild(resultDiv);\r\n collapseDiv.appendChild(accordionBodyDiv);\r\n headerH2.appendChild(button);\r\n accordionItemDiv.appendChild(headerH2);\r\n accordionItemDiv.appendChild(collapseDiv);\r\n accordionMainDiv.appendChild(accordionItemDiv);\r\n colDiv.appendChild(accordionMainDiv);\r\n accordionDiv.appendChild(colDiv);\r\n\r\n return accordionDiv;\r\n}\r\nfunction createDamageTakenAccordion(enemyIndex) {\r\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageTakenAccordionEnemy${enemyIndex}`);\r\n\r\n const colDiv = createElement('div', 'col');\r\n const accordionMainDiv = createElement('div', 'accordion');\r\n const accordionItemDiv = createElement('div', 'accordion-item');\r\n\r\n const headerH2 = createElement('h2', 'accordion-header');\r\n const button = createElement('button', 'accordion-button collapsed',\r\n `Damage Taken (Enemy ${enemyIndex})`,\r\n `buttonSimulationResultDamageTakenAccordionEnemy${enemyIndex}`\r\n );\r\n button.setAttribute('type', 'button');\r\n button.setAttribute('data-bs-toggle', 'collapse');\r\n button.setAttribute('data-bs-target', `#collapseDamageTaken${enemyIndex}`);\r\n button.style.padding = '0.5em';\r\n\r\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageTaken${enemyIndex}`);\r\n const accordionBodyDiv = createElement('div', 'accordion-body');\r\n\r\n const headerRow = createElement('div', 'row');\r\n headerRow.innerHTML = `\r\n
Source
\r\n
Hitchance
\r\n
DPS
\r\n
%
\r\n `;\r\n\r\n const resultDiv = createElement('div', '', '', `simulationResultDamageTakenEnemy${enemyIndex}`);\r\n\r\n accordionBodyDiv.appendChild(headerRow);\r\n accordionBodyDiv.appendChild(resultDiv);\r\n collapseDiv.appendChild(accordionBodyDiv);\r\n headerH2.appendChild(button);\r\n accordionItemDiv.appendChild(headerH2);\r\n accordionItemDiv.appendChild(collapseDiv);\r\n accordionMainDiv.appendChild(accordionItemDiv);\r\n colDiv.appendChild(accordionMainDiv);\r\n accordionDiv.appendChild(colDiv);\r\n\r\n return accordionDiv;\r\n}\r\n\r\n\r\nfunction initDamageDoneTaken() {\r\n for (let i = 64; i > 0; i--) {\r\n document.getElementById(\"simulationResultTotalDamageDone\").insertAdjacentElement('afterend', createDamageDoneAccordion(i));\r\n document.getElementById(\"simulationResultTotalDamageTaken\").insertAdjacentElement('afterend', createDamageTakenAccordion(i));\r\n }\r\n}\r\n\r\nfunction showSimulationResult(simResult) {\r\n currentSimResults = simResult;\r\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\r\n expensesModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\r\n revenueModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let noRngRevenueModalTable = document.querySelector(\"#noRngRevenueTable > tbody\");\r\n noRngRevenueModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let playerToDisplay = \"player1\";\r\n if (selectedPlayers.includes(parseInt(currentPlayerTabId))) {\r\n playerToDisplay = \"player\" + currentPlayerTabId;\r\n }\r\n if (!simResult.dropRateMultiplier[playerToDisplay]) {\r\n return;\r\n }\r\n\r\n showKills(simResult, playerToDisplay);\r\n showDeaths(simResult, playerToDisplay);\r\n showExperienceGained(simResult, playerToDisplay);\r\n showConsumablesUsed(simResult, playerToDisplay);\r\n showHpSpent(simResult, playerToDisplay);\r\n showManaUsed(simResult, playerToDisplay);\r\n showHitpointsGained(simResult, playerToDisplay);\r\n showManapointsGained(simResult, playerToDisplay);\r\n showDamageDone(simResult, playerToDisplay);\r\n showDamageTaken(simResult, playerToDisplay);\r\n renderWipeEvents(simResult);\r\n window.profit = window.revenue - window.expenses;\r\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\r\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\r\n window.noRngProfit = window.noRngRevenue - window.expenses;\r\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\r\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\r\n}\r\n\r\nfunction showAllSimulationResults(simResults) {\r\n let displaySimResults = manipulateSimResultsDataForDisplay(simResults);\r\n updateAllSimsModal(displaySimResults);\r\n}\r\n\r\nfunction manipulateSimResultsDataForDisplay(simResults) {\r\n let displaySimResults = [];\r\n for (let i = 0; i < simResults.length; i++) {\r\n for (let j = 0; j < selectedPlayers.length; j++) {\r\n let playerToDisplay = \"player\" + selectedPlayers[j].toString();\r\n let simResult = simResults[i];\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let zoneName = simResult.zoneName;\r\n let difficultyTier = simResult.difficultyTier;\r\n let encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\r\n let playerDeaths = simResult.deaths[playerToDisplay] ?? 0;\r\n let deathsPerHour = (playerDeaths / hoursSimulated).toFixed(2);\r\n\r\n let totalExperience = 0;\r\n if (simResult.experienceGained[playerToDisplay]) {\r\n totalExperience = Object.values(simResult.experienceGained[playerToDisplay]).reduce((prev, cur) => prev + cur, 0);\r\n }\r\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\r\n\r\n let experiencePerHour = {};\r\n const skills = [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"];\r\n skills.forEach((skill) => {\r\n const skillLower = skill.toLowerCase();\r\n let experience = simResult.experienceGained[playerToDisplay]?.[skillLower] ?? 0;\r\n let experiencePerHourValue = 0;\r\n if (experience != 0) {\r\n experiencePerHourValue = (experience / hoursSimulated).toFixed(0);\r\n }\r\n experiencePerHour[skill] = experiencePerHourValue;\r\n });\r\n getDropProfit(simResult, playerToDisplay);\r\n let noRngRevenue = simResult[\"noRngRevenue\"];\r\n let noRngProfit = simResult[\"noRngProfit\"];\r\n let expenses = simResult[\"expenses\"];\r\n\r\n let displaySimRow = {\r\n \"ZoneName\": zoneName, \"DifficultyTier\": difficultyTier, \"Player\": playerToDisplay, \"Encounters\": encountersPerHour, \"Deaths\": deathsPerHour,\r\n \"TotalExperience\": totalExperiencePerHour, \"Stamina\": experiencePerHour[\"Stamina\"],\r\n \"Intelligence\": experiencePerHour[\"Intelligence\"], \"Attack\": experiencePerHour[\"Attack\"],\r\n \"Magic\": experiencePerHour[\"Magic\"], \"Ranged\": experiencePerHour[\"Ranged\"],\r\n \"Melee\": experiencePerHour[\"Melee\"], \"Defense\": experiencePerHour[\"Defense\"],\r\n \"noRngRevenue\": noRngRevenue,\r\n \"expenses\": expenses,\r\n \"noRngProfit\": noRngProfit\r\n };\r\n displaySimResults.push(displaySimRow);\r\n }\r\n }\r\n return displaySimResults;\r\n}\r\n\r\nfunction fidDropAmount(dropAmount) {\r\n if (Number.isInteger(dropAmount)) return dropAmount;\r\n\r\n const intPart = Math.floor(dropAmount);\r\n const fracPart = dropAmount - intPart;\r\n return Math.random() < fracPart ? intPart + 1 : intPart;\r\n}\r\n\r\nfunction calcDropMaps(simResult, playerToDisplay) {\r\n let dropRateMultiplier = simResult.dropRateMultiplier[playerToDisplay];\r\n let rareFindMultiplier = simResult.rareFindMultiplier[playerToDisplay];\r\n let combatDropQuantity = simResult.combatDropQuantity[playerToDisplay];\r\n let debuffOnLevelGap = simResult.debuffOnLevelGap[playerToDisplay];\r\n\r\n let numberOfPlayers = simResult.numberOfPlayers;\r\n let monsters = Object.keys(simResult.deaths)\r\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\r\n .sort();\r\n\r\n const totalDropMap = new Map();\r\n const noRngTotalDropMap = new Map();\r\n for (const monster of monsters) {\r\n const dropMap = new Map();\r\n const rareDropMap = new Map();\r\n if (combatMonsterDetailMap[monster].dropTable) {\r\n for (const drop of combatMonsterDetailMap[monster].dropTable) {\r\n if (drop.minDifficultyTier > simResult.difficultyTier) {\r\n continue;\r\n }\r\n\r\n let multiplier = 1.0 + 0.1 * simResult.difficultyTier;\r\n let dropRate = Math.min(1.0, multiplier * (drop.dropRate + (drop.dropRatePerDifficultyTier ?? 0) * simResult.difficultyTier));\r\n if (dropRate <= 0) continue;\r\n\r\n dropMap.set(drop.itemHrid, { \"dropRate\": Math.min(1.0, dropRate * dropRateMultiplier), \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\r\n }\r\n if (combatMonsterDetailMap[monster].rareDropTable)\r\n for (const drop of combatMonsterDetailMap[monster].rareDropTable) {\r\n if (drop.minDifficultyTier > simResult.difficultyTier) {\r\n continue;\r\n }\r\n rareDropMap.set(drop.itemHrid, { \"dropRate\": drop.dropRate * rareFindMultiplier, \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\r\n }\r\n\r\n for (let dropObject of dropMap.values()) {\r\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\r\n\r\n }\r\n for (let dropObject of rareDropMap.values()) {\r\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\r\n }\r\n\r\n for (let i = 0; i < simResult.deaths[monster]; i++) {\r\n for (let dropObject of dropMap.values()) {\r\n let chance = Math.random();\r\n if (chance <= dropObject.dropRate / numberOfPlayers) {\r\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\r\n dropObject.number = dropObject.number + fidDropAmount(amount);\r\n }\r\n }\r\n for (let dropObject of rareDropMap.values()) {\r\n let chance = Math.random();\r\n if (chance <= dropObject.dropRate / numberOfPlayers) {\r\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\r\n dropObject.number = dropObject.number + fidDropAmount(amount);\r\n }\r\n }\r\n }\r\n for (let [name, dropObject] of dropMap.entries()) {\r\n if (totalDropMap.has(name)) {\r\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\r\n } else {\r\n totalDropMap.set(name, dropObject.number);\r\n }\r\n if (noRngTotalDropMap.has(name)) {\r\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\r\n } else {\r\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\r\n }\r\n }\r\n for (let [name, dropObject] of rareDropMap.entries()) {\r\n if (totalDropMap.has(name)) {\r\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\r\n } else {\r\n totalDropMap.set(name, dropObject.number);\r\n }\r\n if (noRngTotalDropMap.has(name)) {\r\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\r\n } else {\r\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return { totalDropMap, noRngTotalDropMap };\r\n}\r\n\r\nfunction getDropProfit(simResult, playerToDisplay) {\r\n let { totalDropMap, noRngTotalDropMap } = calcDropMaps(simResult, playerToDisplay);\r\n\r\n let noRngTotal = 0;\r\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\r\n let price = -1;\r\n let revenueSetting = document.getElementById('selectPrices_drops').value;\r\n if (window.prices) {\r\n let item = window.prices[name];\r\n if (item) {\r\n if (revenueSetting == 'bid') {\r\n if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n } else if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n }\r\n } else if (revenueSetting == 'ask') {\r\n if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n } else if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n }\r\n }\r\n if (price == -1) {\r\n price = item['vendor'];\r\n }\r\n }\r\n }\r\n noRngTotal += price * dropAmount;\r\n }\r\n\r\n let consumablesUsed = simResult.consumablesUsed?.[playerToDisplay];\r\n\r\n if (consumablesUsed) {\r\n consumablesUsed = Object.entries(consumablesUsed).sort((a, b) => b[1] - a[1]);\r\n } else {\r\n consumablesUsed = [];\r\n }\r\n\r\n let expenses = 0;\r\n for (const [consumable, amount] of consumablesUsed) {\r\n let price = -1;\r\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\r\n if (window.prices) {\r\n let item = window.prices[consumable];\r\n if (item) {\r\n if (expensesSetting == 'bid') {\r\n if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n } else if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n }\r\n } else if (expensesSetting == 'ask') {\r\n if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n } else if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n }\r\n }\r\n if (price == -1) {\r\n price = item['vendor'];\r\n }\r\n }\r\n }\r\n expenses += price * amount;\r\n }\r\n\r\n simResult[\"noRngRevenue\"] = (noRngTotal).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n simResult[\"expenses\"] = (expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n simResult[\"noRngProfit\"] = (noRngTotal - expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n}\r\n\r\nfunction updateAllSimsModal(data) {\r\n const tableBody = document.getElementById('allZonesData').getElementsByTagName('tbody')[0];\r\n tableBody.innerHTML = '';\r\n data.forEach(item => {\r\n const row = document.createElement('tr');\r\n\r\n Object.keys(item).forEach(key => {\r\n const cell = document.createElement('td');\r\n cell.textContent = item[key];\r\n if (key === 'ZoneName') {\r\n cell.setAttribute(\"data-i18n\", \"actionNames.\" + item[key]);\r\n }\r\n row.appendChild(cell);\r\n });\r\n\r\n tableBody.appendChild(row);\r\n });\r\n\r\n const table = document.getElementById('allZonesData');\r\n const rows = table.getElementsByTagName('tr');\r\n const numCols = rows[0].cells.length;\r\n\r\n // 遍历每一列\r\n for (let col = 5; col < numCols; col++) {\r\n let max = -Infinity;\r\n let maxCell = null;\r\n\r\n // 找到最大值及其单元格\r\n for (let row = 1; row < rows.length; row++) {\r\n const cell = rows[row].cells[col];\r\n const value = parseFloat(cell.textContent.replace(/,/g, ''));\r\n if (value > max) {\r\n max = value;\r\n maxCell = cell;\r\n }\r\n }\r\n\r\n // 将最大值单元格的背景色设置为绿色\r\n if (maxCell && max != 0) {\r\n maxCell.style.backgroundColor = 'green';\r\n maxCell.style.color = 'white'; // 设置文字颜色为白色以提高可读性\r\n }\r\n }\r\n}\r\n\r\nlet currentSortColumn = null;\r\nlet currentSortDirection = 'desc';\r\n\r\nfunction sortTable(tableId, columnIndex, direction) {\r\n const table = document.getElementById(tableId);\r\n const tbody = table.querySelector('tbody');\r\n const rows = Array.from(tbody.querySelectorAll('tr'));\r\n\r\n const sortedRows = rows.sort((rowA, rowB) => {\r\n const cellA = rowA.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\r\n const cellB = rowB.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\r\n\r\n const valueA = parseFloat(cellA.replace(/,/g, ''));\r\n const valueB = parseFloat(cellB.replace(/,/g, ''));\r\n\r\n return direction === 'asc' ? valueA - valueB : valueB - valueA;\r\n });\r\n\r\n sortedRows.forEach(row => tbody.appendChild(row));\r\n updateSortIndicators(tableId, columnIndex, direction);\r\n}\r\n\r\nfunction updateSortIndicators(tableId, columnIndex, direction) {\r\n const headers = document.querySelectorAll(`#${tableId} th`);\r\n headers.forEach((header, index) => {\r\n header.classList.remove('sort-asc', 'sort-desc');\r\n if (index === columnIndex) {\r\n header.classList.add(direction === 'asc' ? 'sort-asc' : 'sort-desc');\r\n }\r\n });\r\n}\r\n\r\ndocument.querySelectorAll('#allZonesData th').forEach((header, index) => {\r\n if (index === 0) return;\r\n if (index === 1) return;\r\n if (index === 2) return;\r\n\r\n header.addEventListener('click', () => {\r\n if (currentSortColumn === index) {\r\n currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';\r\n } else {\r\n currentSortColumn = index;\r\n currentSortDirection = 'desc';\r\n }\r\n sortTable('allZonesData', currentSortColumn, currentSortDirection);\r\n });\r\n});\r\n\r\ndocument.getElementById('buttonExportResults').addEventListener('click', function () {\r\n var table = document.getElementById('allZonesData');\r\n var csv = [];\r\n var rows = table.querySelectorAll('tr');\r\n\r\n for (var i = 0; i < rows.length; i++) {\r\n var row = rows[i];\r\n var cols = row.querySelectorAll('th, td');\r\n var csvRow = [];\r\n\r\n cols.forEach(function (col) {\r\n csvRow.push('\"' + col.innerText.replace(/\"/g, '\"\"') + '\"');\r\n });\r\n\r\n csv.push(csvRow.join(','));\r\n }\r\n\r\n var csvFile = new Blob([csv.join('\\n')], { type: 'text/csv' });\r\n var downloadLink = document.createElement('a');\r\n downloadLink.download = 'simData.csv';\r\n downloadLink.href = URL.createObjectURL(csvFile);\r\n downloadLink.style.display = 'none';\r\n document.body.appendChild(downloadLink);\r\n downloadLink.click();\r\n document.body.removeChild(downloadLink);\r\n});\r\n\r\nfunction showKills(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultKills\");\r\n let dropsResultDiv = document.getElementById(\"simulationResultDrops\");\r\n let noRngDropsResultDiv = document.getElementById(\"noRngDrops\");\r\n let newChildren = [];\r\n let newDropChildren = [];\r\n let newNoRngDropChildren = [];\r\n\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let encountersPerHour = 0;\r\n let encountersRow = null;\r\n if (simResult.isDungeon) {\r\n let wavesCompletedRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Wave Reached\", simResult.maxWaveReached]);\r\n wavesCompletedRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxWaveReached\");\r\n newChildren.push(wavesCompletedRow);\r\n let completedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Completed Dungeons\", simResult.dungeonsCompleted]);\r\n completedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsCompleted\");\r\n newChildren.push(completedDungeonsRow);\r\n if (simResult.dungeonsFailed > 0) {\r\n let failedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Failed Dungeons\", simResult.dungeonsFailed]);\r\n failedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsFailed\");\r\n newChildren.push(failedDungeonsRow);\r\n }\r\n encountersPerHour = (simResult.dungeonsCompleted / hoursSimulated).toFixed(1);\r\n let averageTime = (hoursSimulated * 60 / simResult.dungeonsCompleted).toFixed(1);\r\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Average Time\", averageTime]);\r\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.averageTime\");\r\n } else {\r\n encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\r\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Encounters\", encountersPerHour]);\r\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.encounters\");\r\n }\r\n\r\n if (simResult.maxEnrageStack > 0) {\r\n let enrageRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Enrage Stack\", simResult.maxEnrageStack]);\r\n enrageRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxEnrageStack\");\r\n newChildren.push(enrageRow);\r\n }\r\n\r\n if (simResult.debuffOnLevelGap[playerToDisplay] != 0) {\r\n let debuffOnLevelGapRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Debuff on Level Gap\", Math.round(simResult.debuffOnLevelGap[playerToDisplay] * 100) + \"%\"]);\r\n debuffOnLevelGapRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.debuffOnLevelGap\");\r\n newChildren.push(debuffOnLevelGapRow);\r\n }\r\n\r\n newChildren.push(encountersRow);\r\n\r\n Object.keys(simResult.deaths)\r\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\r\n .sort()\r\n .forEach(monster => {\r\n let killsPerHour = (simResult.deaths[monster] / hoursSimulated).toFixed(1);\r\n let monsterRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [combatMonsterDetailMap[monster].name, killsPerHour]\r\n );\r\n monsterRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + monster);\r\n newChildren.push(monsterRow);\r\n });\r\n\r\n let { totalDropMap, noRngTotalDropMap } = !simResult.isDungeon ? calcDropMaps(simResult, playerToDisplay) : {totalDropMap:new Map(), noRngTotalDropMap:new Map()};\r\n\r\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\r\n let total = 0;\r\n for (let [name, dropAmount] of totalDropMap.entries()) {\r\n let dropRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [name, dropAmount.toLocaleString()]\r\n );\r\n dropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\r\n newDropChildren.push(dropRow);\r\n\r\n let tableRow = ' tbody\");\r\n let noRngTotal = 0;\r\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\r\n let noRngDropRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [name, dropAmount.toLocaleString()]\r\n );\r\n noRngDropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\r\n newNoRngDropChildren.push(noRngDropRow);\r\n\r\n let tableRow = ' prev + cur, 0);\r\n }\r\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\r\n let totalRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Total\", totalExperiencePerHour]);\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"].forEach((skill) => {\r\n let experience = simResult.experienceGained[playerToDisplay]?.[skill.toLowerCase()] ?? 0;\r\n if (experience == 0) {\r\n return;\r\n }\r\n let experiencePerHour = (experience / hoursSimulated).toFixed(0);\r\n let experienceRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [skill, experiencePerHour]);\r\n experienceRow.firstElementChild.setAttribute(\"data-i18n\", \"leaderboardCategoryNames.\" + skill.toLowerCase());\r\n newChildren.push(experienceRow);\r\n });\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showHpSpent(simResult, playerToDisplay) {\r\n let hpSpentHeadingDiv = document.getElementById(\"simulationHpSpentHeading\");\r\n hpSpentHeadingDiv.classList.add(\"d-none\");\r\n let hpSpentDiv = document.getElementById(\"simulationHpSpent\");\r\n hpSpentDiv.classList.add(\"d-none\");\r\n\r\n if (simResult.hitpointsSpent[playerToDisplay]) {\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let hpSpentSources = [];\r\n for (const source of Object.keys(simResult.hitpointsSpent[playerToDisplay])) {\r\n let hpSpentPerHour = (simResult.hitpointsSpent[playerToDisplay][source] / hoursSimulated).toFixed(2);\r\n let hpSpentRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [abilityDetailMap[source].name, hpSpentPerHour]);\r\n hpSpentRow.firstElementChild.setAttribute(\"data-i18n\", \"abilityNames.\" + source);\r\n hpSpentSources.push(hpSpentRow);\r\n }\r\n hpSpentDiv.replaceChildren(...hpSpentSources);\r\n hpSpentHeadingDiv.classList.remove(\"d-none\");\r\n hpSpentDiv.classList.remove(\"d-none\");\r\n }\r\n}\r\n\r\nfunction showConsumablesUsed(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultConsumablesUsed\");\r\n let newChildren = [];\r\n\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n\r\n if (!simResult.consumablesUsed[playerToDisplay]) {\r\n resultDiv.replaceChildren(...newChildren);\r\n window.expenses = 0;\r\n return;\r\n }\r\n\r\n let consumablesUsed = Object.entries(simResult.consumablesUsed[playerToDisplay]).sort((a, b) => b[1] - a[1]);\r\n\r\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\r\n let total = 0;\r\n for (const [consumable, amount] of consumablesUsed) {\r\n let consumablesPerHour = (amount / hoursSimulated).toFixed(0);\r\n let consumableRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [itemDetailMap[consumable].name, consumablesPerHour]\r\n );\r\n consumableRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + consumable);\r\n newChildren.push(consumableRow);\r\n\r\n let tableRow = ' b[1] - a[1]);\r\n\r\n let totalHitpointsGained = hitpointsGained.reduce((prev, cur) => prev + cur[1], 0);\r\n let totalHitpointsPerSecond = (totalHitpointsGained / secondsSimulated).toFixed(2);\r\n let totalRow = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [\"Total\", totalHitpointsPerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [source, amount] of hitpointsGained) {\r\n if (amount == 0) {\r\n continue;\r\n }\r\n\r\n let sourceText;\r\n let sourceFullHrid;\r\n switch (source) {\r\n case \"regen\":\r\n sourceText = \"Regen\";\r\n sourceFullHrid = \"combatStats.hpRegenPer10\";\r\n break;\r\n case \"lifesteal\":\r\n sourceText = \"Life Steal\";\r\n sourceFullHrid = \"combatStats.lifeSteal\";\r\n break;\r\n case \"bloom\":\r\n sourceText = \"Bloom\";\r\n sourceFullHrid = \"combatStats.bloom\";\r\n break;\r\n default:\r\n if (itemDetailMap[source]) {\r\n sourceText = itemDetailMap[source].name;\r\n sourceFullHrid = \"itemNames.\" + source;\r\n } else if (abilityDetailMap[source]) {\r\n sourceText = abilityDetailMap[source].name;\r\n sourceFullHrid = \"abilityNames.\" + source;\r\n }\r\n break;\r\n }\r\n let hitpointsPerSecond = (amount / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * amount) / totalHitpointsGained).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [sourceText, hitpointsPerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showManapointsGained(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultManaRestored\");\r\n let newChildren = [];\r\n\r\n let secondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n if (!simResult.manapointsGained[playerToDisplay]) {\r\n resultDiv.replaceChildren(...newChildren);\r\n return;\r\n }\r\n\r\n let manapointsGained = Object.entries(simResult.manapointsGained[playerToDisplay]).sort((a, b) => b[1] - a[1]);\r\n\r\n let totalManapointsGained = manapointsGained.reduce((prev, cur) => prev + cur[1], 0);\r\n let totalManapointsPerSecond = (totalManapointsGained / secondsSimulated).toFixed(2);\r\n let totalRow = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [\"Total\", totalManapointsPerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [source, amount] of manapointsGained) {\r\n if (amount == 0) {\r\n continue;\r\n }\r\n\r\n let sourceText;\r\n let sourceFullHrid;\r\n switch (source) {\r\n case \"regen\":\r\n sourceText = \"Regen\";\r\n sourceFullHrid = \"combatStats.mpRegenPer10\";\r\n break;\r\n case \"manaLeech\":\r\n sourceText = \"Mana Leech\";\r\n sourceFullHrid = \"combatStats.manaLeech\";\r\n break;\r\n case \"ripple\":\r\n sourceText = \"Ripple\";\r\n sourceFullHrid = \"combatStats.ripple\";\r\n break;\r\n default:\r\n sourceText = itemDetailMap[source].name;\r\n sourceFullHrid = \"itemNames.\" + source;\r\n break;\r\n }\r\n let manapointsPerSecond = (amount / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * amount) / totalManapointsGained).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [sourceText, manapointsPerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n let ranOutOfManaText = simResult.playerRanOutOfMana[playerToDisplay] ? \"Yes\" : \"No\";\r\n let ranOutOfManaRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Ran out of mana\", ranOutOfManaText]);\r\n ranOutOfManaRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfMana\");\r\n ranOutOfManaRow.lastElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.\" + ranOutOfManaText);\r\n newChildren.push(ranOutOfManaRow);\r\n\r\n if (simResult.playerRanOutOfMana[playerToDisplay]) {\r\n let ranOutOfManaStat = simResult.playerRanOutOfManaTime[playerToDisplay]; // {isOutOfMana: false, startTimeForOutOfMana:0, totalTimeForOutOfMana:0};\r\n let totalTimeForOut = ranOutOfManaStat.totalTimeForOutOfMana + (ranOutOfManaStat.isOutOfMana ? (simResult.simulatedTime - ranOutOfManaStat.startTimeForOutOfMana) : 0);\r\n\r\n let ranOutOfManaStatRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [\r\n \"Run Out Ratio\",\r\n (totalTimeForOut / simResult.simulatedTime * 100).toFixed(2) + \"%\"\r\n ]\r\n );\r\n ranOutOfManaStatRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfManaRatio\");\r\n newChildren.push(ranOutOfManaStatRow);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showDamageDone(simResult, playerToDisplay) {\r\n let totalDamageDone = {};\r\n let enemyIndex = 1;\r\n\r\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n for (let i = 1; i < 64; i++) {\r\n let accordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + i);\r\n hideElement(accordion);\r\n }\r\n\r\n let bossTimeHeadingDiv = document.getElementById(\"simulationBossTimeHeading\");\r\n bossTimeHeadingDiv.classList.add(\"d-none\");\r\n let bossTimeDiv = document.getElementById(\"simulationBossTime\");\r\n bossTimeDiv.classList.add(\"d-none\");\r\n\r\n if (!simResult.attacks[playerToDisplay]) {\r\n return;\r\n }\r\n\r\n for (const [target, abilities] of Object.entries(simResult.attacks[playerToDisplay])) {\r\n let targetDamageDone = {};\r\n\r\n const i = simResult.timeSpentAlive.findIndex(e => e.name === target);\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\r\n\r\n for (const [ability, abilityCasts] of Object.entries(abilities)) {\r\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\r\n let misses = abilityCasts[\"miss\"] ?? 0;\r\n let damage = Object.entries(abilityCasts)\r\n .filter((entry) => entry[0] != \"miss\")\r\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\r\n\r\n targetDamageDone[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n if (totalDamageDone[ability]) {\r\n totalDamageDone[ability].casts += casts;\r\n totalDamageDone[ability].misses += misses;\r\n totalDamageDone[ability].damage += damage;\r\n } else {\r\n totalDamageDone[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n }\r\n }\r\n\r\n let resultDiv = document.getElementById(\"simulationResultDamageDoneEnemy\" + enemyIndex);\r\n createDamageTable(resultDiv, targetDamageDone, aliveSecondsSimulated);\r\n\r\n let resultAccordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + enemyIndex);\r\n showElement(resultAccordion);\r\n\r\n let resultAccordionButton = document.getElementById(\r\n \"buttonSimulationResultDamageDoneAccordionEnemy\" + enemyIndex\r\n );\r\n let targetName = combatMonsterDetailMap[target].name;\r\n resultAccordionButton.innerHTML = \"Damage Done (\" + \"\" + targetName + \"\" + \")\";\r\n\r\n if (simResult.bossSpawns.includes(target)) {\r\n let hoursSpentOnBoss = (aliveSecondsSimulated / 60 / 60).toFixed(2);\r\n let percentSpentOnBoss = (aliveSecondsSimulated / totalSecondsSimulated * 100).toFixed(2);\r\n\r\n let bossRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [targetName, hoursSpentOnBoss + \"h(\" + percentSpentOnBoss + \"%)\"]);\r\n bossRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + target);\r\n bossTimeDiv.replaceChildren(bossRow);\r\n\r\n bossTimeHeadingDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.classList.remove(\"d-none\");\r\n }\r\n\r\n enemyIndex++;\r\n }\r\n\r\n if (simResult.isDungeon) {\r\n let newChildren = [];\r\n for (const waveName of simResult.bossSpawns) {\r\n // waveName is something like \"#15,/monsters/jackalope,/monsters/butterjerry\"\r\n let waveNumber = waveName.split(\",\")[0];\r\n const idx = simResult.timeSpentAlive.findIndex(e => e.name === waveNumber);\r\n if (idx == -1 || simResult.timeSpentAlive[idx].count == 0) {\r\n continue;\r\n }\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[idx].timeSpentAlive / ONE_SECOND / simResult.timeSpentAlive[idx].count;\r\n let bossRow = createRow([\"col-md-6\", \"col-md-2\", \"col-md-4 text-end\"], [waveNumber, simResult.timeSpentAlive[idx].count, aliveSecondsSimulated.toFixed(1) + \"s\"]);\r\n newChildren.push(bossRow);\r\n }\r\n if (newChildren.length > 0) {\r\n bossTimeHeadingDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.replaceChildren(...newChildren);\r\n }\r\n }\r\n\r\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageDone\");\r\n createDamageTable(totalResultDiv, totalDamageDone, totalSecondsSimulated);\r\n}\r\n\r\nfunction showDamageTaken(simResult, playerToDisplay) {\r\n let totalDamageTaken = {};\r\n let enemyIndex = 1;\r\n\r\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n for (let i = 1; i < 64; i++) {\r\n let accordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + i);\r\n hideElement(accordion);\r\n }\r\n\r\n for (const [source, targets] of Object.entries(simResult.attacks)) {\r\n const validSources = [\"player1\", \"player2\", \"player3\", \"player4\", \"player5\"];\r\n if (validSources.includes(source)) {\r\n continue;\r\n }\r\n const i = simResult.timeSpentAlive.findIndex(e => e.name === source);\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\r\n let sourceDamageTaken = {};\r\n if (targets[playerToDisplay] && Object.keys(targets[playerToDisplay]).length > 0) {\r\n for (const [ability, abilityCasts] of Object.entries(targets[playerToDisplay])) {\r\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\r\n let misses = abilityCasts[\"miss\"] ?? 0;\r\n let damage = Object.entries(abilityCasts)\r\n .filter((entry) => entry[0] != \"miss\")\r\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\r\n\r\n sourceDamageTaken[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n if (totalDamageTaken[ability]) {\r\n totalDamageTaken[ability].casts += casts;\r\n totalDamageTaken[ability].misses += misses;\r\n totalDamageTaken[ability].damage += damage;\r\n } else {\r\n totalDamageTaken[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n }\r\n }\r\n }\r\n\r\n let resultDiv = document.getElementById(\"simulationResultDamageTakenEnemy\" + enemyIndex);\r\n createDamageTable(resultDiv, sourceDamageTaken, aliveSecondsSimulated);\r\n\r\n let resultAccordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + enemyIndex);\r\n showElement(resultAccordion);\r\n\r\n let resultAccordionButton = document.getElementById(\r\n \"buttonSimulationResultDamageTakenAccordionEnemy\" + enemyIndex\r\n );\r\n let sourceName = combatMonsterDetailMap[source].name;\r\n resultAccordionButton.innerHTML = \"Damage Taken (\" + \"\" + sourceName + \"\" + \")\";\r\n\r\n enemyIndex++;\r\n }\r\n\r\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageTaken\");\r\n createDamageTable(totalResultDiv, totalDamageTaken, totalSecondsSimulated);\r\n}\r\n\r\nfunction createDamageTable(resultDiv, damageDone, secondsSimulated) {\r\n let newChildren = [];\r\n\r\n let sortedDamageDone = Object.entries(damageDone).sort((a, b) => b[1].damage - a[1].damage);\r\n\r\n let totalCasts = sortedDamageDone.reduce((prev, cur) => prev + cur[1].casts, 0);\r\n let totalMisses = sortedDamageDone.reduce((prev, cur) => prev + cur[1].misses, 0);\r\n let totalDamage = sortedDamageDone.reduce((prev, cur) => prev + cur[1].damage, 0);\r\n let totalHitChance = ((100 * (totalCasts - totalMisses)) / totalCasts).toFixed(1);\r\n let totalDamagePerSecond = (totalDamage / secondsSimulated).toFixed(2);\r\n\r\n let totalRow = createRow(\r\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\r\n [\"Total\", totalHitChance + \"%\", totalDamagePerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [ability, damageInfo] of sortedDamageDone) {\r\n let abilityText;\r\n let abilityFullHrid;\r\n switch (ability) {\r\n case \"autoAttack\":\r\n abilityText = \"Auto Attack\";\r\n abilityFullHrid = \"combatUnit.autoAttack\";\r\n break;\r\n case \"parry\":\r\n abilityText = \"Parry Attack\";\r\n abilityFullHrid = \"common:simulationResults.parryAttack\";\r\n break;\r\n case \"damageOverTime\":\r\n abilityText = \"Damage Over Time\";\r\n abilityFullHrid = \"common:simulationResults.damageOverTime\";\r\n break;\r\n case \"physicalThorns\":\r\n abilityText = \"Physical Thorns\";\r\n abilityFullHrid = \"combatStats.physicalThorns\";\r\n break;\r\n case \"elementalThorns\":\r\n abilityText = \"Elemental Thorns\";\r\n abilityFullHrid = \"combatStats.elementalThorns\";\r\n break;\r\n case \"retaliation\":\r\n abilityText = \"Retaliation\";\r\n abilityFullHrid = \"combatStats.retaliation\";\r\n break;\r\n case 'blaze':\r\n abilityText = \"Blaze\";\r\n abilityFullHrid = \"combatStats.blaze\";\r\n break;\r\n default:\r\n abilityText = abilityDetailMap[ability].name;\r\n abilityFullHrid = \"abilityNames.\" + ability;\r\n break;\r\n }\r\n\r\n let hitChance = ((100 * (damageInfo.casts - damageInfo.misses)) / damageInfo.casts).toFixed(1);\r\n let damagePerSecond = (damageInfo.damage / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * damageInfo.damage) / totalDamage).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\r\n [abilityText, hitChance + \"%\", damagePerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", abilityFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction createRow(columnClassNames, columnValues) {\r\n let row = createElement(\"div\", \"row\");\r\n\r\n for (let i = 0; i < columnClassNames.length; i++) {\r\n let column = createElement(\"div\", columnClassNames[i], columnValues[i]);\r\n row.appendChild(column);\r\n }\r\n\r\n return row;\r\n}\r\n\r\nfunction createElement(tagName, className, innerHTML = \"\", id = \"\") {\r\n let element = document.createElement(tagName);\r\n element.className = className;\r\n element.innerHTML = innerHTML;\r\n if (id) element.id = id;\r\n return element;\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Simulation Controls\r\n\r\ndocument.addEventListener('DOMContentLoaded', function () {\r\n const simDungeonToggle = document.getElementById('simDungeonToggle');\r\n const playerContainer = document.getElementById('playerCheckBox');\r\n\r\n function addPlayers() {\r\n const player4 = document.createElement('div');\r\n player4.classList.add('form-check');\r\n player4.innerHTML = `\r\n \r\n \r\n `;\r\n\r\n const player5 = document.createElement('div');\r\n player5.classList.add('form-check');\r\n player5.innerHTML = `\r\n \r\n \r\n `;\r\n\r\n playerContainer.appendChild(player4);\r\n playerContainer.appendChild(player5);\r\n }\r\n\r\n function removePlayers() {\r\n const player4 = document.getElementById('player4');\r\n const player5 = document.getElementById('player5');\r\n if (player4) player4.parentElement.remove();\r\n if (player5) player5.parentElement.remove();\r\n }\r\n\r\n function updatePlayerNames() {\r\n const tabLinks = document.querySelectorAll('#playerTab .nav-link');\r\n tabLinks.forEach((tabLink, index) => {\r\n const label = document.querySelector(`label[for=\"player${index + 1}\"]`);\r\n if (label) {\r\n label.textContent = tabLink.textContent.trim();\r\n }\r\n });\r\n }\r\n\r\n function updatePlayersCheckbox(isCheck) {\r\n const boxes = playerContainer.querySelectorAll('.player-checkbox');\r\n boxes.forEach((checkBox) => { checkBox.checked = isCheck });\r\n }\r\n\r\n function updateDifficultySelect(isCheck) {\r\n const difficultySelect = document.getElementById('selectDifficulty');\r\n // disable last four option\r\n if (isCheck && Number(difficultySelect.value) >= 3) {\r\n difficultySelect.value = 0;\r\n }\r\n for (let i = 3; i < difficultySelect.options.length; i++) {\r\n difficultySelect.options[i].disabled = isCheck;\r\n }\r\n }\r\n\r\n simDungeonToggle.addEventListener('change', function () {\r\n if (simDungeonToggle.checked) {\r\n addPlayers();\r\n updatePlayersCheckbox(true);\r\n updateDifficultySelect(true);\r\n } else {\r\n removePlayers();\r\n updatePlayersCheckbox(false);\r\n updateDifficultySelect(false);\r\n }\r\n updatePlayerNames();\r\n });\r\n\r\n document.getElementById('buttonSimulationSetup').addEventListener('click', function () {\r\n updatePlayerNames();\r\n });\r\n});\r\n\r\nfunction onTabChange(event) {\r\n const nextPlayerTabId = event.target.getAttribute('href').substring(7);\r\n savePreviousPlayer(currentPlayerTabId);\r\n updateNextPlayer(nextPlayerTabId);\r\n currentPlayerTabId = nextPlayerTabId;\r\n updateState();\r\n updateUI();\r\n if (Object.keys(currentSimResults).length !== 0) {\r\n showSimulationResult(currentSimResults);\r\n }\r\n\r\n updateContent();\r\n}\r\n\r\ndocument.querySelectorAll('#playerTab .nav-link').forEach(tab => {\r\n tab.addEventListener('shown.bs.tab', onTabChange);\r\n});\r\n\r\nfunction initSimulationControls() {\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n simulationTimeInput.value = 24;\r\n\r\n buttonStartSimulation.addEventListener(\"click\", (event) => {\r\n let invalidElements = document.querySelectorAll(\":invalid\");\r\n if (invalidElements.length > 0) {\r\n invalidElements.forEach((element) => element.reportValidity());\r\n return;\r\n }\r\n savePreviousPlayer(currentPlayerTabId);\r\n\r\n const simDungeonToggle = document.getElementById(\"simDungeonToggle\");\r\n const checkboxes = document.querySelectorAll('.player-checkbox');\r\n selectedPlayers = [];\r\n checkboxes.forEach(checkbox => {\r\n if (checkbox.checked) {\r\n const playerNumber = parseInt(checkbox.id.replace('player', ''));\r\n selectedPlayers.push(playerNumber);\r\n }\r\n });\r\n\r\n if (selectedPlayers.length === 0) {\r\n alert(\"You need to select at least one player to sim.\");\r\n return;\r\n }\r\n // buttonStartSimulation.disabled = true;\r\n buttonStopSimulation.style.display = 'block';\r\n startSimulation(selectedPlayers);\r\n });\r\n\r\n buttonStopSimulation.style.display = 'none';\r\n buttonStopSimulation.addEventListener(\"click\", (event) => {\r\n progressbar.style.width = \"0%\";\r\n progressbar.innerHTML = \"0%\";\r\n if (worker) {\r\n worker.terminate();\r\n }\r\n worker = new Worker(new URL(\"worker.js\", import.meta.url));\r\n\r\n if (multiWorker) {\r\n multiWorker.terminate();\r\n }\r\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n\r\n for (let worker of workerPool) {\r\n worker.worker.terminate();\r\n }\r\n\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n });\r\n}\r\n\r\nfunction startSimulation(selectedPlayers) {\r\n let playersToSim = [];\r\n for (let j = 1; j < 6; j++) {\r\n if (selectedPlayers.includes(j)) {\r\n updateNextPlayer(j);\r\n updateState();\r\n updateUI();\r\n player.hrid = \"player\" + j.toString();\r\n for (let i = 0; i < 3; i++) {\r\n if (food[i] && i < player.combatDetails.combatStats.foodSlots) {\r\n let consumable = new Consumable(food[i], triggerMap[food[i]]);\r\n player.food[i] = consumable;\r\n } else {\r\n player.food[i] = null;\r\n }\r\n\r\n if (drinks[i] && i < player.combatDetails.combatStats.drinkSlots) {\r\n let consumable = new Consumable(drinks[i], triggerMap[drinks[i]]);\r\n player.drinks[i] = consumable;\r\n } else {\r\n player.drinks[i] = null;\r\n }\r\n }\r\n\r\n for (let i = 0; i < 5; i++) {\r\n if (abilities[i] && player.intelligenceLevel >= abilitySlotsLevelRequirementList[i + 1]) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let ability = new Ability(abilities[i], Number(abilityLevelInput.value), triggerMap[abilities[i]]);\r\n player.abilities[i] = ability;\r\n } else {\r\n player.abilities[i] = null;\r\n }\r\n }\r\n\r\n playersToSim.push(structuredClone(player));\r\n }\r\n }\r\n updateNextPlayer(currentPlayerTabId);\r\n updateState();\r\n updateUI();\r\n\r\n let maxPlayerCombatLevel = 1;\r\n for (let player of playersToSim) {\r\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\r\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\r\n }\r\n\r\n for (let player of playersToSim) {\r\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\r\n const maxDebuffOnLevelGap = 0.9;\r\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\r\n\r\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\r\n\r\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\r\n }\r\n else {\r\n player.debuffOnLevelGap = 0;\r\n }\r\n }\r\n\r\n let extra = {};\r\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\r\n extra.comExp = 0;\r\n if (document.getElementById(\"comExpToggle\").checked) {\r\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\r\n }\r\n extra.comDrop = 0;\r\n if (document.getElementById(\"comDropToggle\").checked) {\r\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\r\n }\r\n\r\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\r\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\r\n let simDungeonToggle = document.getElementById(\"simDungeonToggle\");\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let dungeonSelect = document.getElementById(\"selectDungeon\");\r\n let difficultySelect = document.getElementById(\"selectDifficulty\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let simulationTimeLimit = Number(simulationTimeInput.value) * ONE_HOUR;\r\n buttonStopSimulation.style.display = 'block';\r\n if (!simAllZonesToggle.checked && !simAllSoloToggle.checked) {\r\n let zoneHrid = zoneSelect.value;\r\n let difficultyTier = Number(difficultySelect.value);\r\n if (simDungeonToggle.checked) {\r\n zoneHrid = dungeonSelect.value;\r\n }\r\n let workerMessage = {\r\n type: \"start_simulation\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n simStartTime = Date.now();\r\n if (!worker) {\r\n worker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n }\r\n worker.onmessage = onWorkerMessage;\r\n worker.postMessage(workerMessage);\r\n } else {\r\n let targetHrids = {};\r\n\r\n if (simAllZonesToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1 &&\r\n document.getElementById(a.hrid)?.checked\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n if (simAllSoloToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount === 1 &&\r\n document.getElementById(a.hrid)?.checked\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n let simHrids = Object.values(targetHrids)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .map(action => {\r\n let result = [];\r\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\r\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\r\n }\r\n return result;\r\n })\r\n .flat();\r\n\r\n let workerMessage = {\r\n type: \"start_simulation_all_zones\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zones: simHrids,\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra: extra\r\n };\r\n simStartTime = Date.now();\r\n if (!multiWorker) {\r\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n }\r\n multiWorker.onmessage = onMultiWorkerMessage;\r\n multiWorker.postMessage(workerMessage);\r\n }\r\n}\r\n\r\nfunction parsePlayerJson(playerJson, hrid) {\r\n let playerData = {\r\n hrid: hrid,\r\n food: [],\r\n drinks: [],\r\n abilities: [],\r\n ...playerJson.player,\r\n houseRooms: playerJson.houseRooms,\r\n };\r\n playerData.equipment = {};\r\n const triggerMap = playerJson.triggerMap;\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"main_hand\", \"two_hand\", \"charm\"].forEach((type) => {\r\n let currentEquipment = playerJson.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment){\r\n playerData.equipment[`/equipment_types/${type}`] = new Equipment(currentEquipment.itemHrid, currentEquipment.enhancementLevel);\r\n }\r\n });\r\n\r\n for (const foodHrid of playerJson.food[\"/action_types/combat\"]) {\r\n if (foodHrid.itemHrid === \"\") continue;\r\n const food = new Consumable(foodHrid.itemHrid, triggerMap[foodHrid.itemHrid]);\r\n playerData.food.push(food);\r\n }\r\n for (const drinkHrid of playerJson.drinks[\"/action_types/combat\"]) {\r\n if (drinkHrid.itemHrid === \"\") continue;\r\n const drink = new Consumable(drinkHrid.itemHrid, triggerMap[drinkHrid.itemHrid]);\r\n playerData.drinks.push(drink);\r\n }\r\n for (const ability of playerJson.abilities) {\r\n if (ability.abilityHrid === \"\") continue;\r\n const abilityLevel = Number(ability.level);\r\n const abilityHrid = ability.abilityHrid;\r\n if (abilityLevel > 0) {\r\n const abilityObj = new Ability(abilityHrid, abilityLevel, triggerMap[abilityHrid]);\r\n playerData.abilities.push(abilityObj);\r\n }\r\n }\r\n const player = Player.createFromDTO(playerData)\r\n player.updateCombatDetails();\r\n player.houseRooms = playerJson.houseRooms;\r\n player.achievements = playerJson.achievements ?? {};\r\n return player;\r\n}\r\n// read JSON file to simulate\r\ndocument.getElementById(\"buttonUploadJSONSimulate\").addEventListener(\"click\", (event) => {\r\n let extra = {};\r\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\r\n extra.comExp = 0;\r\n if (document.getElementById(\"comExpToggle\").checked) {\r\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\r\n }\r\n extra.comDrop = 0;\r\n if (document.getElementById(\"comDropToggle\").checked) {\r\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\r\n }\r\n\r\n let fileInput = document.getElementById(\"inputUploadJSONSimulation\");\r\n let file = fileInput.files[0];\r\n if (!file) {\r\n alert(\"Please select a file to upload.\");\r\n return;\r\n }\r\n\r\n let reader = new FileReader();\r\n reader.onload = function (event) {\r\n let fileContent = event.target.result;\r\n const jsonDataList = JSON.parse(fileContent);\r\n try {\r\n const simDataList = [];\r\n for (const key in jsonDataList) {\r\n if (jsonDataList[key].cases) {\r\n const cases = getProductCases(jsonDataList[key], jsonDataList[key].cases);\r\n simDataList.push(...cases);\r\n } else {\r\n simDataList.push(jsonDataList[key]);\r\n }\r\n }\r\n for (const key in simDataList) {\r\n const jsonData = simDataList[key];\r\n if (!jsonData || !jsonData.zone || !jsonData.players) {\r\n alert(\"Invalid JSON file format. Please ensure it contains a 'simulationResult' property.\");\r\n return;\r\n }\r\n const playersToSim = Object.values(jsonData.players).map(\r\n (player, index) => parsePlayerJson(player, `player${index + 1}`)\r\n );\r\n\r\n let maxPlayerCombatLevel = 1;\r\n for (let player of playersToSim) {\r\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\r\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\r\n }\r\n\r\n for (let player of playersToSim) {\r\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\r\n const maxDebuffOnLevelGap = 0.9;\r\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\r\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\r\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\r\n }\r\n else {\r\n player.debuffOnLevelGap = 0;\r\n }\r\n }\r\n\r\n const simulationTimeLimit = (jsonData.simulationTimeLimit || 24) * ONE_HOUR;\r\n const simName = jsonData.name || `Json ${key}`;\r\n const zoneHrid = jsonData.zone;\r\n if (zoneHrid === \"all\") {\r\n let targetHrids = {};\r\n\r\n if (simAllZonesToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n let simHrids = Object.values(targetHrids)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .map(action => {\r\n let result = [];\r\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\r\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\r\n }\r\n return result;\r\n })\r\n .flat();\r\n\r\n let workerMessage = {\r\n simulationName: simName,\r\n type: \"start_simulation_all_zones\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zones: simHrids,\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \r\n worker.onmessage = mainWorkerOnMessage;\r\n worker.postMessage(workerMessage);\r\n customAlert(\"Simulation task Created\", \"info\")\r\n workerPool.push({\r\n workerId: workerMessage.workerId,\r\n worker: worker,\r\n });\r\n } else {\r\n let difficultyTier = jsonData.difficultyTier || 0;\r\n let workerMessage = {\r\n simulationName: simName,\r\n type: \"start_simulation\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \r\n worker.onmessage = mainWorkerOnMessage;\r\n worker.postMessage(workerMessage);\r\n customAlert(\"Simulation task Created\", \"info\")\r\n workerPool.push({\r\n workerId: workerMessage.workerId,\r\n worker: worker,\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n // alert(\"Error parsing JSON file: \" + error.message);\r\n customAlert(\"Error parsing JSON file: \" + error.message, \"danger\");\r\n }\r\n }\r\n reader.readAsText(file);\r\n});\r\n\r\n\r\n// #endregion\r\n\r\n// #region WipeEvents\r\n\r\nfunction renderWipeEvents(simResult) {\r\n const selector = document.getElementById('wipeEventSelector');\r\n const logsContainer = document.getElementById('wipeLogsContainer');\r\n const waveBadge = document.getElementById('wipeWaveBadge');\r\n const timeInfo = document.getElementById('wipeTimeInfo');\r\n\r\n selector.innerHTML = '';\r\n logsContainer.innerHTML = '';\r\n\r\n if (!simResult.wipeEvents || simResult.wipeEvents.length === 0) {\r\n selector.innerHTML = ``;\r\n logsContainer.innerHTML = `
No Wipe Events Detected
`;\r\n waveBadge.textContent = '';\r\n timeInfo.textContent = '';\r\n return;\r\n }\r\n\r\n simResult.wipeEvents.forEach((event, index) => {\r\n const wave = event.wave || '?';\r\n // const time = (event.simulationTime / 1e9).toFixed(2);\r\n // const timestamp = new Date(event.timestamp).toLocaleTimeString();\r\n\r\n const option = document.createElement('option');\r\n option.value = index;\r\n option.textContent = `#${index + 1} - 波次: ${wave}`;\r\n selector.appendChild(option);\r\n });\r\n\r\n selector.value = 0;\r\n renderSelectedWipeEvent(0, simResult);\r\n\r\n selector.addEventListener('change', () => {\r\n renderSelectedWipeEvent(selector.value, simResult);\r\n });\r\n}\r\n\r\n// 渲染选中的团灭事件\r\nfunction renderSelectedWipeEvent(index, simResult) {\r\n const logsContainer = document.getElementById('wipeLogsContainer');\r\n const waveBadge = document.getElementById('wipeWaveBadge');\r\n const timeInfo = document.getElementById('wipeTimeInfo');\r\n\r\n logsContainer.innerHTML = '';\r\n\r\n if (index < 0 || index >= simResult.wipeEvents.length) {\r\n logsContainer.innerHTML = `
No Wipe Events
`;\r\n waveBadge.textContent = '';\r\n timeInfo.textContent = '';\r\n return;\r\n }\r\n\r\n const wipeEvent = simResult.wipeEvents[index];\r\n const wave = wipeEvent.wave || '?';\r\n const time = (wipeEvent.simulationTime / 1e9).toFixed(2);\r\n const timestamp = new Date(wipeEvent.timestamp).toLocaleString();\r\n\r\n waveBadge.textContent = `波次: ${wave}`;\r\n timeInfo.textContent = `模拟时间: ${time}s | 记录时间: ${timestamp}`;\r\n\r\n const logsByTime = groupLogsByTime(wipeEvent.logs);\r\n\r\n const baseTime = logsByTime.length > 0 ? logsByTime[0].time : 0;\r\n\r\n logsByTime.forEach(group => {\r\n const timeGroupElement = document.createElement('div');\r\n timeGroupElement.className = 'log-time-group';\r\n\r\n const relativeTime = (group.time - baseTime) / 1e9;\r\n\r\n // 时间标题\r\n const timeHeader = document.createElement('div');\r\n timeHeader.className = 'log-time-header';\r\n timeHeader.textContent = `[${relativeTime.toFixed(2)}s] [Wave#${group.wave}]`;\r\n timeGroupElement.appendChild(timeHeader);\r\n\r\n // 事件列表\r\n const eventsList = document.createElement('div');\r\n eventsList.className = 'log-events';\r\n\r\n const damagedPlayers = new Set();\r\n\r\n group.logs.forEach(log => {\r\n const eventElement = document.createElement('div');\r\n eventElement.className = 'log-event';\r\n\r\n damagedPlayers.add(log.target);\r\n\r\n const sourceSpan = document.createElement('span');\r\n sourceSpan.className = 'log-source';\r\n if (log.ability === \"damageOverTime\") {\r\n sourceSpan.textContent = log.target;\r\n } else if(log.source == 'UNKNOWN_SOURCE') {\r\n sourceSpan.textContent = 'UNKNOWN';\r\n } else {\r\n sourceSpan.setAttribute('data-i18n', `monsterNames.${log.source}`);\r\n sourceSpan.textContent = log.source;\r\n }\r\n\r\n const castSpan = document.createElement('span');\r\n castSpan.className = 'log-cast';\r\n castSpan.setAttribute('data-i18n', `common:cast`);\r\n castSpan.textContent = ' cast ';\r\n\r\n const abilitySpan = document.createElement('span');\r\n abilitySpan.className = 'log-ability';\r\n if (log.ability === \"autoAttack\") {\r\n abilitySpan.setAttribute('data-i18n', 'combatUnit.autoAttack');\r\n abilitySpan.textContent = 'Auto Attack';\r\n } else if (log.ability === \"physicalThorns\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.physicalThorns`);\r\n abilitySpan.textContent = 'Physical Thorns';\r\n } else if (log.ability === \"elementalThorns\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.elementalThorns`);\r\n abilitySpan.textContent = 'Elemental Thorns';\r\n } else if (log.ability === \"retaliation\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.retaliation`);\r\n abilitySpan.textContent = 'Retaliation';\r\n } else if (log.ability === \"damageOverTime\") {\r\n abilitySpan.setAttribute('data-i18n', `common:simulationResults.damageOverTime`);\r\n abilitySpan.textContent = 'Damage Over Time';\r\n } else {\r\n abilitySpan.setAttribute('data-i18n', `abilityNames.${log.ability}`);\r\n abilitySpan.textContent = log.ability;\r\n }\r\n\r\n const toSpan = document.createElement('span');\r\n toSpan.className = 'log-to';\r\n toSpan.setAttribute('data-i18n', `common:to`);\r\n toSpan.textContent = ' to ';\r\n\r\n const targetSpan = document.createElement('span');\r\n targetSpan.className = 'log-target';\r\n targetSpan.textContent = log.target;\r\n\r\n const dealDamageSpan = document.createElement('span');\r\n dealDamageSpan.className = 'log-deal-damage';\r\n dealDamageSpan.setAttribute('data-i18n', `common:dealDamage`);\r\n dealDamageSpan.textContent = ' deal damage ';\r\n\r\n const damageDoneSpan = document.createElement('span');\r\n damageDoneSpan.className = 'log-damage-done';\r\n damageDoneSpan.textContent = log.damage;\r\n if (log.isCrit) {\r\n damageDoneSpan.style.fontWeight = 'bold';\r\n damageDoneSpan.textContent += '!!!';\r\n }\r\n\r\n eventElement.appendChild(sourceSpan);\r\n eventElement.appendChild(castSpan);\r\n eventElement.appendChild(abilitySpan);\r\n eventElement.appendChild(toSpan);\r\n eventElement.appendChild(targetSpan);\r\n eventElement.appendChild(dealDamageSpan);\r\n eventElement.appendChild(damageDoneSpan);\r\n eventElement.appendChild(document.createTextNode(` , HP ${log.beforeHp} → ${log.afterHp}`));\r\n\r\n eventsList.appendChild(eventElement);\r\n });\r\n\r\n timeGroupElement.appendChild(eventsList);\r\n\r\n const lastLog = group.logs[group.logs.length - 1];\r\n const playersHpElement = document.createElement('div');\r\n\r\n const playerHpTitle = document.createElement('span');\r\n playerHpTitle.className = 'log-players-hp';\r\n playerHpTitle.setAttribute('data-i18n', `common:playersHp`);\r\n playerHpTitle.textContent = 'Players HP: ';\r\n playersHpElement.appendChild(playerHpTitle);\r\n\r\n lastLog.playersHp.forEach((player, idx) => {\r\n const playerElement = document.createElement('span');\r\n playerElement.className = 'log-player-hp';\r\n playerElement.textContent = `${player.hrid}: ${player.current}/${player.max}`;\r\n\r\n if (player.current <= 0) {\r\n playerElement.style.color = darkModeToggle.checked ? '#FF6347' : '#CC0000';\r\n } else if (damagedPlayers.has(player.hrid)) {\r\n playerElement.style.color = darkModeToggle.checked ? '#00BFFF' : '#007BFF';\r\n }\r\n\r\n if (idx > 0) {\r\n playersHpElement.appendChild(document.createTextNode(' | '));\r\n }\r\n playersHpElement.appendChild(playerElement);\r\n });\r\n const spacer = document.createElement('div');\r\n spacer.style.height = '15px';\r\n logsContainer.appendChild(spacer);\r\n timeGroupElement.appendChild(playersHpElement);\r\n logsContainer.appendChild(timeGroupElement);\r\n });\r\n\r\n // 更新汉化\r\n updateContent()\r\n}\r\n\r\n// 按时间分组日志\r\nfunction groupLogsByTime(logs) {\r\n const groups = [];\r\n let currentGroup = null;\r\n\r\n logs.forEach(log => {\r\n if (!currentGroup || currentGroup.time !== log.time) {\r\n currentGroup = {\r\n time: log.time,\r\n wave: log.wave,\r\n logs: [log]\r\n };\r\n groups.push(currentGroup);\r\n } else {\r\n currentGroup.logs.push(log);\r\n }\r\n });\r\n\r\n groups.forEach(group => {\r\n let hpMap = {};\r\n if (group.logs.length > 0) {\r\n group.logs[0].playersHp.forEach(p => {\r\n hpMap[p.hrid] = { current: p.current, max: p.max };\r\n });\r\n }\r\n group.logs.forEach(log => {\r\n if (hpMap[log.target]) {\r\n hpMap[log.target].current = log.afterHp;\r\n }\r\n });\r\n group.logs.forEach(log => {\r\n log.playersHp = Object.entries(hpMap).map(([hrid, val]) => ({\r\n hrid,\r\n current: val.current,\r\n max: val.max\r\n }));\r\n });\r\n });\r\n\r\n return groups;\r\n}\r\n\r\n// #endregion\r\n\r\n\r\n// #region Equipment Sets\r\n\r\nfunction initEquipmentSetsModal() {\r\n let equipmentSetsModal = document.getElementById(\"equipmentSetsModal\");\r\n equipmentSetsModal.addEventListener(\"show.bs.modal\", equipmentSetsModalShownHandler);\r\n\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n equipmentSetNameInput.addEventListener(\"input\", (event) => equipmentSetNameChangedHandler(event));\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.addEventListener(\"click\", createNewEquipmentSetHandler);\r\n}\r\n\r\nfunction equipmentSetsModalShownHandler() {\r\n resetNewEquipmentSetControls();\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction resetNewEquipmentSetControls() {\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n equipmentSetNameInput.value = \"\";\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.disabled = true;\r\n}\r\n\r\nfunction updateEquipmentSetList() {\r\n let newChildren = [];\r\n let equipmentSets = loadEquipmentSets();\r\n\r\n for (const equipmentSetName of Object.keys(equipmentSets)) {\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let nameCol = createElement(\"div\", \"col align-self-center\", equipmentSetName);\r\n row.appendChild(nameCol);\r\n\r\n let loadButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let loadButton = createElement(\"button\", \"btn btn-primary\", \"Load\");\r\n loadButton.setAttribute(\"data-i18n\", \"common:controls.load\");\r\n loadButton.setAttribute(\"type\", \"button\");\r\n loadButton.addEventListener(\"click\", (_) => loadEquipmentSetHandler(equipmentSetName));\r\n loadButtonCol.appendChild(loadButton);\r\n row.appendChild(loadButtonCol);\r\n\r\n let saveButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let saveButton = createElement(\"button\", \"btn btn-primary\", \"Save\");\r\n saveButton.setAttribute(\"data-i18n\", \"common:controls.save\");\r\n saveButton.setAttribute(\"type\", \"button\");\r\n saveButton.addEventListener(\"click\", (_) => updateEquipmentSetHandler(equipmentSetName));\r\n saveButtonCol.appendChild(saveButton);\r\n row.appendChild(saveButtonCol);\r\n\r\n let deleteButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let deleteButton = createElement(\"button\", \"btn btn-danger\", \"Delete\");\r\n deleteButton.setAttribute(\"data-i18n\", \"common:controls.delete\");\r\n deleteButton.setAttribute(\"type\", \"button\");\r\n deleteButton.addEventListener(\"click\", (_) => deleteEquipmentSetHandler(equipmentSetName));\r\n deleteButtonCol.appendChild(deleteButton);\r\n row.appendChild(deleteButtonCol);\r\n\r\n newChildren.push(row);\r\n }\r\n\r\n let equipmentSetList = document.getElementById(\"equipmentSetList\");\r\n equipmentSetList.replaceChildren(...newChildren);\r\n\r\n updateContent();\r\n}\r\n\r\nfunction equipmentSetNameChangedHandler(event) {\r\n let invalid = false;\r\n\r\n if (event.target.value.length == 0) {\r\n invalid = true;\r\n }\r\n\r\n let equipmentSets = loadEquipmentSets();\r\n if (equipmentSets[event.target.value]) {\r\n invalid = true;\r\n }\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.disabled = invalid;\r\n}\r\n\r\nfunction createNewEquipmentSetHandler() {\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n let equipmentSetName = equipmentSetNameInput.value;\r\n\r\n let equipmentSet = getEquipmentSetFromUI();\r\n let equipmentSets = loadEquipmentSets();\r\n equipmentSets[equipmentSetName] = equipmentSet;\r\n saveEquipmentSets(equipmentSets);\r\n\r\n resetNewEquipmentSetControls();\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction loadEquipmentSetHandler(name) {\r\n let equipmentSets = loadEquipmentSets();\r\n loadEquipmentSetIntoUI(equipmentSets[name]);\r\n}\r\n\r\nfunction updateEquipmentSetHandler(name) {\r\n let equipmentSet = getEquipmentSetFromUI();\r\n let equipmentSets = loadEquipmentSets();\r\n equipmentSets[name] = equipmentSet;\r\n saveEquipmentSets(equipmentSets);\r\n}\r\n\r\nfunction deleteEquipmentSetHandler(name) {\r\n let equipmentSets = loadEquipmentSets();\r\n delete equipmentSets[name];\r\n saveEquipmentSets(equipmentSets);\r\n\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction loadEquipmentSets() {\r\n return JSON.parse(localStorage.getItem(\"equipmentSets\")) ?? {};\r\n}\r\n\r\nfunction saveEquipmentSets(equipmentSets) {\r\n localStorage.setItem(\"equipmentSets\", JSON.stringify(equipmentSets));\r\n}\r\n\r\nfunction getEquipmentSetFromUI() {\r\n let equipmentSet = {\r\n levels: {},\r\n equipment: {},\r\n food: {},\r\n drinks: {},\r\n abilities: {},\r\n triggerMap: {},\r\n houseRooms: {},\r\n achievements: {},\r\n };\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n equipmentSet.levels[skill] = Number(levelInput.value);\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n\r\n equipmentSet.equipment[type] = {\r\n equipment: equipmentSelect.value,\r\n enhancementLevel: Number(enhancementLevelInput.value),\r\n };\r\n });\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n equipmentSet.food[i] = foodSelect.value;\r\n }\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n equipmentSet.drinks[i] = drinkSelect.value;\r\n }\r\n\r\n for (let i = 0; i < 5; i++) {\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n equipmentSet.abilities[i] = {\r\n ability: abilitySelect.value,\r\n level: Number(abilityLevelInput.value),\r\n };\r\n }\r\n\r\n equipmentSet.triggerMap = triggerMap;\r\n\r\n equipmentSet.houseRooms = player.houseRooms;\r\n equipmentSet.achievements = player.achievements;\r\n\r\n return equipmentSet;\r\n}\r\n\r\nfunction fixTriggerMap(triggerMap) {\r\n let delKeys = []\r\n for (const key of Object.keys(triggerMap)) {\r\n let err = false;\r\n if (null == triggerMap[key]) {\r\n triggerMap[key] = [];\r\n }\r\n for (const trigger of triggerMap[key]) {\r\n if (!combatTriggerConditionDetailMap[trigger.conditionHrid]) {\r\n err = true;\r\n break;\r\n }\r\n }\r\n if (err) {\r\n delKeys.push(key);\r\n }\r\n }\r\n for (const key of delKeys) {\r\n delete triggerMap[key];\r\n }\r\n}\r\n\r\nfunction loadEquipmentSetIntoUI(equipmentSet) {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !equipmentSet.levels[\"meleeLevel\"] && equipmentSet.levels[\"powerLevel\"]) {\r\n equipmentSet.levels[\"meleeLevel\"] = equipmentSet.levels[\"powerLevel\"];\r\n }\r\n levelInput.value = equipmentSet.levels[skill] ?? 1;\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n\r\n let currentEquipment = equipmentSet.equipment[type];\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.equipment;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n foodSelect.value = equipmentSet.food[i];\r\n }\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n drinkSelect.value = equipmentSet.drinks[i].replace(\"power\", \"melee\");\r\n }\r\n\r\n let hasSpecial = false;\r\n if (equipmentSet.abilities && Object.keys(equipmentSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n equipmentSet.abilities[i].ability == \"/abilities/aqua_aura\" ||\r\n equipmentSet.abilities[i].ability == \"/abilities/flame_aura\" ||\r\n equipmentSet.abilities[i].ability == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n equipmentSet.abilities[i].ability = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (equipmentSet.abilities[i].ability == \"/abilities/arcane_reflection\") {\r\n equipmentSet.abilities[i].ability = \"/abilities/retribution\";\r\n }\r\n\r\n abilitySelect.value = equipmentSet.abilities[i].ability;\r\n abilityLevelInput.value = equipmentSet.abilities[i].level;\r\n }\r\n\r\n triggerMap = equipmentSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n\r\n if (equipmentSet.houseRooms) {\r\n for (const room in equipmentSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (equipmentSet.houseRooms[room]) {\r\n field.value = equipmentSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = equipmentSet.houseRooms;\r\n } else {\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n\r\n if (equipmentSet.achievements) {\r\n for (const achievement in equipmentSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (equipmentSet.achievements[achievement]) {\r\n field.checked = true;\r\n } else {\r\n field.checked = false;\r\n }\r\n player.achievements[achievement] = field.checked;\r\n }\r\n } else {\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n refreshAchievementStatics();\r\n\r\n updateState();\r\n updateUI();\r\n\r\n updateContent();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Error Handling\r\n\r\nfunction initErrorHandling() {\r\n window.addEventListener(\"error\", (event) => {\r\n showErrorModal(event.message);\r\n });\r\n\r\n let copyErrorButton = document.getElementById(\"buttonCopyError\");\r\n copyErrorButton.addEventListener(\"click\", (event) => {\r\n let errorInput = document.getElementById(\"inputError\");\r\n navigator.clipboard.writeText(errorInput.value);\r\n });\r\n}\r\n\r\nfunction initImportExportModal() {\r\n let exportSetButton = document.getElementById(\"buttonExportSet\");\r\n exportSetButton.addEventListener(\"click\", (event) => {\r\n savePreviousPlayer(currentPlayerTabId);\r\n const activeTab = document.querySelector('#importTab .nav-link.active');\r\n if (activeTab.id === 'group-combat-tab') {\r\n doGroupExport();\r\n } else if (activeTab.id === 'solo-tab') {\r\n doSoloExport();\r\n }\r\n });\r\n\r\n let importSetButton = document.getElementById(\"buttonImportSet\");\r\n importSetButton.addEventListener(\"click\", (event) => {\r\n const activeTab = document.querySelector('#importTab .nav-link.active');\r\n if (activeTab.id === 'group-combat-tab') {\r\n doGroupImport();\r\n } else if (activeTab.id === 'solo-tab') {\r\n doSoloImport();\r\n }\r\n updateState();\r\n updateUI();\r\n resetImportInputs();\r\n });\r\n}\r\n\r\nfunction resetImportInputs() {\r\n document.getElementById('inputSetGroupCombatAll').value = '';\r\n document.getElementById('inputSetGroupCombatplayer1').value = '';\r\n document.getElementById('inputSetGroupCombatplayer2').value = '';\r\n document.getElementById('inputSetGroupCombatplayer3').value = '';\r\n document.getElementById('inputSetGroupCombatplayer4').value = '';\r\n document.getElementById('inputSetGroupCombatplayer5').value = '';\r\n document.getElementById('inputSetSolo').value = '';\r\n}\r\n\r\nfunction doGroupExport() {\r\n try {\r\n navigator.clipboard.writeText(JSON.stringify(playerDataMap)).then(() => alert(\"Current Group has been copied to clipboard.\"));\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction doSoloExport() {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let equipmentArray = [];\r\n for (const item in player.equipment) {\r\n if (player.equipment[item] != null) {\r\n equipmentArray.push({\r\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\r\n \"itemHrid\": player.equipment[item].hrid,\r\n \"enhancementLevel\": player.equipment[item].enhancementLevel\r\n });\r\n }\r\n }\r\n let playerArray = {\r\n \"attackLevel\": player.attackLevel,\r\n \"magicLevel\": player.magicLevel,\r\n \"meleeLevel\": player.meleeLevel,\r\n \"rangedLevel\": player.rangedLevel,\r\n \"defenseLevel\": player.defenseLevel,\r\n \"staminaLevel\": player.staminaLevel,\r\n \"intelligenceLevel\": player.intelligenceLevel,\r\n \"equipment\": equipmentArray\r\n };\r\n let abilitiesArray = [];\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let abilityName = document.getElementById(\"selectAbility_\" + i);\r\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\r\n }\r\n let drinksArray = [];\r\n for (let i = 0; i < drinks?.length; i++) {\r\n drinksArray.push({ \"itemHrid\": drinks[i] });\r\n }\r\n let foodArray = [];\r\n for (let i = 0; i < food?.length; i++) {\r\n foodArray.push({ \"itemHrid\": food[i] });\r\n }\r\n let state = {\r\n player: playerArray,\r\n food: { \"/action_types/combat\": foodArray },\r\n drinks: { \"/action_types/combat\": drinksArray },\r\n abilities: abilitiesArray,\r\n triggerMap: triggerMap,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n houseRooms: player.houseRooms,\r\n achievements: player.achievements\r\n };\r\n try {\r\n navigator.clipboard.writeText(JSON.stringify(state)).then(() => alert(\"Current set has been copied to clipboard.\"));\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction setPlayerData(playerId, inputElementId) {\r\n const inputElement = document.getElementById(inputElementId);\r\n const value = inputElement ? inputElement.value.trim() : \"\";\r\n\r\n // Only set the value in the map if it's not null, undefined, or empty\r\n if (value) {\r\n playerDataMap[playerId] = value;\r\n return true;\r\n }\r\n return false;\r\n}\r\n\r\nfunction doGroupImport() {\r\n let needUpdateCurrentTab = false;\r\n const value = document.getElementById(\"inputSetGroupCombatAll\")?.value || \"\";\r\n if (!value.trim()) {\r\n for (let i of ['1', '2', '3', '4', '5']) {\r\n if (setPlayerData(i, \"inputSetGroupCombatplayer\" + i) && currentPlayerTabId == i) {\r\n needUpdateCurrentTab = true;\r\n }\r\n }\r\n } else {\r\n playerDataMap = JSON.parse(value);\r\n needUpdateCurrentTab = true;\r\n }\r\n\r\n if (needUpdateCurrentTab) {\r\n updateNextPlayer(currentPlayerTabId);\r\n }\r\n}\r\n\r\nfunction doSoloImport() {\r\n let importSet = document.getElementById(\"inputSetSolo\").value;\r\n importSet = JSON.parse(importSet);\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\r\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\r\n }\r\n levelInput.value = importSet.player[skill + \"Level\"];\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.itemHrid;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\r\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\r\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\r\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\r\n if (mainhandWeapon !== undefined) {\r\n weaponSelect.value = mainhandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\r\n } else if (twohandWeapon !== undefined) {\r\n weaponSelect.value = twohandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\r\n } else {\r\n weaponSelect.value = \"\";\r\n weaponEnhancementLevelInput.value = 0;\r\n }\r\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\r\n importSet.food = importSet.food[\"/action_types/combat\"];\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n if (importSet.drinks[i] != null) {\r\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\r\n } else {\r\n drinkSelect.value = \"\";\r\n }\r\n if (importSet.food[i] != null) {\r\n foodSelect.value = importSet.food[i].itemHrid;\r\n } else {\r\n foodSelect.value = \"\";\r\n }\r\n }\r\n\r\n let hasSpecial = false;\r\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\r\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\r\n }\r\n\r\n if (importSet.abilities[i] != null) {\r\n abilitySelect.value = importSet.abilities[i].abilityHrid;\r\n abilityLevelInput.value = String(importSet.abilities[i].level);\r\n } else {\r\n abilitySelect.value = \"\";\r\n abilityLevelInput.value = \"1\";\r\n }\r\n }\r\n\r\n if (importSet.triggerMap) {\r\n triggerMap = importSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n }\r\n\r\n if (importSet.houseRooms) {\r\n for (const room in importSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (importSet.houseRooms[room]) {\r\n field.value = importSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = importSet.houseRooms;\r\n } else {\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n\r\n if (importSet.achievements) {\r\n for (const achievement in importSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (importSet.achievements[achievement]) {\r\n field.checked = true;\r\n } else {\r\n field.checked = false;\r\n }\r\n player.achievements[achievement] = field.checked;\r\n }\r\n } else {\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n refreshAchievementStatics();\r\n\r\n if (\"zone\" in importSet) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n zoneSelect.value = importSet[\"zone\"];\r\n }\r\n\r\n if (\"simulationTime\" in importSet) {\r\n let simulationDuration = document.getElementById(\"inputSimulationTime\");\r\n simulationDuration.value = importSet[\"simulationTime\"];\r\n }\r\n}\r\n\r\nfunction savePreviousPlayer(playerId) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let equipmentArray = [];\r\n for (const item in player.equipment) {\r\n if (player.equipment[item] != null) {\r\n equipmentArray.push({\r\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\r\n \"itemHrid\": player.equipment[item].hrid,\r\n \"enhancementLevel\": player.equipment[item].enhancementLevel\r\n });\r\n }\r\n }\r\n let playerArray = {\r\n \"attackLevel\": player.attackLevel,\r\n \"magicLevel\": player.magicLevel,\r\n \"meleeLevel\": player.meleeLevel,\r\n \"rangedLevel\": player.rangedLevel,\r\n \"defenseLevel\": player.defenseLevel,\r\n \"staminaLevel\": player.staminaLevel,\r\n \"intelligenceLevel\": player.intelligenceLevel,\r\n \"equipment\": equipmentArray\r\n };\r\n let abilitiesArray = [];\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let abilityName = document.getElementById(\"selectAbility_\" + i);\r\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\r\n }\r\n let drinksArray = [];\r\n for (let i = 0; i < drinks?.length; i++) {\r\n drinksArray.push({ \"itemHrid\": drinks[i] });\r\n }\r\n let foodArray = [];\r\n for (let i = 0; i < food?.length; i++) {\r\n foodArray.push({ \"itemHrid\": food[i] });\r\n }\r\n let state = {\r\n player: playerArray,\r\n food: { \"/action_types/combat\": foodArray },\r\n drinks: { \"/action_types/combat\": drinksArray },\r\n abilities: abilitiesArray,\r\n triggerMap: triggerMap,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n houseRooms: player.houseRooms,\r\n achievements: player.achievements\r\n };\r\n try {\r\n playerDataMap[playerId] = JSON.stringify(state);\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction updateNextPlayer(currentPlayerNumber) {\r\n let playerImportData = playerDataMap[currentPlayerNumber];\r\n let importSet = JSON.parse(playerImportData);\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\r\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\r\n }\r\n levelInput.value = importSet.player[skill + \"Level\"];\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.itemHrid;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\r\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\r\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\r\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\r\n if (mainhandWeapon !== undefined) {\r\n weaponSelect.value = mainhandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\r\n } else if (twohandWeapon !== undefined) {\r\n weaponSelect.value = twohandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\r\n } else {\r\n weaponSelect.value = \"\";\r\n weaponEnhancementLevelInput.value = 0;\r\n }\r\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\r\n importSet.food = importSet.food[\"/action_types/combat\"];\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n if (importSet.drinks[i] != null) {\r\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\r\n } else {\r\n drinkSelect.value = \"\";\r\n }\r\n if (importSet.food[i] != null) {\r\n foodSelect.value = importSet.food[i].itemHrid;\r\n } else {\r\n foodSelect.value = \"\";\r\n }\r\n }\r\n\r\n let hasSpecial = false;\r\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\r\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\r\n }\r\n\r\n if (importSet.abilities[i] != null) {\r\n abilitySelect.value = importSet.abilities[i].abilityHrid;\r\n abilityLevelInput.value = String(importSet.abilities[i].level);\r\n } else {\r\n abilitySelect.value = \"\";\r\n abilityLevelInput.value = \"1\";\r\n }\r\n }\r\n\r\n if (importSet.triggerMap) {\r\n triggerMap = importSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n }\r\n\r\n { // reset all houseRooms\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n if (importSet.houseRooms) {\r\n for (const room in importSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (importSet.houseRooms[room]) {\r\n field.value = importSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = importSet.houseRooms;\r\n }\r\n\r\n { // reset all achievements\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n if (importSet.achievements) {\r\n for (const achievement in importSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (importSet.achievements[achievement]) {\r\n field.checked = true;\r\n player.achievements[achievement] = true;\r\n } else {\r\n field.checked = false;\r\n player.achievements[achievement] = false;\r\n }\r\n }\r\n }\r\n refreshAchievementStatics();\r\n}\r\n\r\nfunction showErrorModal(error) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n\r\n let state = {\r\n error: error,\r\n player: player,\r\n food: food,\r\n drinks: drinks,\r\n abilities: abilities,\r\n triggerMap: triggerMap,\r\n modalTriggers: modalTriggers,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n };\r\n\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n state[\"abilityLevel\" + i] = abilityLevelInput.value;\r\n }\r\n\r\n let errorInput = document.getElementById(\"inputError\");\r\n errorInput.value = JSON.stringify(state);\r\n\r\n let errorModal = new bootstrap.Modal(document.getElementById(\"errorModal\"));\r\n errorModal.show();\r\n}\r\n\r\nwindow.prices;\r\n\r\nasync function fetchPrices() {\r\n let response = null;\r\n try {\r\n response = await fetch('https://www.milkywayidle.com/game_data/marketplace.json'\r\n , {\r\n mode: 'cors'\r\n }\r\n );\r\n if (!response.ok) {\r\n console.log('Error fetching prices');\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n\r\n if (response == null) {\r\n try {\r\n response = await fetch('https://www.milkywayidlecn.com/game_data/marketplace.json'\r\n , {\r\n mode: 'cors'\r\n }\r\n );\r\n if (!response.ok) {\r\n console.log('Error fetching prices');\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n }\r\n\r\n if (!response || !response.ok) {\r\n return;\r\n }\r\n\r\n try {\r\n\r\n let btn = document.querySelector('#buttonGetPrices');\r\n btn.style.backgroundColor = 'green';\r\n\r\n const pricesJson = await response.json();\r\n\r\n const priceTmp = pricesJson['marketData'];\r\n window.prices = {};\r\n for (const item in itemDetailMap) {\r\n const hrid = itemDetailMap[item].hrid;\r\n if (hrid in priceTmp) {\r\n window.prices[hrid] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (priceTmp[hrid]['0']) {\r\n window.prices[hrid].ask = priceTmp[hrid]['0'].a;\r\n window.prices[hrid].bid = priceTmp[hrid]['0'].b;\r\n }\r\n }\r\n } \r\n\r\n window.prices[\"/items/coin\"] = { \"ask\": 1, \"bid\": 1, \"vendor\": 1 };\r\n\r\n window.prices[\"/items/small_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n window.prices[\"/items/medium_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n window.prices[\"/items/large_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n}\r\n\r\ndocument.getElementById(\"buttonGetPrices\").onclick = async () => {\r\n await fetchPrices();\r\n};\r\n\r\ndocument.addEventListener(\"input\", (e) => {\r\n let element = e.target;\r\n if (element.tagName == \"TD\" && element.parentNode.parentNode.parentNode.classList.value.includes('profit-table')) {\r\n let tableId = element.parentNode.parentNode.parentNode.id;\r\n let row = element.parentNode.querySelectorAll('td');\r\n let item = row[0].getAttribute('data-i18n').split('.')[1];\r\n let newPrice = element.innerText;\r\n\r\n let revenueSetting = document.getElementById('selectPrices_drops').value;\r\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\r\n\r\n let expensesDifference = 0;\r\n let revenueDifference = 0;\r\n let noRngRevenueDifference = 0;\r\n\r\n if (tableId == 'expensesTable') {\r\n expensesDifference = updateTable('expensesTable', item, newPrice);\r\n if (revenueSetting == expensesSetting) {\r\n revenueDifference = updateTable('revenueTable', item, newPrice);\r\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\r\n }\r\n if (window.prices) {\r\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (expensesSetting == 'bid') {\r\n window.prices[item]['bid'] = newPrice;\r\n } else {\r\n window.prices[item]['ask'] = newPrice;\r\n }\r\n }\r\n } else {\r\n revenueDifference = updateTable('revenueTable', item, newPrice);\r\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\r\n if (revenueSetting == expensesSetting) {\r\n expensesDifference = updateTable('expensesTable', item, newPrice);\r\n }\r\n if (window.prices) {\r\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (revenueSetting == 'bid') {\r\n window.prices[item]['bid'] = newPrice;\r\n } else {\r\n window.prices[item]['ask'] = newPrice;\r\n }\r\n }\r\n }\r\n\r\n window.expenses += expensesDifference;\r\n document.getElementById('expensesSpan').innerText = window.expenses.toLocaleString();\r\n window.revenue += revenueDifference;\r\n document.getElementById('revenueSpan').innerText = window.revenue.toLocaleString();\r\n window.noRngRevenue += noRngRevenueDifference;\r\n document.getElementById('noRngRevenueSpan').innerText = window.noRngRevenue.toLocaleString();\r\n\r\n window.profit = window.revenue - window.expenses;\r\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\r\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\r\n window.noRngProfit = window.noRngRevenue - window.expenses;\r\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\r\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\r\n }\r\n});\r\n\r\nfunction updateTable(tableId, item, price) {\r\n let row = document.querySelector('#' + tableId + ' .' + CSS.escape(item));\r\n if (row == null) {\r\n return 0;\r\n }\r\n\r\n row = row.querySelectorAll('td');\r\n let priceTd = row[1];\r\n let amountTd = row[2];\r\n let totalTd = row[3];\r\n let oldTotal = totalTd.innerText;\r\n let newTotal = price * amountTd.innerText;\r\n\r\n if (priceTd.innerText != price) {\r\n priceTd.innerText = price;\r\n }\r\n totalTd.innerText = newTotal;\r\n\r\n return newTotal - oldTotal;\r\n}\r\n\r\n// #endregion\r\n\r\nfunction initPatchNotes() {\r\n const patchNotesRows = document.getElementById(\"patchNotes\");\r\n for (const pn in patchNote) {\r\n const patchNoteContainer = document.createElement(\"div\");\r\n patchNotesRows.setAttribute('class', 'col-12 mb-4');\r\n\r\n const patchNoteElement = document.createElement(\"h6\");\r\n patchNoteElement.innerHTML = pn;\r\n const patchNoteList = document.createElement(\"ul\");\r\n for (const note of patchNote[pn]) {\r\n const noteElement = document.createElement(\"li\");\r\n noteElement.innerHTML = note;\r\n patchNoteList.appendChild(noteElement);\r\n }\r\n patchNoteContainer.appendChild(patchNoteElement);\r\n patchNoteContainer.appendChild(patchNoteList);\r\n\r\n patchNotesRows.appendChild(patchNoteContainer);\r\n }\r\n}\r\n\r\nfunction initExtraBuffSection() {\r\n // mooPass\r\n let mooPassToggle = document.getElementById(\"mooPassToggle\");\r\n let mooPass = localStorage.getItem('mooPass');\r\n if (mooPass) {\r\n mooPassToggle.checked = Boolean(mooPass);\r\n }\r\n mooPassToggle.onchange = () => {\r\n localStorage.setItem('mooPass', mooPassToggle.checked);\r\n }\r\n \r\n // comExp\r\n let comExpToggle = document.getElementById(\"comExpToggle\");\r\n let comExpInput = document.getElementById(\"comExpInput\");\r\n let comExp = localStorage.getItem('comExp');\r\n if (comExp) {\r\n let comExpNumber = Number(comExp);\r\n if (comExpNumber > 0) {\r\n comExpToggle.checked = true;\r\n comExpInput.value = comExpNumber;\r\n } else {\r\n comExpToggle.checked = false;\r\n comExpInput.disabled = true;\r\n }\r\n }\r\n const updateComExp = () => {\r\n if (comExpToggle.checked) {\r\n let comExp = Number(comExpInput.value);\r\n localStorage.setItem('comExp', comExp); \r\n comExpInput.disabled = false;\r\n } else {\r\n localStorage.setItem('comExp', 0);\r\n comExpInput.disabled = true;\r\n }\r\n }\r\n comExpToggle.onchange = updateComExp;\r\n comExpInput.onchange = updateComExp;\r\n\r\n // comDrop\r\n let comDropToggle = document.getElementById(\"comDropToggle\");\r\n let comDropInput = document.getElementById(\"comDropInput\");\r\n let comDrop = localStorage.getItem('comDrop');\r\n if (comDrop) {\r\n let comDropNumber = Number(comDrop);\r\n if (comDropNumber > 0) {\r\n comDropToggle.checked = true;\r\n comDropInput.value = comDropNumber;\r\n } else {\r\n comDropToggle.checked = false;\r\n comDropInput.disabled = true;\r\n }\r\n }\r\n const updateComDrop = () => {\r\n if (comDropToggle.checked) {\r\n let comDrop = Number(comDropInput.value);\r\n localStorage.setItem('comDrop', comDrop); \r\n comDropInput.disabled = false;\r\n } else {\r\n localStorage.setItem('comDrop', 0);\r\n comDropInput.disabled = true;\r\n }\r\n }\r\n comDropToggle.onchange = updateComDrop;\r\n comDropInput.onchange = updateComDrop;\r\n}\r\n\r\n\r\nfunction updateState() {\r\n updateEquipmentState();\r\n updateLevels();\r\n updateFoodState();\r\n updateDrinksState();\r\n updateAbilityState();\r\n}\r\n\r\nfunction updateUI() {\r\n updateCombatStatsUI();\r\n updateFoodUI();\r\n updateDrinksUI();\r\n updateAbilityUI();\r\n\r\n updateContent();\r\n}\r\n\r\nconst darkModeToggle = document.getElementById('darkModeToggle');\r\nconst body = document.body;\r\n\r\nif (localStorage.getItem('darkModeEnabled') === 'true') {\r\n body.classList.add('dark-mode');\r\n const tables = document.getElementsByClassName('profit-table');\r\n for (const table of tables) {\r\n table.classList.toggle('table-striped');\r\n }\r\n darkModeToggle.checked = true;\r\n}\r\n\r\ndarkModeToggle.addEventListener('change', () => {\r\n body.classList.toggle('dark-mode');\r\n const tables = document.getElementsByClassName('profit-table');\r\n for (const table of tables) {\r\n table.classList.toggle('table-striped');\r\n }\r\n localStorage.setItem('darkModeEnabled', darkModeToggle.checked);\r\n});\r\n\r\nfunction updateContent() {\r\n document.querySelectorAll('[data-i18n]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n');\r\n if (key) {\r\n element.textContent = i18next.t(key);\r\n }\r\n });\r\n\r\n document.querySelectorAll('[data-i18n-placeholder]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n-placeholder');\r\n if (key) {\r\n element.placeholder = i18next.t(key);\r\n }\r\n });\r\n\r\n document.querySelectorAll('option[data-i18n]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n');\r\n if (key) {\r\n element.textContent = i18next.t(key);\r\n }\r\n });\r\n}\r\n\r\ninitEquipmentSection();\r\ninitHouseRoomsModal();\r\ninitAchievementsModal();\r\ninitLevelSection();\r\ninitFoodSection();\r\ninitDrinksSection();\r\ninitAbilitiesSection();\r\ninitZones();\r\ninitDungeons();\r\ninitTriggerModal();\r\ninitSimulationControls();\r\ninitEquipmentSetsModal();\r\ninitErrorHandling();\r\ninitImportExportModal();\r\ninitDamageDoneTaken();\r\ninitPatchNotes();\r\ninitExtraBuffSection();\r\n\r\nupdateState();\r\nupdateUI();\r\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/src/main.js b/src/main.js index 56fe76b4..60f09450 100644 --- a/src/main.js +++ b/src/main.js @@ -235,8 +235,19 @@ function initAchievementsModal(){ bufDesc.appendChild(buffValue); cardHeader.appendChild(bufDesc); - let cardStatics = createElement("div", "ms-auto", `(0/${detailMapCount})`); + let cardStatics = createElement("div", "ms-auto btn", `(0/${detailMapCount})`); cardStatics.id = `AchTier${tier.sortIndex}Statics`; + cardStatics.dataset.checked = "true"; + cardStatics.addEventListener("click", function (e) { + const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier="${tier.sortIndex}"]`); + for (const check of checks) { + check.checked = cardStatics.dataset.checked == "true"; + const hrid = check.dataset.achievementHrid; + player.achievements[hrid] = check.checked; + } + cardStatics.dataset.checked = cardStatics.dataset.checked == "true" ? "false" : "true"; + refreshAchievementStatics(); + }); cardHeader.appendChild(cardStatics); card.appendChild(cardHeader); From 429a0b8222bd9d35b1563935f7d3a930683a9124 Mon Sep 17 00:00:00 2001 From: shykai Date: Fri, 19 Dec 2025 09:39:04 +0800 Subject: [PATCH 03/46] fix --- dist/bundle.js | 10 +++++----- dist/bundle.js.map | 2 +- src/main.js | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/dist/bundle.js b/dist/bundle.js index afcd2e23..de5c174f 100644 --- a/dist/bundle.js +++ b/dist/bundle.js @@ -1820,11 +1820,11 @@ let currentSimResults = {}; let currentPlayerTabId = '1'; let playerDataMap = { - "1": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},achievements:{}}", - "2": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},achievements:{}}", - "3": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},achievements:{}}", - "4": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},achievements:{}}", - "5": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},achievements:{}}" + "1": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},\"achievements\":{}}", + "2": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},\"achievements\":{}}", + "3": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},\"achievements\":{}}", + "4": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},\"achievements\":{}}", + "5": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},\"achievements\":{}}" }; window.revenue = 0; window.noRngRevenue = 0; diff --git a/dist/bundle.js.map b/dist/bundle.js.map index bc969c74..725e832a 100644 --- a/dist/bundle.js.map +++ b/dist/bundle.js.map @@ -1 +1 @@ -{"version":3,"file":"bundle.js","mappings":";;;;;;;;;;;;;;;;;AAA0B;AACkC;AAC5B;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,wDAAgB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,6CAAI;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;ACxNG;AACkD;AACR;AACpE;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,gEAAwB;AAChE;AACA,0CAA0C,4DAAoB;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,WAAW;;;;;;;;;;;;;;AC1B1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,IAAI,EAAC;;;;;;;;;;;;;;;ACdpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wDAAwD;AACtF,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,mBAAmB;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;;AC/gBA;AAC4B;AACtB;AAChC;AACA;AACA;AACA;AACA;AACA,6BAA6B,qDAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;ACtF4B;AACmD;AACzG;AACA;AACA;AACA;AACA,uBAAuB,qDAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,iFAAoC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS,EAAC;;;;;;;;;;;;;;;;;AC/CC;AACsC;AAChE;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,0DAAkB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS;;;;;;;;;;;;;;;;;;;;AC7BQ;AACM;AACA;AACF;AACA;AACI;AACxC;AACA,qBAAqB,mDAAU;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,kDAAS;AACrD;AACA;AACA,qDAAqD,mDAAU;AAC/D,2DAA2D,mDAAU;AACrE,qEAAqE,gDAAO;AAC5E;AACA;AACA,2CAA2C,kDAAS;AACpD;AACA,SAAS;AACT;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,MAAM,EAAC;;;;;;;;;;;;;;;;ACvLsE;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,wEAAgC;AAC5C;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UCjLvB;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;UAEA;UACA;;;;;WCzBA;WACA;WACA;WACA;WACA,yCAAyC,wCAAwC;WACjF;WACA;WACA;;;;;WCPA;WACA;WACA;WACA;WACA;;;;;WCJA;WACA;WACA;WACA;WACA,GAAG;WACH;WACA;WACA,CAAC;;;;;WCPD;;;;;WCAA;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D;;;;;WCNA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;;;;WClBA;;WAEA;WACA;WACA;WACA;WACA;WACA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrBuD;AACN;AAC2B;AACN;AACU;AAC7B;AACM;AACN;AACyD;AACF;AACE;AACA;AAClC;AACc;AACN;AACE;AACF;AACG;AACF;AACnF;AAC0C;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,2FAA4B;AAC5D,qCAAqC,qHAAiC;AACtE;AACA;AACA;AACA,iBAAiB,kEAAM;AACvB;AACA,IAAI,SAAI;AACR,IAAI,WAAM;AACV,IAAI,cAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,iBAAiB;AACxpC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,iBAAiB;AACxpC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,iBAAiB;AACxpC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,iBAAiB;AACxpC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,iBAAiB;AACxpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA,sCAAsC,qEAAa;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,mCAAmC,0EAAkB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,iFAAkB;AAClD;AACA,4FAA4F,eAAe;AAC3G;AACA;AACA;AACA,uDAAuD,eAAe;AACtE,6BAA6B,KAAK,GAAG,MAAM;AAC3C;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,iFAAkB;AAClD;AACA,sCAAsC,6EAAoB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,eAAe;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oEAAoE,eAAe;AACnF,mCAAmC,eAAe;AAClD;AACA;AACA,gGAAgG,eAAe;AAC/G;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,gCAAgC,eAAe;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,iBAAiB;AACpD;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,iDAAiD,iBAAiB;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,qEAAa;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,qEAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,qEAAS;AACvD,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,6EAAoB;AACvD;AACA;AACA,qBAAqB,4EAAmB;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,sCAAsC,qEAAa;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,SAAI;AACZ,YAAY,SAAI,mBAAmB,SAAI;AACvC,2BAA2B,qEAAa,CAAC,SAAI;AAC7C,uBAAuB,SAAI;AAC3B;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,qFAAqF,SAAI;AACzF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,uCAAuC,qEAAa;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,WAAM;AACd,YAAY,WAAM,mBAAmB,WAAM;AAC3C,2BAA2B,qEAAa,CAAC,WAAM;AAC/C,uBAAuB,WAAM;AAC7B;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,sFAAsF,WAAM;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D,UAAU;AACV,0CAA0C,wEAAgB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,cAAS;AACjB,YAAY,cAAS,mBAAmB,cAAS;AACjD,8BAA8B,wEAAgB,CAAC,cAAS;AACxD,uBAAuB,cAAS;AAChC;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,4DAA4D,yFAAgC;AAC5F,2DAA2D,yFAAgC;AAC3F,4DAA4D,yFAAgC,YAAY,cAAS;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAS;AACjC,IAAI,cAAS,iBAAiB,cAAS;AACvC,IAAI,cAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,cAAS;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,SAAI;AAChC;AACA;AACA,4BAA4B,WAAM;AAClC;AACA;AACA,4BAA4B,cAAS;AACrC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,qEAAa;AACrD,MAAM;AACN,wCAAwC,wEAAgB;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,yFAAgC;AAC5C;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,wFAAgC;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,wFAAgC;AACrD;AACA;AACA;AACA,mCAAmC,uFAA+B;AAClE,MAAM;AACN,mCAAmC,uFAA+B;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uFAA+B;AACnD;AACA,qEAAqE,yFAAgC;AACrG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,wEAAe;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2GAA2G,WAAW;AACtH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,WAAW;AAC5C,yDAAyD,WAAW;AACpE;AACA;AACA;AACA,gEAAgE,WAAW;AAC3E;AACA;AACA,qGAAqG,WAAW;AAChH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qFAAqF,WAAW;AAChG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4GAA4G,WAAW;AACvH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,WAAW;AAC7C,0DAA0D,WAAW;AACrE;AACA;AACA;AACA,iEAAiE,WAAW;AAC5E;AACA;AACA,sGAAsG,WAAW;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sFAAsF,WAAW;AACjG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,OAAO;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uBAAuB;AAC3C,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,+EAAsB;AAClC,+BAA+B,+EAAsB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,iJAAiJ;AAC9L;AACA,gBAAgB,+EAAsB;AACtC,mCAAmC,+EAAsB;AACzD;AACA;AACA;AACA,qDAAqD,uIAAuI;AAC5L;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,+BAA+B;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,UAAU,kCAAkC;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uEAAuE,oDAAoD;AAC3H,iEAAiE,oDAAoD;AACrH,iFAAiF,oDAAoD;AACrI;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,eAAe;AACrC;AACA;AACA;AACA;AACA,0BAA0B,mBAAmB;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,SAAS;AAC3D;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,KAAK;AACL,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,iBAAiB;AACrC;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,+CAA+C,kBAAkB;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,+EAAsB;AACvC;AACA;AACA;AACA,SAAS;AACT;AACA,UAAU,kCAAkC,qEAAqE;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2EAA2E,wEAAgB;AAC3F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa,qEAAa;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,qEAAa;AACjC,iCAAiC,qEAAa;AAC9C;AACA,kBAAkB,SAAS,wEAAgB;AAC3C,iCAAiC,wEAAgB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,qEAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kFAAkF,IAAI;AACtF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,QAAQ;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,QAAQ;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wEAAgB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,6BAA6B;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qEAAqE,UAAU;AAC/E;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,sCAAsC,4BAA4B;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,qCAAqC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC,2FAA4B;AAChE;AACA;AACA;AACA;AACA,yCAAyC,qHAAiC;AAC1E;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,SAAI;AACxB,yCAAyC,sEAAU,CAAC,SAAI,gBAAgB,SAAI;AAC5E;AACA,kBAAkB;AAClB;AACA;AACA;AACA,oBAAoB,WAAM;AAC1B,yCAAyC,sEAAU,CAAC,WAAM,gBAAgB,WAAM;AAChF;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,cAAS,mCAAmC,yFAAgC;AAChG;AACA,sCAAsC,mEAAO,CAAC,cAAS,iDAAiD,cAAS;AACjH;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,oDAAoD;AACxE;AACA;AACA;AACA;AACA;AACA,wCAAwC,qHAAiC;AACzE;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;AACA;AACA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,wCAAwC;AACrF,kCAAkC,uDAAuD;AACzF;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,qHAAiC;AAC9E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,KAAK,SAAS,qEAAS;AAC5E;AACA,KAAK;AACL;AACA;AACA;AACA,yBAAyB,sEAAU;AACnC;AACA;AACA;AACA;AACA,0BAA0B,sEAAU;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,mEAAO;AAC1C;AACA;AACA;AACA,mBAAmB,kEAAM;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wEAAwE,UAAU;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,IAAI;AAC7D;AACA;AACA;AACA;AACA;AACA,sCAAsC,wEAAe;AACrD;AACA;AACA;AACA;AACA;AACA,4CAA4C,0BAA0B;AACtE;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,wCAAwC;AACjG,8CAA8C,uDAAuD;AACrG;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,oDAAoD;AACpF;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,WAAW,QAAQ,KAAK;AACzD;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,KAAK;AACxC,oCAAoC,KAAK,YAAY,UAAU;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,wBAAwB,WAAW,WAAW;AACnF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd,qEAAqE,WAAW;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd,sEAAsE,YAAY;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sEAAsE,cAAc,IAAI,YAAY;AACpG;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,YAAY,IAAI,eAAe,GAAG,WAAW;AACxF;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,kCAAkC;AAClC,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB,qBAAqB;AACrB,gBAAgB;AAChB,kBAAkB;AAClB,qBAAqB;AACrB,sBAAsB;AACtB,sBAAsB;AACtB,wBAAwB;AACxB;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,uFAA+B;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,SAAI;AAClB,gBAAgB,WAAM;AACtB,mBAAmB,cAAS;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,qEAAa;AACxC,yBAAyB,qEAAa;AACtC;AACA,wCAAwC,gCAAgC,qEAAa;AACrF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yCAAyC;AACzC;AACA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;AACA;AACA;AACA,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,sBAAsB,4GAAmD;AACzE;AACA,aAAa;AACb;AACA;AACA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,6CAAS;AAC9B;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,6CAAS;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sources":["webpack://mwicombatsimulator/./src/combatsimulator/ability.js","webpack://mwicombatsimulator/./src/combatsimulator/achievement.js","webpack://mwicombatsimulator/./src/combatsimulator/buff.js","webpack://mwicombatsimulator/./src/combatsimulator/combatUnit.js","webpack://mwicombatsimulator/./src/combatsimulator/consumable.js","webpack://mwicombatsimulator/./src/combatsimulator/equipment.js","webpack://mwicombatsimulator/./src/combatsimulator/houseRoom.js","webpack://mwicombatsimulator/./src/combatsimulator/player.js","webpack://mwicombatsimulator/./src/combatsimulator/trigger.js","webpack://mwicombatsimulator/webpack/bootstrap","webpack://mwicombatsimulator/webpack/runtime/define property getters","webpack://mwicombatsimulator/webpack/runtime/get javascript chunk filename","webpack://mwicombatsimulator/webpack/runtime/global","webpack://mwicombatsimulator/webpack/runtime/hasOwnProperty shorthand","webpack://mwicombatsimulator/webpack/runtime/make namespace object","webpack://mwicombatsimulator/webpack/runtime/publicPath","webpack://mwicombatsimulator/webpack/runtime/jsonp chunk loading","webpack://mwicombatsimulator/./src/main.js"],"sourcesContent":["import Buff from \"./buff\";\r\nimport abilityDetailMap from \"./data/abilityDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nconst abilityFromCombatStat = {\r\n \"blaze\":\r\n {\r\n \"hrid\": \"/abilities/blaze\",\r\n \"name\": \"Blaze\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"allEnemies\",\r\n \"effectType\": \"/ability_effect_types/damage\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"/damage_types/fire\",\r\n \"baseDamageFlat\": 0,\r\n \"baseDamageFlatLevelBonus\": 0.0,\r\n \"baseDamageRatio\": 0.3,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/number_of_active_units\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n },\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/current_hp\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n }\r\n ],\r\n },\r\n \"bloom\":\r\n {\r\n \"hrid\": \"/abilities/bloom\",\r\n \"name\": \"Bloom\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"lowestHpAlly\",\r\n \"effectType\": \"/ability_effect_types/heal\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"\",\r\n \"baseDamageFlat\": 10,\r\n \"baseDamageFlatLevelBonus\": 0,\r\n \"baseDamageRatio\": 0.15,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_allies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/lowest_hp_percentage\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/less_than_equal\",\r\n \"value\": 100\r\n }\r\n ],\r\n }\r\n}\r\n\r\nclass Ability {\r\n constructor(hrid, level = 1, triggers = null) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameAbility = abilityDetailMap[hrid];\r\n if (!gameAbility) {\r\n gameAbility = abilityFromCombatStat[hrid];\r\n }\r\n if (!gameAbility) {\r\n throw new Error(\"No ability found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.manaCost = gameAbility.manaCost;\r\n this.cooldownDuration = gameAbility.cooldownDuration;\r\n this.castDuration = gameAbility.castDuration;\r\n this.isSpecialAbility = gameAbility.isSpecialAbility;\r\n\r\n this.abilityEffects = [];\r\n\r\n for (const effect of gameAbility.abilityEffects) {\r\n let abilityEffect = {\r\n targetType: effect.targetType,\r\n effectType: effect.effectType,\r\n combatStyleHrid: effect.combatStyleHrid,\r\n damageType: effect.damageType,\r\n damageFlat: effect.baseDamageFlat + (this.level - 1) * effect.baseDamageFlatLevelBonus,\r\n damageRatio: effect.baseDamageRatio + (this.level - 1) * effect.baseDamageRatioLevelBonus,\r\n bonusAccuracyRatio: effect.bonusAccuracyRatio + (this.level - 1) * effect.bonusAccuracyRatioLevelBonus,\r\n damageOverTimeRatio: effect.damageOverTimeRatio,\r\n damageOverTimeDuration: effect.damageOverTimeDuration,\r\n armorDamageRatio: effect.armorDamageRatio + (this.level - 1) * effect.armorDamageRatioLevelBonus,\r\n hpDrainRatio: effect.hpDrainRatio,\r\n pierceChance: effect.pierceChance,\r\n blindChance: effect.blindChance,\r\n blindDuration: effect.blindDuration,\r\n silenceChance: effect.silenceChance,\r\n silenceDuration: effect.silenceDuration,\r\n stunChance: effect.stunChance,\r\n stunDuration: effect.stunDuration,\r\n spendHpRatio: effect.spendHpRatio,\r\n buffs: null,\r\n };\r\n if (effect.buffs) {\r\n abilityEffect.buffs = [];\r\n for (const buff of effect.buffs) {\r\n abilityEffect.buffs.push(new Buff(buff, this.level));\r\n }\r\n }\r\n this.abilityEffects.push(abilityEffect);\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameAbility.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let ability = new Ability(dto.hrid, dto.level, triggers);\r\n\r\n return ability;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n\r\n if (source.isSilenced) {\r\n return false;\r\n }\r\n\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = this.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Ability;\r\n","import Buff from \"./buff\";\r\nimport achievementTierDetailMap from \"./data/achievementTierDetailMap.json\";\r\nimport achievementDetailMap from \"./data/achievementDetailMap.json\";\r\n\r\nclass Achievement {\r\n constructor(achievements) {\r\n this.achievements = achievements;\r\n this.buffs = [];\r\n\r\n for(const tier of Object.values(achievementTierDetailMap)) {\r\n let isGetAll = true;\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid)\r\n for(const achievement of Object.values(detailMap)) {\r\n if(!this.achievements[achievement.hrid] || this.achievements[achievement.hrid] == false) {\r\n isGetAll = false;\r\n break;\r\n }\r\n }\r\n if(isGetAll) {\r\n let buff = new Buff(tier.buff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default Achievement;","class Buff {\r\n startTime;\r\n\r\n constructor(buff, level = 1) {\r\n this.uniqueHrid = buff.uniqueHrid;\r\n this.typeHrid = buff.typeHrid;\r\n this.ratioBoost = buff.ratioBoost + (level - 1) * buff.ratioBoostLevelBonus;\r\n this.flatBoost = buff.flatBoost + (level - 1) * buff.flatBoostLevelBonus;\r\n this.duration = buff.duration;\r\n this.multiplierForSkillHrid = buff.multiplierForSkillHrid ?? \"\";\r\n this.multiplierPerSkillLevel = buff.multiplierPerSkillLevel ?? 0;\r\n }\r\n}\r\n\r\nexport default Buff;\r\n","class CombatUnit {\r\n isPlayer;\r\n isStunned = false;\r\n stunExpireTime = null;\r\n isBlinded = false;\r\n blindExpireTime = null;\r\n isSilenced = false;\r\n silenceExpireTime = null;\r\n\r\n isOutOfMana = false;\r\n\r\n // Base levels which don't change after initialization\r\n staminaLevel = 1;\r\n intelligenceLevel = 1;\r\n attackLevel = 1;\r\n meleeLevel = 1;\r\n defenseLevel = 1;\r\n rangedLevel = 1;\r\n magicLevel = 1;\r\n\r\n experience = 0;\r\n experienceRate = 0;\r\n enrageTime = 0;\r\n\r\n abilities = [null, null, null, null];\r\n food = [null, null, null];\r\n drinks = [null, null, null];\r\n houseRooms = [];\r\n achievements = null;\r\n dropTable = [];\r\n rareDropTable = [];\r\n abilityManaCosts = new Map();\r\n\r\n // Calculated combat stats including temporary buffs\r\n combatDetails = {\r\n staminaLevel: 1,\r\n intelligenceLevel: 1,\r\n attackLevel: 1,\r\n meleeLevel: 1,\r\n defenseLevel: 1,\r\n rangedLevel: 1,\r\n magicLevel: 1,\r\n maxHitpoints: 110,\r\n currentHitpoints: 110,\r\n maxManapoints: 110,\r\n currentManapoints: 110,\r\n stabAccuracyRating: 11,\r\n slashAccuracyRating: 11,\r\n smashAccuracyRating: 11,\r\n rangedAccuracyRating: 11,\r\n magicAccuracyRating: 11,\r\n stabMaxDamage: 11,\r\n slashMaxDamage: 11,\r\n smashMaxDamage: 11,\r\n rangedMaxDamage: 11,\r\n magicMaxDamage: 11,\r\n stabEvasionRating: 11,\r\n slashEvasionRating: 11,\r\n smashEvasionRating: 11,\r\n rangedEvasionRating: 11,\r\n magicEvasionRating: 11,\r\n defensiveMaxDamage: 0,\r\n totalArmor: 0.2,\r\n totalWaterResistance: 0.4,\r\n totalNatureResistance: 0.4,\r\n totalFireResistance: 0.4,\r\n abilityHaste: 0,\r\n tenacity: 0,\r\n totalThreat: 100,\r\n combatStats: {\r\n combatStyleHrid: \"/combat_styles/smash\",\r\n damageType: \"/damage_types/physical\",\r\n attackInterval: 3000000000,\r\n autoAttackDamage: 0,\r\n abilityDamage: 0,\r\n criticalRate: 0,\r\n criticalDamage: 0,\r\n stabAccuracy: 0,\r\n slashAccuracy: 0,\r\n smashAccuracy: 0,\r\n rangedAccuracy: 0,\r\n magicAccuracy: 0,\r\n stabDamage: 0,\r\n slashDamage: 0,\r\n smashDamage: 0,\r\n rangedDamage: 0,\r\n magicDamage: 0,\r\n defensiveDamage: 0,\r\n taskDamage: 0,\r\n physicalAmplify: 0,\r\n waterAmplify: 0,\r\n natureAmplify: 0,\r\n fireAmplify: 0,\r\n healingAmplify: 0,\r\n physicalThorns: 0,\r\n elementalThorns: 0,\r\n maxHitpoints: 0,\r\n maxManapoints: 0,\r\n stabEvasion: 0,\r\n slashEvasion: 0,\r\n smashEvasion: 0,\r\n rangedEvasion: 0,\r\n magicEvasion: 0,\r\n armor: 0,\r\n waterResistance: 0,\r\n natureResistance: 0,\r\n fireResistance: 0,\r\n lifeSteal: 0,\r\n hpRegenPer10: 0.01,\r\n mpRegenPer10: 0.01,\r\n combatDropRate: 0,\r\n combatDropQuantity: 0,\r\n combatRareFind: 0,\r\n combatExperience: 0,\r\n foodSlots: 1,\r\n drinkSlots: 1,\r\n armorPenetration: 0,\r\n waterPenetration: 0,\r\n naturePenetration: 0,\r\n firePenetration: 0,\r\n manaLeech: 0,\r\n castSpeed: 0,\r\n threat: 100,\r\n parry: 0,\r\n mayhem: 0,\r\n pierce: 0,\r\n curse: 0,\r\n ripple: 0,\r\n bloom: 0,\r\n blaze: 0,\r\n weaken: 0,\r\n fury: 0,\r\n foodHaste: 0,\r\n drinkConcentration: 0,\r\n damageTaken: 0,\r\n attackSpeed: 0,\r\n armorDamageRatio: 0,\r\n hpDrainRatio: 0,\r\n primaryTraining: \"\",\r\n focusTraining: \"\",\r\n staminaExperience: 0,\r\n intelligenceExperience: 0,\r\n attackExperience: 0,\r\n defenseExperience: 0,\r\n meleeExperience: 0,\r\n rangedExperience: 0,\r\n magicExperience: 0,\r\n retaliation: 0,\r\n },\r\n };\r\n combatBuffs = {};\r\n permanentBuffs = {};\r\n zoneBuffs = {};\r\n extraBuffs = {};\r\n\r\n constructor() { }\r\n\r\n updateCombatDetails() {\r\n if (this.isPlayer) {\r\n if (this.combatDetails.combatStats.hpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01 + this.combatDetails.combatStats.hpRegenPer10;\r\n }\r\n if (this.combatDetails.combatStats.mpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01 + this.combatDetails.combatStats.mpRegenPer10;\r\n }\r\n }\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((stat) => {\r\n this.combatDetails[stat + \"Level\"] = this[stat + \"Level\"];\r\n let boosts = this.getBuffBoosts(\"/buff_types/\" + stat + \"_level\");\r\n boosts.forEach((buff) => {\r\n this.combatDetails[stat + \"Level\"] += (this[stat + \"Level\"] * buff.ratioBoost);\r\n this.combatDetails[stat + \"Level\"] += buff.flatBoost;\r\n });\r\n });\r\n\r\n this.combatDetails.maxHitpoints = Math.floor\r\n (10 * (10 + this.combatDetails.staminaLevel) + this.combatDetails.combatStats.maxHitpoints);\r\n this.combatDetails.maxManapoints = Math.floor\r\n (10 * (10 + this.combatDetails.intelligenceLevel) + this.combatDetails.combatStats.maxManapoints);\r\n\r\n let accuracyRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_accuracy\").ratioBoost;\r\n let damageRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_damage\").ratioBoost;\r\n // if (accuracyRatioBoostFromFury > 0) {\r\n // console.log(\"Fury Boost: \" + accuracyRatioBoostFromFury);\r\n // }\r\n\r\n let accuracyRatioBoost = this.getBuffBoost(\"/buff_types/accuracy\").ratioBoost;\r\n let damageRatioBoost = this.getBuffBoost(\"/buff_types/damage\").ratioBoost;\r\n\r\n [\"stab\", \"slash\", \"smash\"].forEach((style) => {\r\n this.combatDetails[style + \"AccuracyRating\"] =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Accuracy\"]) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails[style + \"MaxDamage\"] =\r\n (10 + this.combatDetails.meleeLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Damage\"]) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n let baseEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats[style + \"Evasion\"]);\r\n this.combatDetails[style + \"EvasionRating\"] = baseEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails[style + \"EvasionRating\"] += boost.flatBoost;\r\n this.combatDetails[style + \"EvasionRating\"] += baseEvasion * boost.ratioBoost;\r\n }\r\n });\r\n\r\n this.combatDetails.defensiveMaxDamage = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.defensiveDamage);\r\n\r\n // when equiped bulwark\r\n if (this.equipment?.['/equipment_types/two_hand']?.hrid.includes(\"bulwark\")) {\r\n this.combatDetails.smashMaxDamage += this.combatDetails.defensiveMaxDamage;\r\n }\r\n\r\n this.combatDetails.rangedAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.rangedAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.rangedMaxDamage =\r\n (10 + this.combatDetails.rangedLevel) *\r\n (1 + this.combatDetails.combatStats.rangedDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseRangedEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.rangedEvasion);\r\n this.combatDetails.rangedEvasionRating = baseRangedEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.rangedEvasionRating += boost.flatBoost;\r\n this.combatDetails.rangedEvasionRating += baseRangedEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.damageTaken = this.getBuffBoost(\"/buff_types/damage_taken\").flatBoost;\r\n // if (this.combatDetails.combatStats.damageTaken > 0) {\r\n // console.log(\"Damage taken: \" + this.combatDetails.combatStats.damageTaken);\r\n // }\r\n\r\n this.combatDetails.magicAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.magicAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.magicMaxDamage =\r\n (10 + this.combatDetails.magicLevel) *\r\n (1 + this.combatDetails.combatStats.magicDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseMagicEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.magicEvasion);\r\n this.combatDetails.magicEvasionRating = baseMagicEvasion;\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.magicEvasionRating += boost.flatBoost;\r\n this.combatDetails.magicEvasionRating += baseMagicEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.physicalAmplify += this.getBuffBoost(\"/buff_types/physical_amplify\").flatBoost;\r\n this.combatDetails.combatStats.waterAmplify += this.getBuffBoost(\"/buff_types/water_amplify\").flatBoost;\r\n this.combatDetails.combatStats.natureAmplify += this.getBuffBoost(\"/buff_types/nature_amplify\").flatBoost;\r\n this.combatDetails.combatStats.fireAmplify += this.getBuffBoost(\"/buff_types/fire_amplify\").flatBoost;\r\n\r\n this.combatDetails.combatStats.attackInterval /= (1 + (this.combatDetails.attackLevel / 2000));\r\n\r\n let baseAttackSpeed = this.combatDetails.combatStats.attackSpeed;\r\n this.combatDetails.combatStats.attackInterval /= (1 + baseAttackSpeed);\r\n let attackIntervalBoosts = this.getBuffBoosts(\"/buff_types/attack_speed\");\r\n let attackIntervalRatioBoost = attackIntervalBoosts\r\n .map((boost) => boost.ratioBoost)\r\n .reduce((prev, cur) => prev + cur, 0);\r\n this.combatDetails.combatStats.attackInterval /= (1 + attackIntervalRatioBoost);\r\n\r\n let baseArmor = 0.2 * this.combatDetails.defenseLevel + this.combatDetails.combatStats.armor;\r\n this.combatDetails.totalArmor = baseArmor;\r\n let armorBoosts = this.getBuffBoosts(\"/buff_types/armor\");\r\n for (const boost of armorBoosts) {\r\n this.combatDetails.totalArmor += boost.flatBoost;\r\n this.combatDetails.totalArmor += baseArmor * boost.ratioBoost;\r\n }\r\n\r\n let baseWaterResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.waterResistance;\r\n this.combatDetails.totalWaterResistance = baseWaterResistance;\r\n let waterResistanceBoosts = this.getBuffBoosts(\"/buff_types/water_resistance\");\r\n for (const boost of waterResistanceBoosts) {\r\n this.combatDetails.totalWaterResistance += boost.flatBoost;\r\n this.combatDetails.totalWaterResistance += baseWaterResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseNatureResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.natureResistance;\r\n this.combatDetails.totalNatureResistance = baseNatureResistance;\r\n let natureResistanceBoosts = this.getBuffBoosts(\"/buff_types/nature_resistance\");\r\n for (const boost of natureResistanceBoosts) {\r\n this.combatDetails.totalNatureResistance += boost.flatBoost;\r\n this.combatDetails.totalNatureResistance += baseNatureResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseFireResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.fireResistance;\r\n this.combatDetails.totalFireResistance = baseFireResistance;\r\n let fireResistanceBoosts = this.getBuffBoosts(\"/buff_types/fire_resistance\");\r\n for (const boost of fireResistanceBoosts) {\r\n this.combatDetails.totalFireResistance += boost.flatBoost;\r\n this.combatDetails.totalFireResistance += baseFireResistance * boost.ratioBoost;\r\n }\r\n\r\n let hpRegenBoosts = this.getBuffBoost(\"/buff_types/hp_regen\");\r\n this.combatDetails.combatStats.hpRegenPer10 += this.combatDetails.combatStats.hpRegenPer10 * hpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.hpRegenPer10 += hpRegenBoosts.flatBoost;\r\n\r\n let mpRegenBoosts = this.getBuffBoost(\"/buff_types/mp_regen\");\r\n this.combatDetails.combatStats.mpRegenPer10 += this.combatDetails.combatStats.mpRegenPer10 * mpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.mpRegenPer10 += mpRegenBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.lifeSteal += this.getBuffBoost(\"/buff_types/life_steal\").flatBoost;\r\n this.combatDetails.combatStats.physicalThorns += this.getBuffBoost(\r\n \"/buff_types/physical_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.elementalThorns += this.getBuffBoost(\r\n \"/buff_types/elemental_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.combatExperience += this.getBuffBoost(\"/buff_types/wisdom\").flatBoost;\r\n this.combatDetails.combatStats.criticalRate += this.getBuffBoost(\"/buff_types/critical_rate\").flatBoost;\r\n this.combatDetails.combatStats.criticalDamage += this.getBuffBoost(\"/buff_types/critical_damage\").flatBoost;\r\n\r\n this.combatDetails.combatStats.castSpeed += this.getBuffBoost(\"/buff_types/cast_speed\").flatBoost;\r\n this.combatDetails.combatStats.castSpeed += this.combatDetails[\"attackLevel\"] / 2000;\r\n\r\n let combatDropRateBoosts = this.getBuffBoost(\"/buff_types/combat_drop_rate\");\r\n this.combatDetails.combatStats.combatDropRate += (1 + this.combatDetails.combatStats.combatDropRate) * combatDropRateBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropRate += combatDropRateBoosts.flatBoost;\r\n let combatRareFindBoosts = this.getBuffBoost(\"/buff_types/rare_find\");\r\n this.combatDetails.combatStats.combatRareFind += (1 + this.combatDetails.combatStats.combatRareFind) * combatRareFindBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatRareFind += combatRareFindBoosts.flatBoost;\r\n let combatDropQuantityBoosts = this.getBuffBoost(\"/buff_types/combat_drop_quantity\");\r\n this.combatDetails.combatStats.combatDropQuantity += (1 + this.combatDetails.combatStats.combatDropQuantity) * combatDropQuantityBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropQuantity += combatDropQuantityBoosts.flatBoost;\r\n\r\n let baseThreat = 100 + this.combatDetails.combatStats.threat;\r\n this.combatDetails.totalThreat = baseThreat;\r\n let threatBoosts = this.getBuffBoost(\"/buff_types/threat\");\r\n if (threatBoosts.ratioBoost !== 0) {\r\n this.combatDetails.combatStats.threat += baseThreat * threatBoosts.ratioBoost;\r\n } else {\r\n this.combatDetails.combatStats.threat = baseThreat;\r\n }\r\n this.combatDetails.combatStats.threat += threatBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.retaliation += this.getBuffBoost(\"/buff_types/retaliation\").flatBoost;\r\n }\r\n\r\n addBuff(buff, currentTime) {\r\n buff.startTime = currentTime;\r\n this.combatBuffs[buff.uniqueHrid] = buff;\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n removeBuff(buff) {\r\n if (!this.combatBuffs[buff.uniqueHrid]) {\r\n return;\r\n }\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n addPermanentBuff(buff) {\r\n if (this.permanentBuffs[buff.typeHrid]) {\r\n this.permanentBuffs[buff.typeHrid].flatBoost += buff.flatBoost;\r\n this.permanentBuffs[buff.typeHrid].ratioBoost += buff.ratioBoost;\r\n } else {\r\n this.permanentBuffs[buff.typeHrid] = buff;\r\n }\r\n }\r\n\r\n generatePermanentBuffs() {\r\n for (let i = 0; i < this.houseRooms.length; i++) {\r\n const houseRoom = this.houseRooms[i];\r\n houseRoom.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n\r\n if (this.achievements) {\r\n this.achievements.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.zoneBuffs) {\r\n this.zoneBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.extraBuffs) {\r\n this.extraBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n }\r\n\r\n removeExpiredBuffs(currentTime) {\r\n let expiredBuffs = Object.values(this.combatBuffs).filter(\r\n (buff) => buff.startTime + buff.duration <= currentTime\r\n );\r\n expiredBuffs.forEach((buff) => {\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n });\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearBuffs() {\r\n this.combatBuffs = structuredClone(this.permanentBuffs);\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearCCs() {\r\n this.isStunned = false;\r\n this.stunExpireTime = null;\r\n this.isSilenced = false;\r\n this.silenceExpireTime = null;\r\n this.isBlinded = false;\r\n this.blindExpireTime = null;\r\n this.combatDetails.combatStats.damageTaken = 0;\r\n }\r\n\r\n getBuffBoosts(type) {\r\n let boosts = [];\r\n Object.values(this.combatBuffs)\r\n .filter((buff) => buff.typeHrid == type)\r\n .forEach((buff) => {\r\n boosts.push({ ratioBoost: buff.ratioBoost, flatBoost: buff.flatBoost });\r\n });\r\n\r\n return boosts;\r\n }\r\n\r\n getBuffBoost(type) {\r\n let boosts = this.getBuffBoosts(type);\r\n\r\n let boost = {\r\n ratioBoost: 0,\r\n flatBoost: 0,\r\n };\r\n\r\n for (let i = 0; i < boosts.length; i++) {\r\n boost.ratioBoost += boosts[i]?.ratioBoost ?? 0;\r\n boost.flatBoost += boosts[i]?.flatBoost ?? 0;\r\n }\r\n\r\n return boost;\r\n }\r\n\r\n reset(currentTime = 0) {\r\n this.clearCCs();\r\n this.clearBuffs();\r\n this.updateCombatDetails();\r\n this.resetCooldowns(currentTime);\r\n\r\n this.combatDetails.currentHitpoints = this.combatDetails.maxHitpoints;\r\n this.combatDetails.currentManapoints = this.combatDetails.maxManapoints;\r\n }\r\n\r\n resetCooldowns(currentTime = 0) {\r\n this.food.filter((food) => food != null).forEach((food) => (food.lastUsed = Number.MIN_SAFE_INTEGER));\r\n this.drinks.filter((drink) => drink != null).forEach((drink) => (drink.lastUsed = Number.MIN_SAFE_INTEGER));\r\n\r\n let haste = this.combatDetails.combatStats.abilityHaste;\r\n\r\n this.abilities\r\n .filter((ability) => ability != null)\r\n .forEach((ability) => {\r\n if (this.isPlayer) {\r\n ability.lastUsed = Number.MIN_SAFE_INTEGER;\r\n } else {\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n ability.lastUsed = currentTime - Math.floor(cooldownDuration * 0.5) + Math.floor(Math.random() * cooldownDuration * 0.5);\r\n }\r\n });\r\n }\r\n\r\n addHitpoints(hitpoints) {\r\n let hitpointsAdded = 0;\r\n\r\n if (this.combatDetails.currentHitpoints >= this.combatDetails.maxHitpoints) {\r\n return hitpointsAdded;\r\n }\r\n\r\n let newHitpoints = Math.min(this.combatDetails.currentHitpoints + hitpoints, this.combatDetails.maxHitpoints);\r\n hitpointsAdded = newHitpoints - this.combatDetails.currentHitpoints;\r\n this.combatDetails.currentHitpoints = newHitpoints;\r\n\r\n return hitpointsAdded;\r\n }\r\n\r\n addManapoints(manapoints) {\r\n let manapointsAdded = 0;\r\n\r\n if (this.combatDetails.currentManapoints >= this.combatDetails.maxManapoints) {\r\n return manapointsAdded;\r\n }\r\n\r\n let newManapoints = Math.min(\r\n this.combatDetails.currentManapoints + manapoints,\r\n this.combatDetails.maxManapoints\r\n );\r\n manapointsAdded = newManapoints - this.combatDetails.currentManapoints;\r\n this.combatDetails.currentManapoints = newManapoints;\r\n\r\n return manapointsAdded;\r\n }\r\n}\r\n\r\nexport default CombatUnit;\r\n","import Buff from \"./buff\";\r\nimport itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nclass Consumable {\r\n constructor(hrid, triggers = null) {\r\n this.hrid = hrid;\r\n\r\n let gameConsumable = itemDetailMap[this.hrid];\r\n if (!gameConsumable) {\r\n throw new Error(\"No consumable found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.cooldownDuration = gameConsumable.consumableDetail.cooldownDuration;\r\n this.hitpointRestore = gameConsumable.consumableDetail.hitpointRestore;\r\n this.manapointRestore = gameConsumable.consumableDetail.manapointRestore;\r\n this.recoveryDuration = gameConsumable.consumableDetail.recoveryDuration;\r\n this.catagoryHrid = gameConsumable.categoryHrid;\r\n\r\n this.buffs = [];\r\n if (gameConsumable.consumableDetail.buffs) {\r\n for (const consumableBuff of gameConsumable.consumableDetail.buffs) {\r\n let buff = new Buff(consumableBuff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameConsumable.consumableDetail.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let consumable = new Consumable(dto.hrid, triggers);\r\n\r\n return consumable;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n let consumableHaste;\r\n if (this.catagoryHrid.includes(\"food\")) {\r\n consumableHaste = source.combatDetails.combatStats.foodHaste\r\n } else {\r\n consumableHaste = source.combatDetails.combatStats.drinkConcentration;\r\n }\r\n let cooldownDuration = this.cooldownDuration;\r\n if (consumableHaste > 0) {\r\n cooldownDuration = cooldownDuration / (1 + consumableHaste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Consumable;\r\n","import itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport enhancementLevelTotalMultiplierTable from \"./data/enhancementLevelTotalBonusMultiplierTable.json\";\r\n\r\nclass Equipment {\r\n constructor(hrid, enhancementLevel) {\r\n this.hrid = hrid;\r\n let gameItem = itemDetailMap[this.hrid];\r\n if (!gameItem) {\r\n throw new Error(\"No equipment found for hrid: \" + this.hrid);\r\n }\r\n this.gameItem = gameItem;\r\n this.enhancementLevel = enhancementLevel;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let equipment = new Equipment(dto.hrid, dto.enhancementLevel);\r\n\r\n return equipment;\r\n }\r\n\r\n getCombatStat(combatStat) {\r\n let multiplier = enhancementLevelTotalMultiplierTable[this.enhancementLevel];\r\n if(this.gameItem.equipmentDetail.combatStats[combatStat]) {\r\n let enhancementBonus = this.gameItem.equipmentDetail.combatEnhancementBonuses[combatStat] || 0;\r\n let stat = this.gameItem.equipmentDetail.combatStats[combatStat] + multiplier * enhancementBonus;\r\n return stat;\r\n }\r\n return 0;\r\n }\r\n\r\n getCombatStyle() {\r\n return this.gameItem.equipmentDetail.combatStats.combatStyleHrids[0];\r\n }\r\n\r\n getDamageType() {\r\n return this.gameItem.equipmentDetail.combatStats.damageType;\r\n }\r\n\r\n getPrimaryTraining() {\r\n return this.gameItem.equipmentDetail.combatStats.primaryTraining;\r\n }\r\n\r\n getFocusTraining(){\r\n return this.gameItem.equipmentDetail.combatStats.focusTraining;\r\n }\r\n}\r\n\r\nexport default Equipment;\r\n","import Buff from \"./buff\";\r\nimport houseRoomDetailMap from \"./data/houseRoomDetailMap.json\";\r\n\r\nclass HouseRoom {\r\n constructor(hrid, level) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameHouseRoom = houseRoomDetailMap[this.hrid];\r\n if (!gameHouseRoom) {\r\n throw new Error(\"No house room found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.buffs = [];\r\n if (gameHouseRoom.actionBuffs) {\r\n for (const actionBuff of gameHouseRoom.actionBuffs) {\r\n let buff = new Buff(actionBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n if (gameHouseRoom.globalBuffs) {\r\n for (const globalBuff of gameHouseRoom.globalBuffs) {\r\n let buff = new Buff(globalBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default HouseRoom;","import Ability from \"./ability\";\r\nimport CombatUnit from \"./combatUnit\";\r\nimport Consumable from \"./consumable\";\r\nimport Equipment from \"./equipment\";\r\nimport HouseRoom from \"./houseRoom\";\r\nimport Achievement from \"./achievement\";\r\n\r\nclass Player extends CombatUnit {\r\n equipment = {\r\n \"/equipment_types/head\": null,\r\n \"/equipment_types/body\": null,\r\n \"/equipment_types/legs\": null,\r\n \"/equipment_types/feet\": null,\r\n \"/equipment_types/hands\": null,\r\n \"/equipment_types/main_hand\": null,\r\n \"/equipment_types/two_hand\": null,\r\n \"/equipment_types/off_hand\": null,\r\n \"/equipment_types/pouch\": null,\r\n \"/equipment_types/back\": null,\r\n };\r\n\r\n constructor() {\r\n super();\r\n\r\n this.isPlayer = true;\r\n this.hrid = \"player\";\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let player = new Player();\r\n\r\n player.staminaLevel = dto.staminaLevel;\r\n player.intelligenceLevel = dto.intelligenceLevel;\r\n player.attackLevel = dto.attackLevel;\r\n player.meleeLevel = dto.meleeLevel;\r\n player.defenseLevel = dto.defenseLevel;\r\n player.rangedLevel = dto.rangedLevel;\r\n player.magicLevel = dto.magicLevel;\r\n\r\n player.hrid = dto.hrid;\r\n\r\n for (const [key, value] of Object.entries(dto.equipment)) {\r\n player.equipment[key] = value ? Equipment.createFromDTO(value) : null;\r\n }\r\n\r\n player.food = dto.food.map((food) => (food ? Consumable.createFromDTO(food) : null));\r\n player.drinks = dto.drinks.map((drink) => (drink ? Consumable.createFromDTO(drink) : null));\r\n player.abilities = dto.abilities.map((ability) => (ability ? Ability.createFromDTO(ability) : null));\r\n Object.entries(dto.houseRooms).forEach(houseRoom => {\r\n if (houseRoom[1] > 0) {\r\n player.houseRooms.push(new HouseRoom(houseRoom[0], houseRoom[1]))\r\n }\r\n });\r\n\r\n player.achievements = new Achievement(dto.achievements);\r\n\r\n player.debuffOnLevelGap = dto.debuffOnLevelGap;\r\n\r\n return player;\r\n }\r\n\r\n updateCombatDetails() {\r\n if (this.equipment[\"/equipment_types/main_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/main_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/main_hand\"].getPrimaryTraining();\r\n } else if (this.equipment[\"/equipment_types/two_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/two_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/two_hand\"].getPrimaryTraining();\r\n } else {\r\n this.combatDetails.combatStats.combatStyleHrid = \"/combat_styles/smash\";\r\n this.combatDetails.combatStats.damageType = \"/damage_types/physical\";\r\n this.combatDetails.combatStats.attackInterval = 3000000000;\r\n this.combatDetails.combatStats.primaryTraining = \"/skills/melee\";\r\n }\r\n\r\n if (this.equipment[\"/equipment_types/charm\"]) {\r\n this.combatDetails.combatStats.focusTraining = this.equipment[\"/equipment_types/charm\"].getFocusTraining();\r\n } else {\r\n this.combatDetails.combatStats.focusTraining = \"\";\r\n }\r\n\r\n [\r\n \"stabAccuracy\",\r\n \"slashAccuracy\",\r\n \"smashAccuracy\",\r\n \"rangedAccuracy\",\r\n \"magicAccuracy\",\r\n \"stabDamage\",\r\n \"slashDamage\",\r\n \"smashDamage\",\r\n \"rangedDamage\",\r\n \"magicDamage\",\r\n \"defensiveDamage\",\r\n \"taskDamage\",\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"stabEvasion\",\r\n \"slashEvasion\",\r\n \"smashEvasion\",\r\n \"rangedEvasion\",\r\n \"magicEvasion\",\r\n \"armor\",\r\n \"waterResistance\",\r\n \"natureResistance\",\r\n \"fireResistance\",\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"combatDropRate\",\r\n \"combatRareFind\",\r\n \"combatDropQuantity\",\r\n \"combatExperience\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"abilityHaste\",\r\n \"tenacity\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"threat\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"foodHaste\",\r\n \"drinkConcentration\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\",\r\n \"retaliation\"\r\n ].forEach((stat) => {\r\n this.combatDetails.combatStats[stat] = Object.values(this.equipment)\r\n .filter((equipment) => equipment != null)\r\n .map((equipment) => equipment.getCombatStat(stat))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n });\r\n\r\n if (this.equipment[\"/equipment_types/pouch\"]) {\r\n this.combatDetails.combatStats.foodSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"foodSlots\");\r\n this.combatDetails.combatStats.drinkSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"drinkSlots\");\r\n } else {\r\n this.combatDetails.combatStats.foodSlots = 1;\r\n this.combatDetails.combatStats.drinkSlots = 1;\r\n }\r\n\r\n super.updateCombatDetails();\r\n }\r\n}\r\n\r\nexport default Player;\r\n","import combatTriggerDependencyDetailMap from \"./data/combatTriggerDependencyDetailMap.json\";\r\n\r\nclass Trigger {\r\n constructor(dependencyHrid, conditionHrid, comparatorHrid, value = 0) {\r\n this.dependencyHrid = dependencyHrid;\r\n this.conditionHrid = conditionHrid;\r\n this.comparatorHrid = comparatorHrid;\r\n this.value = value;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let trigger = new Trigger(dto.dependencyHrid, dto.conditionHrid, dto.comparatorHrid, dto.value);\r\n\r\n return trigger;\r\n }\r\n\r\n isActive(source, target, friendlies, enemies, currentTime) {\r\n if (combatTriggerDependencyDetailMap[this.dependencyHrid].isSingleTarget) {\r\n return this.isActiveSingleTarget(source, target, currentTime);\r\n } else {\r\n return this.isActiveMultiTarget(friendlies, enemies, currentTime);\r\n }\r\n }\r\n\r\n isActiveSingleTarget(source, target, currentTime) {\r\n let dependencyValue;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/self\":\r\n dependencyValue = this.getDependencyValue(source, currentTime);\r\n break;\r\n case \"/combat_trigger_dependencies/targeted_enemy\":\r\n if (!target) {\r\n return false;\r\n }\r\n dependencyValue = this.getDependencyValue(target, currentTime);\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n isActiveMultiTarget(friendlies, enemies, currentTime) {\r\n let dependency;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/all_allies\":\r\n dependency = friendlies;\r\n break;\r\n case \"/combat_trigger_dependencies/all_enemies\":\r\n if (!enemies) {\r\n return false;\r\n }\r\n dependency = enemies;\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n let dependencyValue;\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/number_of_active_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints > 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/number_of_dead_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints <= 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/lowest_hp_percentage\":\r\n dependencyValue = dependency.reduce((prev, curr) => {\r\n let currentHpPercentage = curr.combatDetails.currentHitpoints / curr.combatDetails.maxHitpoints;\r\n return currentHpPercentage < prev ? currentHpPercentage : prev;\r\n }, 2) * 100;\r\n break;\r\n default:\r\n dependencyValue = dependency\r\n .map((unit) => this.getDependencyValue(unit, currentTime))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n break;\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n getDependencyValue(source, currentTime) {\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/berserk\":\r\n case \"/combat_trigger_conditions/frenzy\":\r\n case \"/combat_trigger_conditions/precision\":\r\n case \"/combat_trigger_conditions/vampirism\":\r\n case \"/combat_trigger_conditions/attack_coffee\":\r\n case \"/combat_trigger_conditions/defense_coffee\":\r\n case \"/combat_trigger_conditions/lucky_coffee\":\r\n case \"/combat_trigger_conditions/magic_coffee\":\r\n case \"/combat_trigger_conditions/melee_coffee\":\r\n case \"/combat_trigger_conditions/ranged_coffee\":\r\n case \"/combat_trigger_conditions/swiftness_coffee\":\r\n case \"/combat_trigger_conditions/wisdom_coffee\":\r\n case \"/combat_trigger_conditions/ice_spear\":\r\n case \"/combat_trigger_conditions/puncture\":\r\n case \"/combat_trigger_conditions/frost_surge\":\r\n case \"/combat_trigger_conditions/elusiveness\":\r\n case \"/combat_trigger_conditions/channeling_coffee\":\r\n case \"/combat_trigger_conditions/fierce_aura\":\r\n case \"/combat_trigger_conditions/invincible_armor\":\r\n case \"/combat_trigger_conditions/invincible_fire_resistance\":\r\n case \"/combat_trigger_conditions/invincible_nature_resistance\":\r\n case \"/combat_trigger_conditions/invincible_water_resistance\":\r\n case \"/combat_trigger_conditions/provoke\":\r\n case \"/combat_trigger_conditions/taunt\":\r\n case \"/combat_trigger_conditions/crippling_slash\":\r\n case \"/combat_trigger_conditions/mana_spring\":\r\n case \"/combat_trigger_conditions/retribution\":\r\n case \"/combat_trigger_conditions/fracturing_impact\":\r\n case \"/combat_trigger_conditions/maim\":\r\n case \"/combat_trigger_conditions/curse\":\r\n case \"/combat_trigger_conditions/weaken\":\r\n let buffHrid = \"/buff_uniques\";\r\n buffHrid += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n return source.combatBuffs[buffHrid];\r\n case \"/combat_trigger_conditions/critical_aura\":\r\n case \"/combat_trigger_conditions/critical_coffee\":\r\n case \"/combat_trigger_conditions/intelligence_coffee\":\r\n case \"/combat_trigger_conditions/stamina_coffee\":\r\n case \"/combat_trigger_conditions/elemental_affinity\":\r\n case \"/combat_trigger_conditions/fury\":\r\n case \"/combat_trigger_conditions/guardian_aura\":\r\n case \"/combat_trigger_conditions/insanity\":\r\n case \"/combat_trigger_conditions/spike_shell\":\r\n case \"/combat_trigger_conditions/toxic_pollen\":\r\n case \"/combat_trigger_conditions/invincible\":\r\n case \"/combat_trigger_conditions/mystic_aura\":\r\n case \"/combat_trigger_conditions/pestilent_shot\":\r\n case \"/combat_trigger_conditions/smoke_burst\":\r\n case \"/combat_trigger_conditions/speed_aura\":\r\n case \"/combat_trigger_conditions/toughness\":\r\n case \"/combat_trigger_conditions/enrage\":\r\n let buffPrefix = \"/buff_uniques\";\r\n buffPrefix += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n let buffs = Object.keys(source.combatBuffs).filter(buff => buff.startsWith(buffPrefix));\r\n return source.combatBuffs[buffs?.[0]];\r\n case \"/combat_trigger_conditions/current_hp\":\r\n return source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/current_mp\":\r\n return source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/missing_hp\":\r\n return source.combatDetails.maxHitpoints - source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/missing_mp\":\r\n return source.combatDetails.maxManapoints - source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/stun_status\":\r\n // Replicate the game's behaviour of \"stun status active\" triggers activating\r\n // immediately after the stun has worn off\r\n return source.isStunned || source.stunExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/blind_status\":\r\n return source.isBlinded || source.blindExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/silence_status\":\r\n return source.isSilenced || source.silenceExpireTime == currentTime;\r\n default:\r\n throw new Error(\"Unknown conditionHrid in trigger: \" + this.conditionHrid);\r\n }\r\n }\r\n\r\n compareValue(dependencyValue) {\r\n switch (this.comparatorHrid) {\r\n case \"/combat_trigger_comparators/greater_than_equal\":\r\n return dependencyValue >= this.value;\r\n case \"/combat_trigger_comparators/less_than_equal\":\r\n return dependencyValue <= this.value;\r\n case \"/combat_trigger_comparators/is_active\":\r\n return !!dependencyValue;\r\n case \"/combat_trigger_comparators/is_inactive\":\r\n return !dependencyValue;\r\n default:\r\n throw new Error(\"Unknown comparatorHrid in trigger: \" + this.comparatorHrid);\r\n }\r\n }\r\n}\r\n\r\nexport default Trigger;\r\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","// This function allow to reference async chunks\n__webpack_require__.u = (chunkId) => {\n\t// return url for filenames based on template\n\treturn \"\" + chunkId + \".bundle.js\";\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","var scriptUrl;\nif (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + \"\";\nvar document = __webpack_require__.g.document;\nif (!scriptUrl && document) {\n\tif (document.currentScript)\n\t\tscriptUrl = document.currentScript.src;\n\tif (!scriptUrl) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tif(scripts.length) {\n\t\t\tvar i = scripts.length - 1;\n\t\t\twhile (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;\n\t\t}\n\t}\n}\n// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration\n// or pass an empty string (\"\") and set the __webpack_public_path__ variable from your code to use your own logic.\nif (!scriptUrl) throw new Error(\"Automatic publicPath is not supported in this browser\");\nscriptUrl = scriptUrl.replace(/#.*$/, \"\").replace(/\\?.*$/, \"\").replace(/\\/[^\\/]+$/, \"/\");\n__webpack_require__.p = scriptUrl;","__webpack_require__.b = document.baseURI || self.location.href;\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t\"main\": 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n// no on chunks loaded\n\n// no jsonp function","import Equipment from \"./combatsimulator/equipment.js\";\r\nimport Player from \"./combatsimulator/player.js\";\r\nimport abilityDetailMap from \"./combatsimulator/data/abilityDetailMap.json\";\r\nimport itemDetailMap from \"./combatsimulator/data/itemDetailMap.json\";\r\nimport houseRoomDetailMap from \"./combatsimulator/data/houseRoomDetailMap.json\";\r\nimport Ability from \"./combatsimulator/ability.js\";\r\nimport Consumable from \"./combatsimulator/consumable.js\";\r\nimport HouseRoom from \"./combatsimulator/houseRoom\"\r\nimport combatTriggerDependencyDetailMap from \"./combatsimulator/data/combatTriggerDependencyDetailMap.json\";\r\nimport combatTriggerConditionDetailMap from \"./combatsimulator/data/combatTriggerConditionDetailMap.json\";\r\nimport combatTriggerComparatorDetailMap from \"./combatsimulator/data/combatTriggerComparatorDetailMap.json\";\r\nimport abilitySlotsLevelRequirementList from \"./combatsimulator/data/abilitySlotsLevelRequirementList.json\";\r\nimport actionDetailMap from \"./combatsimulator/data/actionDetailMap.json\";\r\nimport combatMonsterDetailMap from \"./combatsimulator/data/combatMonsterDetailMap.json\";\r\nimport damageTypeDetailMap from \"./combatsimulator/data/damageTypeDetailMap.json\";\r\nimport combatStyleDetailMap from \"./combatsimulator/data/combatStyleDetailMap.json\";\r\nimport openableLootDropMap from \"./combatsimulator/data/openableLootDropMap.json\";\r\nimport achievementTierMap from \"./combatsimulator/data/achievementTierDetailMap.json\"\r\nimport achievementDetailMap from \"./combatsimulator/data/achievementDetailMap.json\"\r\n\r\nimport patchNote from \"../patchNote.json\";\r\n\r\nconst ONE_SECOND = 1e9;\r\nconst ONE_HOUR = 60 * 60 * ONE_SECOND;\r\n\r\nlet buttonStartSimulation = document.getElementById(\"buttonStartSimulation\");\r\nlet buttonStopSimulation = document.getElementById(\"buttonStopSimulation\");\r\nlet progressbar = document.getElementById(\"simulationProgressBar\");\r\nlet simStartTime = 0;\r\n\r\nlet worker = new Worker(new URL(\"worker.js\", import.meta.url));\r\nlet multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n\r\n\r\n\r\nlet player = new Player();\r\nlet selectedPlayers = [];\r\nlet food = [null, null, null];\r\nlet drinks = [null, null, null];\r\nlet abilities = [null, null, null, null];\r\nlet triggerMap = {};\r\nlet modalTriggers = [];\r\nlet currentSimResults = {};\r\n\r\nlet currentPlayerTabId = '1';\r\nlet playerDataMap = {\r\n \"1\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},achievements:{}}\",\r\n \"2\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},achievements:{}}\",\r\n \"3\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},achievements:{}}\",\r\n \"4\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},achievements:{}}\",\r\n \"5\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},achievements:{}}\"\r\n};\r\nwindow.revenue = 0;\r\nwindow.noRngRevenue = 0;\r\nwindow.expenses = 0;\r\nwindow.profit = 0;\r\nwindow.noRngProfit = 0;\r\n\r\n// #region Worker\r\n\r\nfunction onWorkerMessage(event) {\r\n switch (event.data.type) {\r\n case \"simulation_result\":\r\n progressbar.style.width = \"100%\";\r\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n //console.log(\"SIM RESULTS: \", event.data.simResult);\r\n showSimulationResult(event.data.simResult);\r\n updateContent();\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n document.getElementById('buttonShowAllSimData').style.display = 'none';\r\n break;\r\n case \"simulation_progress\":\r\n let progress = Math.floor(100 * event.data.progress);\r\n progressbar.style.width = progress + \"%\";\r\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n break;\r\n case \"simulation_error\":\r\n showErrorModal(event.data.error.toString());\r\n break;\r\n }\r\n};\r\n\r\nfunction onMultiWorkerMessage(event) {\r\n switch (event.data.type) {\r\n case \"simulation_result_allZones\":\r\n progressbar.style.width = \"100%\";\r\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n showAllSimulationResults(event.data.simResults);\r\n updateContent();\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n document.getElementById('buttonShowAllSimData').style.display = 'block';\r\n break;\r\n case \"simulation_progress\":\r\n let progress = Math.floor(100 * event.data.progress);\r\n progressbar.style.width = progress + \"%\";\r\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n break;\r\n case \"simulation_error\":\r\n showErrorModal(event.data.error.toString());\r\n break;\r\n }\r\n};\r\n\r\n// #endregion\r\n\r\n// #region Equipment\r\n\r\nfunction initEquipmentSection() {\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n initEquipmentSelect(type);\r\n initEnhancementLevelInput(type);\r\n });\r\n}\r\n\r\nfunction initEquipmentSelect(equipmentType) {\r\n let selectId = \"selectEquipment_\";\r\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\r\n selectId += \"weapon\";\r\n } else {\r\n selectId += equipmentType;\r\n }\r\n let selectElement = document.getElementById(selectId);\r\n\r\n let gameEquipment = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/equipment\")\r\n .filter((item) => item.equipmentDetail.type == \"/equipment_types/\" + equipmentType)\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const equipment of Object.values(gameEquipment)) {\r\n let opt = new Option(equipment.name, equipment.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + equipment.hrid);\r\n selectElement.add(opt);\r\n }\r\n\r\n selectElement.addEventListener(\"change\", (event) => {\r\n equipmentSelectHandler(event, equipmentType);\r\n });\r\n}\r\n\r\nfunction initHouseRoomsModal() {\r\n let houseRoomsList = document.getElementById(\"houseRoomsList\");\r\n let newChildren = [];\r\n let houseRooms = Object.values(houseRoomDetailMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n player.houseRooms = {};\r\n\r\n for (const room of Object.values(houseRooms)) {\r\n player.houseRooms[room.hrid] = 0;\r\n\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let nameCol = createElement(\"div\", \"col-md-4 offset-md-3 align-self-center\", room.name);\r\n nameCol.setAttribute(\"data-i18n\", \"houseRoomNames.\" + room.hrid);\r\n row.appendChild(nameCol);\r\n\r\n let levelCol = createElement(\"div\", \"col-md-2\");\r\n let levelInput = createHouseInput(room.hrid);\r\n\r\n levelInput.addEventListener(\"input\", function (e) {\r\n let inputValue = e.target.value;\r\n const hrid = e.target.dataset.houseHrid;\r\n player.houseRooms[hrid] = parseInt(inputValue);\r\n });\r\n\r\n levelCol.appendChild(levelInput);\r\n row.appendChild(levelCol);\r\n\r\n newChildren.push(row);\r\n }\r\n\r\n houseRoomsList.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction createHouseInput(hrid) {\r\n let levelInput = document.createElement(\"input\");\r\n levelInput.className = \"form-control\";\r\n levelInput.type = \"number\";\r\n levelInput.placeholder = 0;\r\n levelInput.min = 0;\r\n levelInput.max = 8;\r\n levelInput.step = 1;\r\n levelInput.dataset.houseHrid = hrid;\r\n\r\n return levelInput;\r\n}\r\n\r\nfunction refreshAchievementStatics() {\r\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n for(const tier of Object.values(tierMap)) {\r\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\r\n const done = Array.from(checks).filter(cb => cb.checked).length;\r\n const total = checks.length;\r\n\r\n const stat = document.getElementById(`AchTier${tier.sortIndex}Statics`);\r\n stat.innerText = `(${done}/${total})`;\r\n if (done == total) {\r\n // set to green\r\n stat.classList.remove(\"text-secondary\");\r\n stat.classList.add(\"text-success\");\r\n } else {\r\n // set to secondary\r\n stat.classList.remove(\"text-success\");\r\n stat.classList.add(\"text-secondary\");\r\n }\r\n }\r\n}\r\n\r\nfunction initAchievementsModal(){\r\n let achievementsList = document.getElementById(\"achievementsList\");\r\n let newChildren = [];\r\n player.achievements = {};\r\n\r\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n for(const tier of Object.values(tierMap)) {\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid).sort((a, b) => a.sortIndex - b.sortIndex);\r\n let detailMapCount = detailMap.length;\r\n if (detailMapCount <= 0) continue;\r\n\r\n let card = createElement(\"div\", \"card\");\r\n let cardHeader = createElement(\"div\", \"card-header d-flex align-items-center\");\r\n\r\n let cardTitle = createElement(\"a\", \"btn\", tier.name);\r\n cardTitle.setAttribute(\"data-bs-toggle\",\"collapse\");\r\n cardTitle.setAttribute(\"href\", `#AchTier${tier.sortIndex}`);\r\n cardTitle.setAttribute(\"data-i18n\", \"achievementTierNames.\"+tier.hrid);\r\n cardHeader.appendChild(cardTitle);\r\n\r\n let bufDesc = createElement(\"div\", \"small text-secondary\");\r\n let buffName = createElement(\"i\", \"\");\r\n buffName.setAttribute(\"data-i18n\", \"buffTypeNames.\"+tier[\"buff\"].typeHrid);\r\n bufDesc.appendChild(buffName);\r\n let buffValue = createElement(\"i\", \"\");\r\n buffValue.innerText = \":+\" + parseFloat(tier[\"buff\"].ratioBoost==0?tier[\"buff\"].flatBoost:tier[\"buff\"].ratioBoost)*100 + \"%\";\r\n bufDesc.appendChild(buffValue);\r\n cardHeader.appendChild(bufDesc);\r\n\r\n let cardStatics = createElement(\"div\", \"ms-auto btn\", `(0/${detailMapCount})`);\r\n cardStatics.id = `AchTier${tier.sortIndex}Statics`;\r\n cardStatics.dataset.checked = \"true\";\r\n cardStatics.addEventListener(\"click\", function (e) {\r\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\r\n for (const check of checks) {\r\n check.checked = cardStatics.dataset.checked == \"true\";\r\n const hrid = check.dataset.achievementHrid;\r\n player.achievements[hrid] = check.checked;\r\n }\r\n cardStatics.dataset.checked = cardStatics.dataset.checked == \"true\" ? \"false\" : \"true\";\r\n refreshAchievementStatics();\r\n });\r\n cardHeader.appendChild(cardStatics);\r\n\r\n card.appendChild(cardHeader);\r\n\r\n let cardMain = createElement(\"div\", \"collapse\");\r\n cardMain.id = `AchTier${tier.sortIndex}`;\r\n let cardBody = createElement(\"div\", \"card-body\");\r\n\r\n for (const detail of Object.values(detailMap)) {\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let formCheck = createElement(\"div\", \"form-check\");\r\n let input = createElement(\"input\", \"form-check-input\");\r\n input.setAttribute(\"type\", \"checkbox\");\r\n input.setAttribute(\"data-tier\", tier.sortIndex);\r\n input.id = `AchDetail${detail.sortIndex}`;\r\n input.dataset.achievementHrid = detail.hrid;\r\n input.addEventListener(\"change\", function (e) {\r\n const hrid = e.target.dataset.achievementHrid;\r\n player.achievements[hrid] = e.target.checked;\r\n\r\n refreshAchievementStatics();\r\n });\r\n formCheck.appendChild(input);\r\n\r\n let name = createElement(\"label\", \"form-check-label\", detail.name);\r\n name.setAttribute(\"data-i18n\", \"achievementNames.\" + detail.hrid);\r\n name.setAttribute(\"for\", `AchDetail${detail.sortIndex}`);\r\n formCheck.appendChild(name);\r\n row.appendChild(formCheck);\r\n cardBody.appendChild(row);\r\n }\r\n cardMain.appendChild(cardBody);\r\n card.appendChild(cardMain);\r\n\r\n newChildren.push(card);\r\n }\r\n\r\n achievementsList.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction initEnhancementLevelInput(equipmentType) {\r\n let inputId = \"inputEquipmentEnhancementLevel_\";\r\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\r\n inputId += \"weapon\";\r\n } else {\r\n inputId += equipmentType;\r\n }\r\n\r\n let inputElement = document.getElementById(inputId);\r\n inputElement.value = 0;\r\n inputElement.addEventListener(\"change\", enhancementLevelInputHandler);\r\n}\r\n\r\nfunction equipmentSelectHandler(event, type) {\r\n let equipmentType = \"/equipment_types/\" + type;\r\n\r\n if (!event.target.value) {\r\n updateEquipmentState();\r\n updateUI();\r\n return;\r\n }\r\n\r\n let gameItem = itemDetailMap[event.target.value];\r\n\r\n // Weapon select has two handlers because of mainhand and twohand weapons. Ignore the handler with the wrong type\r\n if (gameItem.equipmentDetail.type != equipmentType) {\r\n return;\r\n }\r\n\r\n if (type == \"two_hand\") {\r\n document.getElementById(\"selectEquipment_off_hand\").value = \"\";\r\n document.getElementById(\"inputEquipmentEnhancementLevel_off_hand\").value = 0;\r\n }\r\n if (type == \"off_hand\" && player.equipment[\"/equipment_types/two_hand\"]) {\r\n document.getElementById(\"selectEquipment_weapon\").value = \"\";\r\n document.getElementById(\"inputEquipmentEnhancementLevel_weapon\").value = 0;\r\n }\r\n\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\nfunction enhancementLevelInputHandler() {\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\nfunction updateEquipmentState() {\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentType = \"/equipment_types/\" + type;\r\n let selectType = type;\r\n if (type == \"main_hand\" || type == \"two_hand\") {\r\n selectType = \"weapon\";\r\n }\r\n\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + selectType);\r\n let equipmentHrid = equipmentSelect.value;\r\n\r\n if (!equipmentHrid) {\r\n player.equipment[equipmentType] = null;\r\n return;\r\n }\r\n\r\n let gameItem = itemDetailMap[equipmentHrid];\r\n\r\n // Clear old weapon if a weapon of a different type is equipped\r\n if (gameItem.equipmentDetail.type != equipmentType) {\r\n player.equipment[equipmentType] = null;\r\n return;\r\n }\r\n\r\n let enhancementLevel = Number(document.getElementById(\"inputEquipmentEnhancementLevel_\" + selectType).value);\r\n player.equipment[equipmentType] = new Equipment(gameItem.hrid, enhancementLevel);\r\n });\r\n}\r\n\r\ndocument.getElementById(\"selectEquipment_set\").onchange = changeEquipmentSetListener;\r\n\r\nfunction changeEquipmentSetListener() {\r\n let value = this.value\r\n let optgroupType = this.options[this.selectedIndex].parentNode.label;\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\"].forEach((type) => {\r\n let selectType = type;\r\n\r\n let currentEquipment = document.getElementById(\"selectEquipment_\" + selectType);\r\n if (type === \"feet\") {\r\n type = \"_boots\";\r\n }\r\n if (type === \"hands\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_bracers\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_gloves\";\r\n } else {\r\n type = \"_gauntlets\";\r\n }\r\n }\r\n if (type === \"head\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_hood\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_hat\";\r\n } else {\r\n type = \"_helmet\";\r\n }\r\n }\r\n if (type === \"legs\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_chaps\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_robe_bottoms\";\r\n } else {\r\n type = \"_plate_legs\";\r\n }\r\n }\r\n if (type === \"body\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_tunic\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_robe_top\";\r\n } else {\r\n type = \"_plate_body\";\r\n }\r\n }\r\n currentEquipment.value = \"/items/\" + value.toLowerCase() + type;\r\n });\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Combat Stats\r\n\r\nfunction updateCombatStatsUI() {\r\n player.updateCombatDetails();\r\n\r\n let combatStyleElement = document.getElementById(\"combatStat_combatStyleHrid\");\r\n let combatStyle = player.combatDetails.combatStats.combatStyleHrid;\r\n combatStyleElement.setAttribute(\"data-i18n\", \"combatStyleNames.\" + combatStyle);\r\n combatStyleElement.innerHTML = combatStyleDetailMap[combatStyle].name;\r\n\r\n let damageTypeElement = document.getElementById(\"combatStat_damageType\");\r\n let damageType = damageTypeDetailMap[player.combatDetails.combatStats.damageType];\r\n damageTypeElement.setAttribute(\"data-i18n\", \"damageTypeNames.\" + damageType.hrid);\r\n damageTypeElement.innerHTML = damageType.name;\r\n\r\n let attackIntervalElement = document.getElementById(\"combatStat_attackInterval\");\r\n attackIntervalElement.innerHTML = (player.combatDetails.combatStats.attackInterval / 1e9).toLocaleString() + \"s\";\r\n\r\n let primaryTrainingElement = document.getElementById(\"combatStat_primaryTraining\");\r\n let primaryTraining = player.combatDetails.combatStats.primaryTraining;\r\n primaryTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + primaryTraining);\r\n primaryTrainingElement.innerHTML = primaryTraining;\r\n\r\n let focusTrainingElement = document.getElementById(\"combatStat_focusTraining\");\r\n let focusTraining = player.combatDetails.combatStats.focusTraining;\r\n if (focusTraining) {\r\n focusTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + focusTraining);\r\n } else {\r\n focusTrainingElement.setAttribute(\"data-i18n\", \"characterSelectPage.slots.empty\");\r\n }\r\n focusTrainingElement.innerHTML = focusTraining;\r\n\r\n [\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"stabAccuracyRating\",\r\n \"stabMaxDamage\",\r\n \"slashAccuracyRating\",\r\n \"slashMaxDamage\",\r\n \"smashAccuracyRating\",\r\n \"smashMaxDamage\",\r\n \"rangedAccuracyRating\",\r\n \"rangedMaxDamage\",\r\n \"magicAccuracyRating\",\r\n \"magicMaxDamage\",\r\n \"defensiveMaxDamage\",\r\n \"stabEvasionRating\",\r\n \"slashEvasionRating\",\r\n \"smashEvasionRating\",\r\n \"rangedEvasionRating\",\r\n \"magicEvasionRating\",\r\n \"totalArmor\",\r\n \"totalWaterResistance\",\r\n \"totalNatureResistance\",\r\n \"totalFireResistance\",\r\n \"totalThreat\"\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n element.innerHTML = Math.floor(player.combatDetails[stat]);\r\n });\r\n\r\n [\r\n \"abilityHaste\",\r\n \"tenacity\"\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n element.innerHTML = Math.floor(player.combatDetails.combatStats[stat]);\r\n });\r\n\r\n [\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"combatExperience\",\r\n \"taskDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"drinkConcentration\",\r\n \"foodHaste\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\"\r\n\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n let value = (100 * player.combatDetails.combatStats[stat]).toLocaleString([], {\r\n minimumFractionDigits: 0,\r\n maximumFractionDigits: 4,\r\n });\r\n element.innerHTML = value + \"%\";\r\n });\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Level\r\n\r\nfunction initLevelSection() {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n levelInput.value = 1;\r\n levelInput.addEventListener(\"change\", levelInputHandler);\r\n });\r\n}\r\n\r\nfunction levelInputHandler() {\r\n updateLevels();\r\n updateUI();\r\n}\r\n\r\nfunction updateLevels() {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n player[skill + \"Level\"] = Number(levelInput.value);\r\n });\r\n updateCombatLevel();\r\n}\r\n\r\nfunction calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel) {\r\n return Math.floor(\r\n 0.1 * (staminaLevel + intelligenceLevel + attackLevel + defenseLevel + Math.max(meleeLevel, rangedLevel, magicLevel))\r\n + 0.5 * Math.max(attackLevel, defenseLevel, meleeLevel, rangedLevel, magicLevel)\r\n );\r\n}\r\n\r\n\r\nfunction updateCombatLevel() {\r\n let staminaLevel = player[\"staminaLevel\"];\r\n let intelligenceLevel = player[\"intelligenceLevel\"];\r\n let defenseLevel = player[\"defenseLevel\"];\r\n let attackLevel = player[\"attackLevel\"];\r\n let meleeLevel = player[\"meleeLevel\"];\r\n let rangedLevel = player[\"rangedLevel\"];\r\n let magicLevel = player[\"magicLevel\"];\r\n\r\n let levelInput = document.getElementById(\"inputLevel_combat\");\r\n levelInput.value = calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel);;\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Food\r\n\r\nfunction initFoodSection() {\r\n for (let i = 0; i < 3; i++) {\r\n let element = document.getElementById(\"selectFood_\" + i);\r\n\r\n let gameFoods = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/food\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const food of Object.values(gameFoods)) {\r\n let opt = new Option(food.name, food.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + food.hrid);\r\n element.add(opt);\r\n }\r\n\r\n element.addEventListener(\"change\", foodSelectHandler);\r\n }\r\n}\r\n\r\nfunction foodSelectHandler() {\r\n updateFoodState();\r\n updateUI();\r\n}\r\n\r\nfunction updateFoodState() {\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n food[i] = foodSelect.value;\r\n if (food[i] && !triggerMap[food[i]]) {\r\n let gameItem = itemDetailMap[food[i]];\r\n triggerMap[food[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateFoodUI() {\r\n for (let i = 0; i < 3; i++) {\r\n let selectElement = document.getElementById(\"selectFood_\" + i);\r\n let triggerButton = document.getElementById(\"buttonFoodTrigger_\" + i);\r\n\r\n selectElement.disabled = i >= player.combatDetails.combatStats.foodSlots;\r\n triggerButton.disabled = i >= player.combatDetails.combatStats.foodSlots || !food[i];\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Drinks\r\n\r\nfunction initDrinksSection() {\r\n for (let i = 0; i < 3; i++) {\r\n let element = document.getElementById(\"selectDrink_\" + i);\r\n\r\n let gameDrinks = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/drink\")\r\n .filter((item) => item.consumableDetail.usableInActionTypeMap[\"/action_types/combat\"])\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const drink of Object.values(gameDrinks)) {\r\n let opt = new Option(drink.name, drink.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + drink.hrid);\r\n element.add(opt);\r\n }\r\n\r\n element.addEventListener(\"change\", drinkSelectHandler);\r\n }\r\n}\r\n\r\nfunction drinkSelectHandler() {\r\n updateDrinksState();\r\n updateDrinksUI();\r\n}\r\n\r\nfunction updateDrinksState() {\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n drinks[i] = drinkSelect.value;\r\n if (drinks[i] && !triggerMap[drinks[i]]) {\r\n let gameItem = itemDetailMap[drinks[i]];\r\n triggerMap[drinks[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateDrinksUI() {\r\n for (let i = 0; i < 3; i++) {\r\n let selectElement = document.getElementById(\"selectDrink_\" + i);\r\n let triggerButton = document.getElementById(\"buttonDrinkTrigger_\" + i);\r\n\r\n selectElement.disabled = i >= player.combatDetails.combatStats.drinkSlots;\r\n triggerButton.disabled = i >= player.combatDetails.combatStats.drinkSlots || !drinks[i];\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Abilities\r\n\r\nfunction initAbilitiesSection() {\r\n for (let i = 0; i < 5; i++) {\r\n let selectElement = document.getElementById(\"selectAbility_\" + i);\r\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\r\n\r\n inputElement.value = 1;\r\n\r\n let gameAbilities;\r\n if (i == 0) {\r\n gameAbilities = Object.values(abilityDetailMap).filter(x => x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\r\n } else {\r\n gameAbilities = Object.values(abilityDetailMap).filter(x => !x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\r\n }\r\n\r\n\r\n for (const ability of Object.values(gameAbilities)) {\r\n let opt = new Option(ability.name, ability.hrid);\r\n opt.setAttribute(\"data-i18n\", \"abilityNames.\" + ability.hrid);\r\n selectElement.add(opt);\r\n }\r\n\r\n selectElement.addEventListener(\"change\", abilitySelectHandler);\r\n }\r\n}\r\n\r\nfunction abilitySelectHandler() {\r\n updateAbilityState();\r\n updateAbilityUI();\r\n}\r\n\r\nfunction updateAbilityState() {\r\n for (let i = 0; i < 5; i++) {\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\r\n abilities[i] = abilitySelect.value;\r\n if (abilities[i] && !triggerMap[abilities[i]]) {\r\n let gameAbility = abilityDetailMap[abilities[i]];\r\n triggerMap[abilities[i]] = structuredClone(gameAbility.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateAbilityUI() {\r\n for (let i = 0; i < 5; i++) {\r\n let selectElement = document.getElementById(\"selectAbility_\" + i);\r\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let triggerButton = document.getElementById(\"buttonAbilityTrigger_\" + i);\r\n\r\n selectElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\r\n inputElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\r\n triggerButton.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1] || !abilities[i];\r\n let moveUpButton = document.getElementById(\"selectAbilityMoveUp_\" + i);\r\n moveUpButton.onclick = () => swapAbilityOrder(i, -1);\r\n }\r\n}\r\n\r\nfunction swapAbilityOrder(abilityIndex, step) {\r\n const swapIndex = abilityIndex + step;\r\n if (swapIndex < 0 || swapIndex > 4) {\r\n return;\r\n }\r\n\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilityIndex);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilityIndex);\r\n\r\n const tempAbility = abilities[abilityIndex];\r\n abilities[abilityIndex] = abilities[swapIndex];\r\n abilities[swapIndex] = tempAbility;\r\n\r\n const tempLevel = abilityLevelInput.value;\r\n abilityLevelInput.value = document.getElementById(\"inputAbilityLevel_\" + swapIndex).value;\r\n document.getElementById(\"inputAbilityLevel_\" + swapIndex).value = tempLevel;\r\n\r\n abilitySelect.value = document.getElementById(\"selectAbility_\" + (swapIndex)).value;\r\n document.getElementById(\"selectAbility_\" + swapIndex).value = abilities[swapIndex];\r\n\r\n updateAbilityState();\r\n updateAbilityUI();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Trigger\r\n\r\nfunction initTriggerModal() {\r\n let modal = document.getElementById(\"triggerModal\");\r\n modal.addEventListener(\"show.bs.modal\", (event) => triggerModalShownHandler(event));\r\n\r\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\r\n triggerSaveButton.addEventListener(\"click\", (event) => triggerModalSaveHandler(event));\r\n\r\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\r\n triggerAddButton.addEventListener(\"click\", (event) => triggerAddButtonHandler(event));\r\n\r\n let triggerDefaultButton = document.getElementById(\"buttonDefaultTrigger\");\r\n triggerDefaultButton.addEventListener(\"click\", (event) => triggerDefaultButtonHandler(event));\r\n\r\n for (let i = 0; i < 4; i++) {\r\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\r\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\r\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\r\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\r\n let triggerRemoveButton = document.getElementById(\"buttonRemoveTrigger_\" + i);\r\n\r\n triggerDependencySelect.addEventListener(\"change\", (event) => triggerDependencySelectHandler(event, i));\r\n triggerConditionSelect.addEventListener(\"change\", (event) => triggerConditionSelectHandler(event, i));\r\n triggerComparatorSelect.addEventListener(\"change\", (event) => triggerComparatorSelectHander(event, i));\r\n triggerValueInput.addEventListener(\"change\", (event) => triggerValueInputHandler(event, i));\r\n triggerRemoveButton.addEventListener(\"click\", (event) => triggerRemoveButtonHandler(event, i));\r\n }\r\n}\r\n\r\nfunction triggerModalShownHandler(event) {\r\n let triggerButton = event.relatedTarget;\r\n\r\n let triggerType = triggerButton.getAttribute(\"data-bs-triggertype\");\r\n let triggerIndex = Number(triggerButton.getAttribute(\"data-bs-triggerindex\"));\r\n\r\n let triggerTarget;\r\n switch (triggerType) {\r\n case \"food\":\r\n triggerTarget = food[triggerIndex];\r\n break;\r\n case \"drink\":\r\n triggerTarget = drinks[triggerIndex];\r\n break;\r\n case \"ability\":\r\n triggerTarget = abilities[triggerIndex];\r\n break;\r\n }\r\n\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n triggerTargetnput.value = triggerTarget;\r\n modalTriggers = triggerMap[triggerTarget];\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerModalSaveHandler(event) {\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n let triggerTarget = triggerTargetnput.value;\r\n\r\n triggerMap[triggerTarget] = modalTriggers;\r\n}\r\n\r\nfunction triggerDependencySelectHandler(event, index) {\r\n modalTriggers[index].dependencyHrid = event.target.value;\r\n modalTriggers[index].conditionHrid = \"\";\r\n modalTriggers[index].comparatorHrid = \"\";\r\n modalTriggers[index].value = 0;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerConditionSelectHandler(event, index) {\r\n modalTriggers[index].conditionHrid = event.target.value;\r\n modalTriggers[index].comparatorHrid = \"\";\r\n modalTriggers[index].value = 0;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerComparatorSelectHander(event, index) {\r\n modalTriggers[index].comparatorHrid = event.target.value;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerValueInputHandler(event, index) {\r\n modalTriggers[index].value = Number(event.target.value);\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerRemoveButtonHandler(event, index) {\r\n modalTriggers.splice(index, 1);\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerAddButtonHandler(event) {\r\n if (modalTriggers.length == 4) {\r\n return;\r\n }\r\n\r\n modalTriggers.push({\r\n dependencyHrid: \"\",\r\n conditionHrid: \"\",\r\n comparatorHrid: \"\",\r\n value: 0,\r\n });\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerDefaultButtonHandler(event) {\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n let triggerTarget = triggerTargetnput.value;\r\n\r\n if (triggerTarget.startsWith(\"/items/\")) {\r\n modalTriggers = structuredClone(itemDetailMap[triggerTarget].consumableDetail.defaultCombatTriggers);\r\n } else {\r\n modalTriggers = structuredClone(abilityDetailMap[triggerTarget].defaultCombatTriggers);\r\n }\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction updateTriggerModal() {\r\n let triggerStartTextElement = document.getElementById(\"triggerStartText\");\r\n if (modalTriggers.length == 0) {\r\n triggerStartTextElement.innerHTML = \"Activate as soon as it's off cooldown\";\r\n } else {\r\n triggerStartTextElement.innerHTML = \"Activate when:\";\r\n }\r\n\r\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\r\n triggerAddButton.disabled = modalTriggers.length == 4;\r\n\r\n let triggersValid = true;\r\n\r\n for (let i = 0; i < 4; i++) {\r\n let triggerElement = document.getElementById(\"modalTrigger_\" + i);\r\n\r\n if (!modalTriggers[i]) {\r\n hideElement(triggerElement);\r\n continue;\r\n }\r\n\r\n showElement(triggerElement);\r\n\r\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\r\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\r\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\r\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\r\n\r\n showElement(triggerDependencySelect);\r\n fillTriggerDependencySelect(triggerDependencySelect);\r\n\r\n if (modalTriggers[i].dependencyHrid == \"\") {\r\n hideElement(triggerConditionSelect);\r\n hideElement(triggerComparatorSelect);\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerDependencySelect.value = modalTriggers[i].dependencyHrid;\r\n showElement(triggerConditionSelect);\r\n fillTriggerConditionSelect(triggerConditionSelect, modalTriggers[i].dependencyHrid);\r\n\r\n if (modalTriggers[i].conditionHrid == \"\") {\r\n hideElement(triggerComparatorSelect);\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerConditionSelect.value = modalTriggers[i].conditionHrid;\r\n showElement(triggerComparatorSelect);\r\n fillTriggerComparatorSelect(triggerComparatorSelect, modalTriggers[i].conditionHrid);\r\n\r\n if (modalTriggers[i].comparatorHrid == \"\") {\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerComparatorSelect.value = modalTriggers[i].comparatorHrid;\r\n\r\n if (combatTriggerComparatorDetailMap[modalTriggers[i].comparatorHrid].allowValue) {\r\n showElement(triggerValueInput);\r\n triggerValueInput.value = modalTriggers[i].value;\r\n } else {\r\n hideElement(triggerValueInput);\r\n }\r\n }\r\n\r\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\r\n triggerSaveButton.disabled = !triggersValid;\r\n\r\n updateContent();\r\n}\r\n\r\nfunction fillTriggerDependencySelect(element) {\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const dependency of Object.values(combatTriggerDependencyDetailMap).sort(\r\n (a, b) => a.sortIndex - b.sortIndex\r\n )) {\r\n let opt = new Option(dependency.name, dependency.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerDependencyNames.\" + dependency.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction fillTriggerConditionSelect(element, dependencyHrid) {\r\n let dependency = combatTriggerDependencyDetailMap[dependencyHrid];\r\n\r\n let conditions;\r\n if (dependency.isSingleTarget) {\r\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isSingleTarget);\r\n } else {\r\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isMultiTarget);\r\n }\r\n\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const condition of Object.values(conditions).sort((a, b) => a.sortIndex - b.sortIndex)) {\r\n let opt = new Option(condition.name, condition.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerConditionNames.\" + condition.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction fillTriggerComparatorSelect(element, conditionHrid) {\r\n let condition = combatTriggerConditionDetailMap[conditionHrid];\r\n\r\n let comparators = condition.allowedComparatorHrids.map((hrid) => combatTriggerComparatorDetailMap[hrid]);\r\n\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const comparator of Object.values(comparators).sort((a, b) => a.sortIndex - b.sortIndex)) {\r\n let opt = new Option(comparator.name, comparator.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerComparatorNames.\" + comparator.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction hideElement(element) {\r\n element.classList.remove(\"d-flex\");\r\n element.classList.add(\"d-none\");\r\n}\r\n\r\nfunction showElement(element) {\r\n element.classList.remove(\"d-none\");\r\n element.classList.add(\"d-flex\");\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Zones\r\n\r\nfunction initZones() {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n\r\n // TOOD dungeon wave spawns\r\n let gameZones = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const zone of Object.values(gameZones)) {\r\n let opt = new Option(zone.name, zone.hrid);\r\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + zone.hrid);\r\n zoneSelect.add(opt);\r\n }\r\n\r\n\r\n let zoneCheckBox = document.getElementById(\"zoneCheckBox\");\r\n let checkAllZonesToggle = document.getElementById('checkAllZones');\r\n\r\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\r\n simAllZonesToggle.addEventListener(\"change\", (event) => {\r\n if (simAllZonesToggle.checked) {\r\n zoneCheckBox.classList.remove(\"d-none\");\r\n zoneCheckBox.querySelectorAll(\".zone-checkbox\").forEach(checkbox => checkbox.checked = true);\r\n checkAllZonesToggle.checked = true;\r\n } else {\r\n zoneCheckBox.classList.add(\"d-none\");\r\n }\r\n });\r\n\r\n let zoneHrids = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .flat();\r\n\r\n for (const zoneHrid of zoneHrids) {\r\n const newZone = document.createElement('div');\r\n newZone.classList.add('form-check');\r\n newZone.innerHTML = `\r\n \r\n \r\n `;\r\n zoneCheckBox.append(newZone);\r\n }\r\n\r\n let checkZoneToggles = document.querySelectorAll('.zone-checkbox');\r\n checkAllZonesToggle.addEventListener('change', () => {\r\n checkZoneToggles.forEach(cb => cb.checked = checkAllZonesToggle.checked);\r\n });\r\n\r\n checkZoneToggles.forEach(cb =>\r\n cb.addEventListener('change', () => {\r\n checkAllZonesToggle.checked = [...checkZoneToggles].every(x => x.checked);\r\n })\r\n );\r\n\r\n\r\n let soloCheckBox = document.getElementById(\"soloCheckBox\");\r\n let checkAllSolosToggle = document.getElementById('checkAllSolos');\r\n\r\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\r\n simAllSoloToggle.addEventListener(\"change\", (event) => {\r\n if (simAllSoloToggle.checked) {\r\n soloCheckBox.classList.remove(\"d-none\");\r\n soloCheckBox.querySelectorAll(\".solo-checkbox\").forEach(checkbox => checkbox.checked = true);\r\n checkAllSolosToggle.checked = true;\r\n } else {\r\n soloCheckBox.classList.add(\"d-none\");\r\n }\r\n });\r\n\r\n let soloHrids = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount == 1)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .flat();\r\n\r\n for (const zoneHrid of soloHrids) {\r\n const newZone = document.createElement('div');\r\n newZone.classList.add('form-check');\r\n newZone.innerHTML = `\r\n \r\n \r\n `;\r\n soloCheckBox.append(newZone);\r\n }\r\n\r\n let checkSoloToggles = document.querySelectorAll('.solo-checkbox');\r\n checkAllSolosToggle.addEventListener('change', () => {\r\n checkSoloToggles.forEach(cb => cb.checked = checkAllSolosToggle.checked);\r\n });\r\n\r\n checkSoloToggles.forEach(cb =>\r\n cb.addEventListener('change', () => {\r\n checkAllSolosToggle.checked = [...checkSoloToggles].every(x => x.checked);\r\n })\r\n );\r\n}\r\n\r\nfunction initDungeons() {\r\n let dungeonSelect = document.getElementById(\"selectDungeon\");\r\n\r\n let gameDungeons = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category == \"/action_categories/combat/dungeons\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const dungeon of Object.values(gameDungeons)) {\r\n let opt = new Option(dungeon.name, dungeon.hrid);\r\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + dungeon.hrid);\r\n dungeonSelect.add(opt);\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Simulation Result\r\n\r\nfunction createDamageDoneAccordion(enemyIndex) {\r\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageDoneAccordionEnemy${enemyIndex}`);\r\n\r\n const colDiv = createElement('div', 'col');\r\n const accordionMainDiv = createElement('div', 'accordion');\r\n const accordionItemDiv = createElement('div', 'accordion-item');\r\n\r\n const headerH2 = createElement('h2', 'accordion-header');\r\n const button = createElement('button', 'accordion-button collapsed',\r\n `Damage Done (Enemy ${enemyIndex})`,\r\n `buttonSimulationResultDamageDoneAccordionEnemy${enemyIndex}`\r\n );\r\n button.setAttribute('type', 'button');\r\n button.setAttribute('data-bs-toggle', 'collapse');\r\n button.setAttribute('data-bs-target', `#collapseDamageDone${enemyIndex}`);\r\n button.style.padding = '0.5em';\r\n\r\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageDone${enemyIndex}`);\r\n const accordionBodyDiv = createElement('div', 'accordion-body');\r\n\r\n const headerRow = createElement('div', 'row');\r\n headerRow.innerHTML = `\r\n
Source
\r\n
Hitchance
\r\n
DPS
\r\n
%
\r\n `;\r\n\r\n const resultDiv = createElement('div', '', '', `simulationResultDamageDoneEnemy${enemyIndex}`);\r\n\r\n accordionBodyDiv.appendChild(headerRow);\r\n accordionBodyDiv.appendChild(resultDiv);\r\n collapseDiv.appendChild(accordionBodyDiv);\r\n headerH2.appendChild(button);\r\n accordionItemDiv.appendChild(headerH2);\r\n accordionItemDiv.appendChild(collapseDiv);\r\n accordionMainDiv.appendChild(accordionItemDiv);\r\n colDiv.appendChild(accordionMainDiv);\r\n accordionDiv.appendChild(colDiv);\r\n\r\n return accordionDiv;\r\n}\r\nfunction createDamageTakenAccordion(enemyIndex) {\r\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageTakenAccordionEnemy${enemyIndex}`);\r\n\r\n const colDiv = createElement('div', 'col');\r\n const accordionMainDiv = createElement('div', 'accordion');\r\n const accordionItemDiv = createElement('div', 'accordion-item');\r\n\r\n const headerH2 = createElement('h2', 'accordion-header');\r\n const button = createElement('button', 'accordion-button collapsed',\r\n `Damage Taken (Enemy ${enemyIndex})`,\r\n `buttonSimulationResultDamageTakenAccordionEnemy${enemyIndex}`\r\n );\r\n button.setAttribute('type', 'button');\r\n button.setAttribute('data-bs-toggle', 'collapse');\r\n button.setAttribute('data-bs-target', `#collapseDamageTaken${enemyIndex}`);\r\n button.style.padding = '0.5em';\r\n\r\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageTaken${enemyIndex}`);\r\n const accordionBodyDiv = createElement('div', 'accordion-body');\r\n\r\n const headerRow = createElement('div', 'row');\r\n headerRow.innerHTML = `\r\n
Source
\r\n
Hitchance
\r\n
DPS
\r\n
%
\r\n `;\r\n\r\n const resultDiv = createElement('div', '', '', `simulationResultDamageTakenEnemy${enemyIndex}`);\r\n\r\n accordionBodyDiv.appendChild(headerRow);\r\n accordionBodyDiv.appendChild(resultDiv);\r\n collapseDiv.appendChild(accordionBodyDiv);\r\n headerH2.appendChild(button);\r\n accordionItemDiv.appendChild(headerH2);\r\n accordionItemDiv.appendChild(collapseDiv);\r\n accordionMainDiv.appendChild(accordionItemDiv);\r\n colDiv.appendChild(accordionMainDiv);\r\n accordionDiv.appendChild(colDiv);\r\n\r\n return accordionDiv;\r\n}\r\n\r\n\r\nfunction initDamageDoneTaken() {\r\n for (let i = 64; i > 0; i--) {\r\n document.getElementById(\"simulationResultTotalDamageDone\").insertAdjacentElement('afterend', createDamageDoneAccordion(i));\r\n document.getElementById(\"simulationResultTotalDamageTaken\").insertAdjacentElement('afterend', createDamageTakenAccordion(i));\r\n }\r\n}\r\n\r\nfunction showSimulationResult(simResult) {\r\n currentSimResults = simResult;\r\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\r\n expensesModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\r\n revenueModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let noRngRevenueModalTable = document.querySelector(\"#noRngRevenueTable > tbody\");\r\n noRngRevenueModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let playerToDisplay = \"player1\";\r\n if (selectedPlayers.includes(parseInt(currentPlayerTabId))) {\r\n playerToDisplay = \"player\" + currentPlayerTabId;\r\n }\r\n if (!simResult.dropRateMultiplier[playerToDisplay]) {\r\n return;\r\n }\r\n\r\n showKills(simResult, playerToDisplay);\r\n showDeaths(simResult, playerToDisplay);\r\n showExperienceGained(simResult, playerToDisplay);\r\n showConsumablesUsed(simResult, playerToDisplay);\r\n showHpSpent(simResult, playerToDisplay);\r\n showManaUsed(simResult, playerToDisplay);\r\n showHitpointsGained(simResult, playerToDisplay);\r\n showManapointsGained(simResult, playerToDisplay);\r\n showDamageDone(simResult, playerToDisplay);\r\n showDamageTaken(simResult, playerToDisplay);\r\n renderWipeEvents(simResult);\r\n window.profit = window.revenue - window.expenses;\r\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\r\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\r\n window.noRngProfit = window.noRngRevenue - window.expenses;\r\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\r\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\r\n}\r\n\r\nfunction showAllSimulationResults(simResults) {\r\n let displaySimResults = manipulateSimResultsDataForDisplay(simResults);\r\n updateAllSimsModal(displaySimResults);\r\n}\r\n\r\nfunction manipulateSimResultsDataForDisplay(simResults) {\r\n let displaySimResults = [];\r\n for (let i = 0; i < simResults.length; i++) {\r\n for (let j = 0; j < selectedPlayers.length; j++) {\r\n let playerToDisplay = \"player\" + selectedPlayers[j].toString();\r\n let simResult = simResults[i];\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let zoneName = simResult.zoneName;\r\n let difficultyTier = simResult.difficultyTier;\r\n let encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\r\n let playerDeaths = simResult.deaths[playerToDisplay] ?? 0;\r\n let deathsPerHour = (playerDeaths / hoursSimulated).toFixed(2);\r\n\r\n let totalExperience = 0;\r\n if (simResult.experienceGained[playerToDisplay]) {\r\n totalExperience = Object.values(simResult.experienceGained[playerToDisplay]).reduce((prev, cur) => prev + cur, 0);\r\n }\r\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\r\n\r\n let experiencePerHour = {};\r\n const skills = [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"];\r\n skills.forEach((skill) => {\r\n const skillLower = skill.toLowerCase();\r\n let experience = simResult.experienceGained[playerToDisplay]?.[skillLower] ?? 0;\r\n let experiencePerHourValue = 0;\r\n if (experience != 0) {\r\n experiencePerHourValue = (experience / hoursSimulated).toFixed(0);\r\n }\r\n experiencePerHour[skill] = experiencePerHourValue;\r\n });\r\n getDropProfit(simResult, playerToDisplay);\r\n let noRngRevenue = simResult[\"noRngRevenue\"];\r\n let noRngProfit = simResult[\"noRngProfit\"];\r\n let expenses = simResult[\"expenses\"];\r\n\r\n let displaySimRow = {\r\n \"ZoneName\": zoneName, \"DifficultyTier\": difficultyTier, \"Player\": playerToDisplay, \"Encounters\": encountersPerHour, \"Deaths\": deathsPerHour,\r\n \"TotalExperience\": totalExperiencePerHour, \"Stamina\": experiencePerHour[\"Stamina\"],\r\n \"Intelligence\": experiencePerHour[\"Intelligence\"], \"Attack\": experiencePerHour[\"Attack\"],\r\n \"Magic\": experiencePerHour[\"Magic\"], \"Ranged\": experiencePerHour[\"Ranged\"],\r\n \"Melee\": experiencePerHour[\"Melee\"], \"Defense\": experiencePerHour[\"Defense\"],\r\n \"noRngRevenue\": noRngRevenue,\r\n \"expenses\": expenses,\r\n \"noRngProfit\": noRngProfit\r\n };\r\n displaySimResults.push(displaySimRow);\r\n }\r\n }\r\n return displaySimResults;\r\n}\r\n\r\nfunction fidDropAmount(dropAmount) {\r\n if (Number.isInteger(dropAmount)) return dropAmount;\r\n\r\n const intPart = Math.floor(dropAmount);\r\n const fracPart = dropAmount - intPart;\r\n return Math.random() < fracPart ? intPart + 1 : intPart;\r\n}\r\n\r\nfunction calcDropMaps(simResult, playerToDisplay) {\r\n let dropRateMultiplier = simResult.dropRateMultiplier[playerToDisplay];\r\n let rareFindMultiplier = simResult.rareFindMultiplier[playerToDisplay];\r\n let combatDropQuantity = simResult.combatDropQuantity[playerToDisplay];\r\n let debuffOnLevelGap = simResult.debuffOnLevelGap[playerToDisplay];\r\n\r\n let numberOfPlayers = simResult.numberOfPlayers;\r\n let monsters = Object.keys(simResult.deaths)\r\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\r\n .sort();\r\n\r\n const totalDropMap = new Map();\r\n const noRngTotalDropMap = new Map();\r\n for (const monster of monsters) {\r\n const dropMap = new Map();\r\n const rareDropMap = new Map();\r\n if (combatMonsterDetailMap[monster].dropTable) {\r\n for (const drop of combatMonsterDetailMap[monster].dropTable) {\r\n if (drop.minDifficultyTier > simResult.difficultyTier) {\r\n continue;\r\n }\r\n\r\n let multiplier = 1.0 + 0.1 * simResult.difficultyTier;\r\n let dropRate = Math.min(1.0, multiplier * (drop.dropRate + (drop.dropRatePerDifficultyTier ?? 0) * simResult.difficultyTier));\r\n if (dropRate <= 0) continue;\r\n\r\n dropMap.set(drop.itemHrid, { \"dropRate\": Math.min(1.0, dropRate * dropRateMultiplier), \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\r\n }\r\n if (combatMonsterDetailMap[monster].rareDropTable)\r\n for (const drop of combatMonsterDetailMap[monster].rareDropTable) {\r\n if (drop.minDifficultyTier > simResult.difficultyTier) {\r\n continue;\r\n }\r\n rareDropMap.set(drop.itemHrid, { \"dropRate\": drop.dropRate * rareFindMultiplier, \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\r\n }\r\n\r\n for (let dropObject of dropMap.values()) {\r\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\r\n\r\n }\r\n for (let dropObject of rareDropMap.values()) {\r\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\r\n }\r\n\r\n for (let i = 0; i < simResult.deaths[monster]; i++) {\r\n for (let dropObject of dropMap.values()) {\r\n let chance = Math.random();\r\n if (chance <= dropObject.dropRate / numberOfPlayers) {\r\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\r\n dropObject.number = dropObject.number + fidDropAmount(amount);\r\n }\r\n }\r\n for (let dropObject of rareDropMap.values()) {\r\n let chance = Math.random();\r\n if (chance <= dropObject.dropRate / numberOfPlayers) {\r\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\r\n dropObject.number = dropObject.number + fidDropAmount(amount);\r\n }\r\n }\r\n }\r\n for (let [name, dropObject] of dropMap.entries()) {\r\n if (totalDropMap.has(name)) {\r\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\r\n } else {\r\n totalDropMap.set(name, dropObject.number);\r\n }\r\n if (noRngTotalDropMap.has(name)) {\r\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\r\n } else {\r\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\r\n }\r\n }\r\n for (let [name, dropObject] of rareDropMap.entries()) {\r\n if (totalDropMap.has(name)) {\r\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\r\n } else {\r\n totalDropMap.set(name, dropObject.number);\r\n }\r\n if (noRngTotalDropMap.has(name)) {\r\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\r\n } else {\r\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return { totalDropMap, noRngTotalDropMap };\r\n}\r\n\r\nfunction getDropProfit(simResult, playerToDisplay) {\r\n let { totalDropMap, noRngTotalDropMap } = calcDropMaps(simResult, playerToDisplay);\r\n\r\n let noRngTotal = 0;\r\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\r\n let price = -1;\r\n let revenueSetting = document.getElementById('selectPrices_drops').value;\r\n if (window.prices) {\r\n let item = window.prices[name];\r\n if (item) {\r\n if (revenueSetting == 'bid') {\r\n if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n } else if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n }\r\n } else if (revenueSetting == 'ask') {\r\n if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n } else if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n }\r\n }\r\n if (price == -1) {\r\n price = item['vendor'];\r\n }\r\n }\r\n }\r\n noRngTotal += price * dropAmount;\r\n }\r\n\r\n let consumablesUsed = simResult.consumablesUsed?.[playerToDisplay];\r\n\r\n if (consumablesUsed) {\r\n consumablesUsed = Object.entries(consumablesUsed).sort((a, b) => b[1] - a[1]);\r\n } else {\r\n consumablesUsed = [];\r\n }\r\n\r\n let expenses = 0;\r\n for (const [consumable, amount] of consumablesUsed) {\r\n let price = -1;\r\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\r\n if (window.prices) {\r\n let item = window.prices[consumable];\r\n if (item) {\r\n if (expensesSetting == 'bid') {\r\n if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n } else if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n }\r\n } else if (expensesSetting == 'ask') {\r\n if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n } else if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n }\r\n }\r\n if (price == -1) {\r\n price = item['vendor'];\r\n }\r\n }\r\n }\r\n expenses += price * amount;\r\n }\r\n\r\n simResult[\"noRngRevenue\"] = (noRngTotal).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n simResult[\"expenses\"] = (expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n simResult[\"noRngProfit\"] = (noRngTotal - expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n}\r\n\r\nfunction updateAllSimsModal(data) {\r\n const tableBody = document.getElementById('allZonesData').getElementsByTagName('tbody')[0];\r\n tableBody.innerHTML = '';\r\n data.forEach(item => {\r\n const row = document.createElement('tr');\r\n\r\n Object.keys(item).forEach(key => {\r\n const cell = document.createElement('td');\r\n cell.textContent = item[key];\r\n if (key === 'ZoneName') {\r\n cell.setAttribute(\"data-i18n\", \"actionNames.\" + item[key]);\r\n }\r\n row.appendChild(cell);\r\n });\r\n\r\n tableBody.appendChild(row);\r\n });\r\n\r\n const table = document.getElementById('allZonesData');\r\n const rows = table.getElementsByTagName('tr');\r\n const numCols = rows[0].cells.length;\r\n\r\n // 遍历每一列\r\n for (let col = 5; col < numCols; col++) {\r\n let max = -Infinity;\r\n let maxCell = null;\r\n\r\n // 找到最大值及其单元格\r\n for (let row = 1; row < rows.length; row++) {\r\n const cell = rows[row].cells[col];\r\n const value = parseFloat(cell.textContent.replace(/,/g, ''));\r\n if (value > max) {\r\n max = value;\r\n maxCell = cell;\r\n }\r\n }\r\n\r\n // 将最大值单元格的背景色设置为绿色\r\n if (maxCell && max != 0) {\r\n maxCell.style.backgroundColor = 'green';\r\n maxCell.style.color = 'white'; // 设置文字颜色为白色以提高可读性\r\n }\r\n }\r\n}\r\n\r\nlet currentSortColumn = null;\r\nlet currentSortDirection = 'desc';\r\n\r\nfunction sortTable(tableId, columnIndex, direction) {\r\n const table = document.getElementById(tableId);\r\n const tbody = table.querySelector('tbody');\r\n const rows = Array.from(tbody.querySelectorAll('tr'));\r\n\r\n const sortedRows = rows.sort((rowA, rowB) => {\r\n const cellA = rowA.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\r\n const cellB = rowB.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\r\n\r\n const valueA = parseFloat(cellA.replace(/,/g, ''));\r\n const valueB = parseFloat(cellB.replace(/,/g, ''));\r\n\r\n return direction === 'asc' ? valueA - valueB : valueB - valueA;\r\n });\r\n\r\n sortedRows.forEach(row => tbody.appendChild(row));\r\n updateSortIndicators(tableId, columnIndex, direction);\r\n}\r\n\r\nfunction updateSortIndicators(tableId, columnIndex, direction) {\r\n const headers = document.querySelectorAll(`#${tableId} th`);\r\n headers.forEach((header, index) => {\r\n header.classList.remove('sort-asc', 'sort-desc');\r\n if (index === columnIndex) {\r\n header.classList.add(direction === 'asc' ? 'sort-asc' : 'sort-desc');\r\n }\r\n });\r\n}\r\n\r\ndocument.querySelectorAll('#allZonesData th').forEach((header, index) => {\r\n if (index === 0) return;\r\n if (index === 1) return;\r\n if (index === 2) return;\r\n\r\n header.addEventListener('click', () => {\r\n if (currentSortColumn === index) {\r\n currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';\r\n } else {\r\n currentSortColumn = index;\r\n currentSortDirection = 'desc';\r\n }\r\n sortTable('allZonesData', currentSortColumn, currentSortDirection);\r\n });\r\n});\r\n\r\ndocument.getElementById('buttonExportResults').addEventListener('click', function () {\r\n var table = document.getElementById('allZonesData');\r\n var csv = [];\r\n var rows = table.querySelectorAll('tr');\r\n\r\n for (var i = 0; i < rows.length; i++) {\r\n var row = rows[i];\r\n var cols = row.querySelectorAll('th, td');\r\n var csvRow = [];\r\n\r\n cols.forEach(function (col) {\r\n csvRow.push('\"' + col.innerText.replace(/\"/g, '\"\"') + '\"');\r\n });\r\n\r\n csv.push(csvRow.join(','));\r\n }\r\n\r\n var csvFile = new Blob([csv.join('\\n')], { type: 'text/csv' });\r\n var downloadLink = document.createElement('a');\r\n downloadLink.download = 'simData.csv';\r\n downloadLink.href = URL.createObjectURL(csvFile);\r\n downloadLink.style.display = 'none';\r\n document.body.appendChild(downloadLink);\r\n downloadLink.click();\r\n document.body.removeChild(downloadLink);\r\n});\r\n\r\nfunction showKills(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultKills\");\r\n let dropsResultDiv = document.getElementById(\"simulationResultDrops\");\r\n let noRngDropsResultDiv = document.getElementById(\"noRngDrops\");\r\n let newChildren = [];\r\n let newDropChildren = [];\r\n let newNoRngDropChildren = [];\r\n\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let encountersPerHour = 0;\r\n let encountersRow = null;\r\n if (simResult.isDungeon) {\r\n let wavesCompletedRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Wave Reached\", simResult.maxWaveReached]);\r\n wavesCompletedRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxWaveReached\");\r\n newChildren.push(wavesCompletedRow);\r\n let completedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Completed Dungeons\", simResult.dungeonsCompleted]);\r\n completedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsCompleted\");\r\n newChildren.push(completedDungeonsRow);\r\n if (simResult.dungeonsFailed > 0) {\r\n let failedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Failed Dungeons\", simResult.dungeonsFailed]);\r\n failedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsFailed\");\r\n newChildren.push(failedDungeonsRow);\r\n }\r\n encountersPerHour = (simResult.dungeonsCompleted / hoursSimulated).toFixed(1);\r\n let averageTime = (hoursSimulated * 60 / simResult.dungeonsCompleted).toFixed(1);\r\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Average Time\", averageTime]);\r\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.averageTime\");\r\n } else {\r\n encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\r\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Encounters\", encountersPerHour]);\r\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.encounters\");\r\n }\r\n\r\n if (simResult.maxEnrageStack > 0) {\r\n let enrageRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Enrage Stack\", simResult.maxEnrageStack]);\r\n enrageRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxEnrageStack\");\r\n newChildren.push(enrageRow);\r\n }\r\n\r\n if (simResult.debuffOnLevelGap[playerToDisplay] != 0) {\r\n let debuffOnLevelGapRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Debuff on Level Gap\", Math.round(simResult.debuffOnLevelGap[playerToDisplay] * 100) + \"%\"]);\r\n debuffOnLevelGapRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.debuffOnLevelGap\");\r\n newChildren.push(debuffOnLevelGapRow);\r\n }\r\n\r\n newChildren.push(encountersRow);\r\n\r\n Object.keys(simResult.deaths)\r\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\r\n .sort()\r\n .forEach(monster => {\r\n let killsPerHour = (simResult.deaths[monster] / hoursSimulated).toFixed(1);\r\n let monsterRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [combatMonsterDetailMap[monster].name, killsPerHour]\r\n );\r\n monsterRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + monster);\r\n newChildren.push(monsterRow);\r\n });\r\n\r\n let { totalDropMap, noRngTotalDropMap } = !simResult.isDungeon ? calcDropMaps(simResult, playerToDisplay) : {totalDropMap:new Map(), noRngTotalDropMap:new Map()};\r\n\r\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\r\n let total = 0;\r\n for (let [name, dropAmount] of totalDropMap.entries()) {\r\n let dropRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [name, dropAmount.toLocaleString()]\r\n );\r\n dropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\r\n newDropChildren.push(dropRow);\r\n\r\n let tableRow = ' tbody\");\r\n let noRngTotal = 0;\r\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\r\n let noRngDropRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [name, dropAmount.toLocaleString()]\r\n );\r\n noRngDropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\r\n newNoRngDropChildren.push(noRngDropRow);\r\n\r\n let tableRow = ' prev + cur, 0);\r\n }\r\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\r\n let totalRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Total\", totalExperiencePerHour]);\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"].forEach((skill) => {\r\n let experience = simResult.experienceGained[playerToDisplay]?.[skill.toLowerCase()] ?? 0;\r\n if (experience == 0) {\r\n return;\r\n }\r\n let experiencePerHour = (experience / hoursSimulated).toFixed(0);\r\n let experienceRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [skill, experiencePerHour]);\r\n experienceRow.firstElementChild.setAttribute(\"data-i18n\", \"leaderboardCategoryNames.\" + skill.toLowerCase());\r\n newChildren.push(experienceRow);\r\n });\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showHpSpent(simResult, playerToDisplay) {\r\n let hpSpentHeadingDiv = document.getElementById(\"simulationHpSpentHeading\");\r\n hpSpentHeadingDiv.classList.add(\"d-none\");\r\n let hpSpentDiv = document.getElementById(\"simulationHpSpent\");\r\n hpSpentDiv.classList.add(\"d-none\");\r\n\r\n if (simResult.hitpointsSpent[playerToDisplay]) {\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let hpSpentSources = [];\r\n for (const source of Object.keys(simResult.hitpointsSpent[playerToDisplay])) {\r\n let hpSpentPerHour = (simResult.hitpointsSpent[playerToDisplay][source] / hoursSimulated).toFixed(2);\r\n let hpSpentRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [abilityDetailMap[source].name, hpSpentPerHour]);\r\n hpSpentRow.firstElementChild.setAttribute(\"data-i18n\", \"abilityNames.\" + source);\r\n hpSpentSources.push(hpSpentRow);\r\n }\r\n hpSpentDiv.replaceChildren(...hpSpentSources);\r\n hpSpentHeadingDiv.classList.remove(\"d-none\");\r\n hpSpentDiv.classList.remove(\"d-none\");\r\n }\r\n}\r\n\r\nfunction showConsumablesUsed(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultConsumablesUsed\");\r\n let newChildren = [];\r\n\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n\r\n if (!simResult.consumablesUsed[playerToDisplay]) {\r\n resultDiv.replaceChildren(...newChildren);\r\n window.expenses = 0;\r\n return;\r\n }\r\n\r\n let consumablesUsed = Object.entries(simResult.consumablesUsed[playerToDisplay]).sort((a, b) => b[1] - a[1]);\r\n\r\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\r\n let total = 0;\r\n for (const [consumable, amount] of consumablesUsed) {\r\n let consumablesPerHour = (amount / hoursSimulated).toFixed(0);\r\n let consumableRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [itemDetailMap[consumable].name, consumablesPerHour]\r\n );\r\n consumableRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + consumable);\r\n newChildren.push(consumableRow);\r\n\r\n let tableRow = ' b[1] - a[1]);\r\n\r\n let totalHitpointsGained = hitpointsGained.reduce((prev, cur) => prev + cur[1], 0);\r\n let totalHitpointsPerSecond = (totalHitpointsGained / secondsSimulated).toFixed(2);\r\n let totalRow = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [\"Total\", totalHitpointsPerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [source, amount] of hitpointsGained) {\r\n if (amount == 0) {\r\n continue;\r\n }\r\n\r\n let sourceText;\r\n let sourceFullHrid;\r\n switch (source) {\r\n case \"regen\":\r\n sourceText = \"Regen\";\r\n sourceFullHrid = \"combatStats.hpRegenPer10\";\r\n break;\r\n case \"lifesteal\":\r\n sourceText = \"Life Steal\";\r\n sourceFullHrid = \"combatStats.lifeSteal\";\r\n break;\r\n case \"bloom\":\r\n sourceText = \"Bloom\";\r\n sourceFullHrid = \"combatStats.bloom\";\r\n break;\r\n default:\r\n if (itemDetailMap[source]) {\r\n sourceText = itemDetailMap[source].name;\r\n sourceFullHrid = \"itemNames.\" + source;\r\n } else if (abilityDetailMap[source]) {\r\n sourceText = abilityDetailMap[source].name;\r\n sourceFullHrid = \"abilityNames.\" + source;\r\n }\r\n break;\r\n }\r\n let hitpointsPerSecond = (amount / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * amount) / totalHitpointsGained).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [sourceText, hitpointsPerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showManapointsGained(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultManaRestored\");\r\n let newChildren = [];\r\n\r\n let secondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n if (!simResult.manapointsGained[playerToDisplay]) {\r\n resultDiv.replaceChildren(...newChildren);\r\n return;\r\n }\r\n\r\n let manapointsGained = Object.entries(simResult.manapointsGained[playerToDisplay]).sort((a, b) => b[1] - a[1]);\r\n\r\n let totalManapointsGained = manapointsGained.reduce((prev, cur) => prev + cur[1], 0);\r\n let totalManapointsPerSecond = (totalManapointsGained / secondsSimulated).toFixed(2);\r\n let totalRow = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [\"Total\", totalManapointsPerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [source, amount] of manapointsGained) {\r\n if (amount == 0) {\r\n continue;\r\n }\r\n\r\n let sourceText;\r\n let sourceFullHrid;\r\n switch (source) {\r\n case \"regen\":\r\n sourceText = \"Regen\";\r\n sourceFullHrid = \"combatStats.mpRegenPer10\";\r\n break;\r\n case \"manaLeech\":\r\n sourceText = \"Mana Leech\";\r\n sourceFullHrid = \"combatStats.manaLeech\";\r\n break;\r\n case \"ripple\":\r\n sourceText = \"Ripple\";\r\n sourceFullHrid = \"combatStats.ripple\";\r\n break;\r\n default:\r\n sourceText = itemDetailMap[source].name;\r\n sourceFullHrid = \"itemNames.\" + source;\r\n break;\r\n }\r\n let manapointsPerSecond = (amount / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * amount) / totalManapointsGained).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [sourceText, manapointsPerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n let ranOutOfManaText = simResult.playerRanOutOfMana[playerToDisplay] ? \"Yes\" : \"No\";\r\n let ranOutOfManaRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Ran out of mana\", ranOutOfManaText]);\r\n ranOutOfManaRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfMana\");\r\n ranOutOfManaRow.lastElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.\" + ranOutOfManaText);\r\n newChildren.push(ranOutOfManaRow);\r\n\r\n if (simResult.playerRanOutOfMana[playerToDisplay]) {\r\n let ranOutOfManaStat = simResult.playerRanOutOfManaTime[playerToDisplay]; // {isOutOfMana: false, startTimeForOutOfMana:0, totalTimeForOutOfMana:0};\r\n let totalTimeForOut = ranOutOfManaStat.totalTimeForOutOfMana + (ranOutOfManaStat.isOutOfMana ? (simResult.simulatedTime - ranOutOfManaStat.startTimeForOutOfMana) : 0);\r\n\r\n let ranOutOfManaStatRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [\r\n \"Run Out Ratio\",\r\n (totalTimeForOut / simResult.simulatedTime * 100).toFixed(2) + \"%\"\r\n ]\r\n );\r\n ranOutOfManaStatRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfManaRatio\");\r\n newChildren.push(ranOutOfManaStatRow);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showDamageDone(simResult, playerToDisplay) {\r\n let totalDamageDone = {};\r\n let enemyIndex = 1;\r\n\r\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n for (let i = 1; i < 64; i++) {\r\n let accordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + i);\r\n hideElement(accordion);\r\n }\r\n\r\n let bossTimeHeadingDiv = document.getElementById(\"simulationBossTimeHeading\");\r\n bossTimeHeadingDiv.classList.add(\"d-none\");\r\n let bossTimeDiv = document.getElementById(\"simulationBossTime\");\r\n bossTimeDiv.classList.add(\"d-none\");\r\n\r\n if (!simResult.attacks[playerToDisplay]) {\r\n return;\r\n }\r\n\r\n for (const [target, abilities] of Object.entries(simResult.attacks[playerToDisplay])) {\r\n let targetDamageDone = {};\r\n\r\n const i = simResult.timeSpentAlive.findIndex(e => e.name === target);\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\r\n\r\n for (const [ability, abilityCasts] of Object.entries(abilities)) {\r\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\r\n let misses = abilityCasts[\"miss\"] ?? 0;\r\n let damage = Object.entries(abilityCasts)\r\n .filter((entry) => entry[0] != \"miss\")\r\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\r\n\r\n targetDamageDone[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n if (totalDamageDone[ability]) {\r\n totalDamageDone[ability].casts += casts;\r\n totalDamageDone[ability].misses += misses;\r\n totalDamageDone[ability].damage += damage;\r\n } else {\r\n totalDamageDone[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n }\r\n }\r\n\r\n let resultDiv = document.getElementById(\"simulationResultDamageDoneEnemy\" + enemyIndex);\r\n createDamageTable(resultDiv, targetDamageDone, aliveSecondsSimulated);\r\n\r\n let resultAccordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + enemyIndex);\r\n showElement(resultAccordion);\r\n\r\n let resultAccordionButton = document.getElementById(\r\n \"buttonSimulationResultDamageDoneAccordionEnemy\" + enemyIndex\r\n );\r\n let targetName = combatMonsterDetailMap[target].name;\r\n resultAccordionButton.innerHTML = \"Damage Done (\" + \"\" + targetName + \"\" + \")\";\r\n\r\n if (simResult.bossSpawns.includes(target)) {\r\n let hoursSpentOnBoss = (aliveSecondsSimulated / 60 / 60).toFixed(2);\r\n let percentSpentOnBoss = (aliveSecondsSimulated / totalSecondsSimulated * 100).toFixed(2);\r\n\r\n let bossRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [targetName, hoursSpentOnBoss + \"h(\" + percentSpentOnBoss + \"%)\"]);\r\n bossRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + target);\r\n bossTimeDiv.replaceChildren(bossRow);\r\n\r\n bossTimeHeadingDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.classList.remove(\"d-none\");\r\n }\r\n\r\n enemyIndex++;\r\n }\r\n\r\n if (simResult.isDungeon) {\r\n let newChildren = [];\r\n for (const waveName of simResult.bossSpawns) {\r\n // waveName is something like \"#15,/monsters/jackalope,/monsters/butterjerry\"\r\n let waveNumber = waveName.split(\",\")[0];\r\n const idx = simResult.timeSpentAlive.findIndex(e => e.name === waveNumber);\r\n if (idx == -1 || simResult.timeSpentAlive[idx].count == 0) {\r\n continue;\r\n }\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[idx].timeSpentAlive / ONE_SECOND / simResult.timeSpentAlive[idx].count;\r\n let bossRow = createRow([\"col-md-6\", \"col-md-2\", \"col-md-4 text-end\"], [waveNumber, simResult.timeSpentAlive[idx].count, aliveSecondsSimulated.toFixed(1) + \"s\"]);\r\n newChildren.push(bossRow);\r\n }\r\n if (newChildren.length > 0) {\r\n bossTimeHeadingDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.replaceChildren(...newChildren);\r\n }\r\n }\r\n\r\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageDone\");\r\n createDamageTable(totalResultDiv, totalDamageDone, totalSecondsSimulated);\r\n}\r\n\r\nfunction showDamageTaken(simResult, playerToDisplay) {\r\n let totalDamageTaken = {};\r\n let enemyIndex = 1;\r\n\r\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n for (let i = 1; i < 64; i++) {\r\n let accordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + i);\r\n hideElement(accordion);\r\n }\r\n\r\n for (const [source, targets] of Object.entries(simResult.attacks)) {\r\n const validSources = [\"player1\", \"player2\", \"player3\", \"player4\", \"player5\"];\r\n if (validSources.includes(source)) {\r\n continue;\r\n }\r\n const i = simResult.timeSpentAlive.findIndex(e => e.name === source);\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\r\n let sourceDamageTaken = {};\r\n if (targets[playerToDisplay] && Object.keys(targets[playerToDisplay]).length > 0) {\r\n for (const [ability, abilityCasts] of Object.entries(targets[playerToDisplay])) {\r\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\r\n let misses = abilityCasts[\"miss\"] ?? 0;\r\n let damage = Object.entries(abilityCasts)\r\n .filter((entry) => entry[0] != \"miss\")\r\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\r\n\r\n sourceDamageTaken[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n if (totalDamageTaken[ability]) {\r\n totalDamageTaken[ability].casts += casts;\r\n totalDamageTaken[ability].misses += misses;\r\n totalDamageTaken[ability].damage += damage;\r\n } else {\r\n totalDamageTaken[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n }\r\n }\r\n }\r\n\r\n let resultDiv = document.getElementById(\"simulationResultDamageTakenEnemy\" + enemyIndex);\r\n createDamageTable(resultDiv, sourceDamageTaken, aliveSecondsSimulated);\r\n\r\n let resultAccordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + enemyIndex);\r\n showElement(resultAccordion);\r\n\r\n let resultAccordionButton = document.getElementById(\r\n \"buttonSimulationResultDamageTakenAccordionEnemy\" + enemyIndex\r\n );\r\n let sourceName = combatMonsterDetailMap[source].name;\r\n resultAccordionButton.innerHTML = \"Damage Taken (\" + \"\" + sourceName + \"\" + \")\";\r\n\r\n enemyIndex++;\r\n }\r\n\r\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageTaken\");\r\n createDamageTable(totalResultDiv, totalDamageTaken, totalSecondsSimulated);\r\n}\r\n\r\nfunction createDamageTable(resultDiv, damageDone, secondsSimulated) {\r\n let newChildren = [];\r\n\r\n let sortedDamageDone = Object.entries(damageDone).sort((a, b) => b[1].damage - a[1].damage);\r\n\r\n let totalCasts = sortedDamageDone.reduce((prev, cur) => prev + cur[1].casts, 0);\r\n let totalMisses = sortedDamageDone.reduce((prev, cur) => prev + cur[1].misses, 0);\r\n let totalDamage = sortedDamageDone.reduce((prev, cur) => prev + cur[1].damage, 0);\r\n let totalHitChance = ((100 * (totalCasts - totalMisses)) / totalCasts).toFixed(1);\r\n let totalDamagePerSecond = (totalDamage / secondsSimulated).toFixed(2);\r\n\r\n let totalRow = createRow(\r\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\r\n [\"Total\", totalHitChance + \"%\", totalDamagePerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [ability, damageInfo] of sortedDamageDone) {\r\n let abilityText;\r\n let abilityFullHrid;\r\n switch (ability) {\r\n case \"autoAttack\":\r\n abilityText = \"Auto Attack\";\r\n abilityFullHrid = \"combatUnit.autoAttack\";\r\n break;\r\n case \"parry\":\r\n abilityText = \"Parry Attack\";\r\n abilityFullHrid = \"common:simulationResults.parryAttack\";\r\n break;\r\n case \"damageOverTime\":\r\n abilityText = \"Damage Over Time\";\r\n abilityFullHrid = \"common:simulationResults.damageOverTime\";\r\n break;\r\n case \"physicalThorns\":\r\n abilityText = \"Physical Thorns\";\r\n abilityFullHrid = \"combatStats.physicalThorns\";\r\n break;\r\n case \"elementalThorns\":\r\n abilityText = \"Elemental Thorns\";\r\n abilityFullHrid = \"combatStats.elementalThorns\";\r\n break;\r\n case \"retaliation\":\r\n abilityText = \"Retaliation\";\r\n abilityFullHrid = \"combatStats.retaliation\";\r\n break;\r\n case 'blaze':\r\n abilityText = \"Blaze\";\r\n abilityFullHrid = \"combatStats.blaze\";\r\n break;\r\n default:\r\n abilityText = abilityDetailMap[ability].name;\r\n abilityFullHrid = \"abilityNames.\" + ability;\r\n break;\r\n }\r\n\r\n let hitChance = ((100 * (damageInfo.casts - damageInfo.misses)) / damageInfo.casts).toFixed(1);\r\n let damagePerSecond = (damageInfo.damage / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * damageInfo.damage) / totalDamage).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\r\n [abilityText, hitChance + \"%\", damagePerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", abilityFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction createRow(columnClassNames, columnValues) {\r\n let row = createElement(\"div\", \"row\");\r\n\r\n for (let i = 0; i < columnClassNames.length; i++) {\r\n let column = createElement(\"div\", columnClassNames[i], columnValues[i]);\r\n row.appendChild(column);\r\n }\r\n\r\n return row;\r\n}\r\n\r\nfunction createElement(tagName, className, innerHTML = \"\", id = \"\") {\r\n let element = document.createElement(tagName);\r\n element.className = className;\r\n element.innerHTML = innerHTML;\r\n if (id) element.id = id;\r\n return element;\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Simulation Controls\r\n\r\ndocument.addEventListener('DOMContentLoaded', function () {\r\n const simDungeonToggle = document.getElementById('simDungeonToggle');\r\n const playerContainer = document.getElementById('playerCheckBox');\r\n\r\n function addPlayers() {\r\n const player4 = document.createElement('div');\r\n player4.classList.add('form-check');\r\n player4.innerHTML = `\r\n \r\n \r\n `;\r\n\r\n const player5 = document.createElement('div');\r\n player5.classList.add('form-check');\r\n player5.innerHTML = `\r\n \r\n \r\n `;\r\n\r\n playerContainer.appendChild(player4);\r\n playerContainer.appendChild(player5);\r\n }\r\n\r\n function removePlayers() {\r\n const player4 = document.getElementById('player4');\r\n const player5 = document.getElementById('player5');\r\n if (player4) player4.parentElement.remove();\r\n if (player5) player5.parentElement.remove();\r\n }\r\n\r\n function updatePlayerNames() {\r\n const tabLinks = document.querySelectorAll('#playerTab .nav-link');\r\n tabLinks.forEach((tabLink, index) => {\r\n const label = document.querySelector(`label[for=\"player${index + 1}\"]`);\r\n if (label) {\r\n label.textContent = tabLink.textContent.trim();\r\n }\r\n });\r\n }\r\n\r\n function updatePlayersCheckbox(isCheck) {\r\n const boxes = playerContainer.querySelectorAll('.player-checkbox');\r\n boxes.forEach((checkBox) => { checkBox.checked = isCheck });\r\n }\r\n\r\n function updateDifficultySelect(isCheck) {\r\n const difficultySelect = document.getElementById('selectDifficulty');\r\n // disable last four option\r\n if (isCheck && Number(difficultySelect.value) >= 3) {\r\n difficultySelect.value = 0;\r\n }\r\n for (let i = 3; i < difficultySelect.options.length; i++) {\r\n difficultySelect.options[i].disabled = isCheck;\r\n }\r\n }\r\n\r\n simDungeonToggle.addEventListener('change', function () {\r\n if (simDungeonToggle.checked) {\r\n addPlayers();\r\n updatePlayersCheckbox(true);\r\n updateDifficultySelect(true);\r\n } else {\r\n removePlayers();\r\n updatePlayersCheckbox(false);\r\n updateDifficultySelect(false);\r\n }\r\n updatePlayerNames();\r\n });\r\n\r\n document.getElementById('buttonSimulationSetup').addEventListener('click', function () {\r\n updatePlayerNames();\r\n });\r\n});\r\n\r\nfunction onTabChange(event) {\r\n const nextPlayerTabId = event.target.getAttribute('href').substring(7);\r\n savePreviousPlayer(currentPlayerTabId);\r\n updateNextPlayer(nextPlayerTabId);\r\n currentPlayerTabId = nextPlayerTabId;\r\n updateState();\r\n updateUI();\r\n if (Object.keys(currentSimResults).length !== 0) {\r\n showSimulationResult(currentSimResults);\r\n }\r\n\r\n updateContent();\r\n}\r\n\r\ndocument.querySelectorAll('#playerTab .nav-link').forEach(tab => {\r\n tab.addEventListener('shown.bs.tab', onTabChange);\r\n});\r\n\r\nfunction initSimulationControls() {\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n simulationTimeInput.value = 24;\r\n\r\n buttonStartSimulation.addEventListener(\"click\", (event) => {\r\n let invalidElements = document.querySelectorAll(\":invalid\");\r\n if (invalidElements.length > 0) {\r\n invalidElements.forEach((element) => element.reportValidity());\r\n return;\r\n }\r\n savePreviousPlayer(currentPlayerTabId);\r\n\r\n const simDungeonToggle = document.getElementById(\"simDungeonToggle\");\r\n const checkboxes = document.querySelectorAll('.player-checkbox');\r\n selectedPlayers = [];\r\n checkboxes.forEach(checkbox => {\r\n if (checkbox.checked) {\r\n const playerNumber = parseInt(checkbox.id.replace('player', ''));\r\n selectedPlayers.push(playerNumber);\r\n }\r\n });\r\n\r\n if (selectedPlayers.length === 0) {\r\n alert(\"You need to select at least one player to sim.\");\r\n return;\r\n }\r\n // buttonStartSimulation.disabled = true;\r\n buttonStopSimulation.style.display = 'block';\r\n startSimulation(selectedPlayers);\r\n });\r\n\r\n buttonStopSimulation.style.display = 'none';\r\n buttonStopSimulation.addEventListener(\"click\", (event) => {\r\n progressbar.style.width = \"0%\";\r\n progressbar.innerHTML = \"0%\";\r\n if (worker) {\r\n worker.terminate();\r\n }\r\n worker = new Worker(new URL(\"worker.js\", import.meta.url));\r\n\r\n if (multiWorker) {\r\n multiWorker.terminate();\r\n }\r\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n\r\n for (let worker of workerPool) {\r\n worker.worker.terminate();\r\n }\r\n\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n });\r\n}\r\n\r\nfunction startSimulation(selectedPlayers) {\r\n let playersToSim = [];\r\n for (let j = 1; j < 6; j++) {\r\n if (selectedPlayers.includes(j)) {\r\n updateNextPlayer(j);\r\n updateState();\r\n updateUI();\r\n player.hrid = \"player\" + j.toString();\r\n for (let i = 0; i < 3; i++) {\r\n if (food[i] && i < player.combatDetails.combatStats.foodSlots) {\r\n let consumable = new Consumable(food[i], triggerMap[food[i]]);\r\n player.food[i] = consumable;\r\n } else {\r\n player.food[i] = null;\r\n }\r\n\r\n if (drinks[i] && i < player.combatDetails.combatStats.drinkSlots) {\r\n let consumable = new Consumable(drinks[i], triggerMap[drinks[i]]);\r\n player.drinks[i] = consumable;\r\n } else {\r\n player.drinks[i] = null;\r\n }\r\n }\r\n\r\n for (let i = 0; i < 5; i++) {\r\n if (abilities[i] && player.intelligenceLevel >= abilitySlotsLevelRequirementList[i + 1]) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let ability = new Ability(abilities[i], Number(abilityLevelInput.value), triggerMap[abilities[i]]);\r\n player.abilities[i] = ability;\r\n } else {\r\n player.abilities[i] = null;\r\n }\r\n }\r\n\r\n playersToSim.push(structuredClone(player));\r\n }\r\n }\r\n updateNextPlayer(currentPlayerTabId);\r\n updateState();\r\n updateUI();\r\n\r\n let maxPlayerCombatLevel = 1;\r\n for (let player of playersToSim) {\r\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\r\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\r\n }\r\n\r\n for (let player of playersToSim) {\r\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\r\n const maxDebuffOnLevelGap = 0.9;\r\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\r\n\r\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\r\n\r\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\r\n }\r\n else {\r\n player.debuffOnLevelGap = 0;\r\n }\r\n }\r\n\r\n let extra = {};\r\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\r\n extra.comExp = 0;\r\n if (document.getElementById(\"comExpToggle\").checked) {\r\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\r\n }\r\n extra.comDrop = 0;\r\n if (document.getElementById(\"comDropToggle\").checked) {\r\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\r\n }\r\n\r\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\r\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\r\n let simDungeonToggle = document.getElementById(\"simDungeonToggle\");\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let dungeonSelect = document.getElementById(\"selectDungeon\");\r\n let difficultySelect = document.getElementById(\"selectDifficulty\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let simulationTimeLimit = Number(simulationTimeInput.value) * ONE_HOUR;\r\n buttonStopSimulation.style.display = 'block';\r\n if (!simAllZonesToggle.checked && !simAllSoloToggle.checked) {\r\n let zoneHrid = zoneSelect.value;\r\n let difficultyTier = Number(difficultySelect.value);\r\n if (simDungeonToggle.checked) {\r\n zoneHrid = dungeonSelect.value;\r\n }\r\n let workerMessage = {\r\n type: \"start_simulation\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n simStartTime = Date.now();\r\n if (!worker) {\r\n worker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n }\r\n worker.onmessage = onWorkerMessage;\r\n worker.postMessage(workerMessage);\r\n } else {\r\n let targetHrids = {};\r\n\r\n if (simAllZonesToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1 &&\r\n document.getElementById(a.hrid)?.checked\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n if (simAllSoloToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount === 1 &&\r\n document.getElementById(a.hrid)?.checked\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n let simHrids = Object.values(targetHrids)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .map(action => {\r\n let result = [];\r\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\r\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\r\n }\r\n return result;\r\n })\r\n .flat();\r\n\r\n let workerMessage = {\r\n type: \"start_simulation_all_zones\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zones: simHrids,\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra: extra\r\n };\r\n simStartTime = Date.now();\r\n if (!multiWorker) {\r\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n }\r\n multiWorker.onmessage = onMultiWorkerMessage;\r\n multiWorker.postMessage(workerMessage);\r\n }\r\n}\r\n\r\nfunction parsePlayerJson(playerJson, hrid) {\r\n let playerData = {\r\n hrid: hrid,\r\n food: [],\r\n drinks: [],\r\n abilities: [],\r\n ...playerJson.player,\r\n houseRooms: playerJson.houseRooms,\r\n };\r\n playerData.equipment = {};\r\n const triggerMap = playerJson.triggerMap;\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"main_hand\", \"two_hand\", \"charm\"].forEach((type) => {\r\n let currentEquipment = playerJson.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment){\r\n playerData.equipment[`/equipment_types/${type}`] = new Equipment(currentEquipment.itemHrid, currentEquipment.enhancementLevel);\r\n }\r\n });\r\n\r\n for (const foodHrid of playerJson.food[\"/action_types/combat\"]) {\r\n if (foodHrid.itemHrid === \"\") continue;\r\n const food = new Consumable(foodHrid.itemHrid, triggerMap[foodHrid.itemHrid]);\r\n playerData.food.push(food);\r\n }\r\n for (const drinkHrid of playerJson.drinks[\"/action_types/combat\"]) {\r\n if (drinkHrid.itemHrid === \"\") continue;\r\n const drink = new Consumable(drinkHrid.itemHrid, triggerMap[drinkHrid.itemHrid]);\r\n playerData.drinks.push(drink);\r\n }\r\n for (const ability of playerJson.abilities) {\r\n if (ability.abilityHrid === \"\") continue;\r\n const abilityLevel = Number(ability.level);\r\n const abilityHrid = ability.abilityHrid;\r\n if (abilityLevel > 0) {\r\n const abilityObj = new Ability(abilityHrid, abilityLevel, triggerMap[abilityHrid]);\r\n playerData.abilities.push(abilityObj);\r\n }\r\n }\r\n const player = Player.createFromDTO(playerData)\r\n player.updateCombatDetails();\r\n player.houseRooms = playerJson.houseRooms;\r\n player.achievements = playerJson.achievements ?? {};\r\n return player;\r\n}\r\n// read JSON file to simulate\r\ndocument.getElementById(\"buttonUploadJSONSimulate\").addEventListener(\"click\", (event) => {\r\n let extra = {};\r\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\r\n extra.comExp = 0;\r\n if (document.getElementById(\"comExpToggle\").checked) {\r\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\r\n }\r\n extra.comDrop = 0;\r\n if (document.getElementById(\"comDropToggle\").checked) {\r\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\r\n }\r\n\r\n let fileInput = document.getElementById(\"inputUploadJSONSimulation\");\r\n let file = fileInput.files[0];\r\n if (!file) {\r\n alert(\"Please select a file to upload.\");\r\n return;\r\n }\r\n\r\n let reader = new FileReader();\r\n reader.onload = function (event) {\r\n let fileContent = event.target.result;\r\n const jsonDataList = JSON.parse(fileContent);\r\n try {\r\n const simDataList = [];\r\n for (const key in jsonDataList) {\r\n if (jsonDataList[key].cases) {\r\n const cases = getProductCases(jsonDataList[key], jsonDataList[key].cases);\r\n simDataList.push(...cases);\r\n } else {\r\n simDataList.push(jsonDataList[key]);\r\n }\r\n }\r\n for (const key in simDataList) {\r\n const jsonData = simDataList[key];\r\n if (!jsonData || !jsonData.zone || !jsonData.players) {\r\n alert(\"Invalid JSON file format. Please ensure it contains a 'simulationResult' property.\");\r\n return;\r\n }\r\n const playersToSim = Object.values(jsonData.players).map(\r\n (player, index) => parsePlayerJson(player, `player${index + 1}`)\r\n );\r\n\r\n let maxPlayerCombatLevel = 1;\r\n for (let player of playersToSim) {\r\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\r\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\r\n }\r\n\r\n for (let player of playersToSim) {\r\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\r\n const maxDebuffOnLevelGap = 0.9;\r\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\r\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\r\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\r\n }\r\n else {\r\n player.debuffOnLevelGap = 0;\r\n }\r\n }\r\n\r\n const simulationTimeLimit = (jsonData.simulationTimeLimit || 24) * ONE_HOUR;\r\n const simName = jsonData.name || `Json ${key}`;\r\n const zoneHrid = jsonData.zone;\r\n if (zoneHrid === \"all\") {\r\n let targetHrids = {};\r\n\r\n if (simAllZonesToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n let simHrids = Object.values(targetHrids)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .map(action => {\r\n let result = [];\r\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\r\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\r\n }\r\n return result;\r\n })\r\n .flat();\r\n\r\n let workerMessage = {\r\n simulationName: simName,\r\n type: \"start_simulation_all_zones\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zones: simHrids,\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \r\n worker.onmessage = mainWorkerOnMessage;\r\n worker.postMessage(workerMessage);\r\n customAlert(\"Simulation task Created\", \"info\")\r\n workerPool.push({\r\n workerId: workerMessage.workerId,\r\n worker: worker,\r\n });\r\n } else {\r\n let difficultyTier = jsonData.difficultyTier || 0;\r\n let workerMessage = {\r\n simulationName: simName,\r\n type: \"start_simulation\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \r\n worker.onmessage = mainWorkerOnMessage;\r\n worker.postMessage(workerMessage);\r\n customAlert(\"Simulation task Created\", \"info\")\r\n workerPool.push({\r\n workerId: workerMessage.workerId,\r\n worker: worker,\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n // alert(\"Error parsing JSON file: \" + error.message);\r\n customAlert(\"Error parsing JSON file: \" + error.message, \"danger\");\r\n }\r\n }\r\n reader.readAsText(file);\r\n});\r\n\r\n\r\n// #endregion\r\n\r\n// #region WipeEvents\r\n\r\nfunction renderWipeEvents(simResult) {\r\n const selector = document.getElementById('wipeEventSelector');\r\n const logsContainer = document.getElementById('wipeLogsContainer');\r\n const waveBadge = document.getElementById('wipeWaveBadge');\r\n const timeInfo = document.getElementById('wipeTimeInfo');\r\n\r\n selector.innerHTML = '';\r\n logsContainer.innerHTML = '';\r\n\r\n if (!simResult.wipeEvents || simResult.wipeEvents.length === 0) {\r\n selector.innerHTML = ``;\r\n logsContainer.innerHTML = `
No Wipe Events Detected
`;\r\n waveBadge.textContent = '';\r\n timeInfo.textContent = '';\r\n return;\r\n }\r\n\r\n simResult.wipeEvents.forEach((event, index) => {\r\n const wave = event.wave || '?';\r\n // const time = (event.simulationTime / 1e9).toFixed(2);\r\n // const timestamp = new Date(event.timestamp).toLocaleTimeString();\r\n\r\n const option = document.createElement('option');\r\n option.value = index;\r\n option.textContent = `#${index + 1} - 波次: ${wave}`;\r\n selector.appendChild(option);\r\n });\r\n\r\n selector.value = 0;\r\n renderSelectedWipeEvent(0, simResult);\r\n\r\n selector.addEventListener('change', () => {\r\n renderSelectedWipeEvent(selector.value, simResult);\r\n });\r\n}\r\n\r\n// 渲染选中的团灭事件\r\nfunction renderSelectedWipeEvent(index, simResult) {\r\n const logsContainer = document.getElementById('wipeLogsContainer');\r\n const waveBadge = document.getElementById('wipeWaveBadge');\r\n const timeInfo = document.getElementById('wipeTimeInfo');\r\n\r\n logsContainer.innerHTML = '';\r\n\r\n if (index < 0 || index >= simResult.wipeEvents.length) {\r\n logsContainer.innerHTML = `
No Wipe Events
`;\r\n waveBadge.textContent = '';\r\n timeInfo.textContent = '';\r\n return;\r\n }\r\n\r\n const wipeEvent = simResult.wipeEvents[index];\r\n const wave = wipeEvent.wave || '?';\r\n const time = (wipeEvent.simulationTime / 1e9).toFixed(2);\r\n const timestamp = new Date(wipeEvent.timestamp).toLocaleString();\r\n\r\n waveBadge.textContent = `波次: ${wave}`;\r\n timeInfo.textContent = `模拟时间: ${time}s | 记录时间: ${timestamp}`;\r\n\r\n const logsByTime = groupLogsByTime(wipeEvent.logs);\r\n\r\n const baseTime = logsByTime.length > 0 ? logsByTime[0].time : 0;\r\n\r\n logsByTime.forEach(group => {\r\n const timeGroupElement = document.createElement('div');\r\n timeGroupElement.className = 'log-time-group';\r\n\r\n const relativeTime = (group.time - baseTime) / 1e9;\r\n\r\n // 时间标题\r\n const timeHeader = document.createElement('div');\r\n timeHeader.className = 'log-time-header';\r\n timeHeader.textContent = `[${relativeTime.toFixed(2)}s] [Wave#${group.wave}]`;\r\n timeGroupElement.appendChild(timeHeader);\r\n\r\n // 事件列表\r\n const eventsList = document.createElement('div');\r\n eventsList.className = 'log-events';\r\n\r\n const damagedPlayers = new Set();\r\n\r\n group.logs.forEach(log => {\r\n const eventElement = document.createElement('div');\r\n eventElement.className = 'log-event';\r\n\r\n damagedPlayers.add(log.target);\r\n\r\n const sourceSpan = document.createElement('span');\r\n sourceSpan.className = 'log-source';\r\n if (log.ability === \"damageOverTime\") {\r\n sourceSpan.textContent = log.target;\r\n } else if(log.source == 'UNKNOWN_SOURCE') {\r\n sourceSpan.textContent = 'UNKNOWN';\r\n } else {\r\n sourceSpan.setAttribute('data-i18n', `monsterNames.${log.source}`);\r\n sourceSpan.textContent = log.source;\r\n }\r\n\r\n const castSpan = document.createElement('span');\r\n castSpan.className = 'log-cast';\r\n castSpan.setAttribute('data-i18n', `common:cast`);\r\n castSpan.textContent = ' cast ';\r\n\r\n const abilitySpan = document.createElement('span');\r\n abilitySpan.className = 'log-ability';\r\n if (log.ability === \"autoAttack\") {\r\n abilitySpan.setAttribute('data-i18n', 'combatUnit.autoAttack');\r\n abilitySpan.textContent = 'Auto Attack';\r\n } else if (log.ability === \"physicalThorns\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.physicalThorns`);\r\n abilitySpan.textContent = 'Physical Thorns';\r\n } else if (log.ability === \"elementalThorns\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.elementalThorns`);\r\n abilitySpan.textContent = 'Elemental Thorns';\r\n } else if (log.ability === \"retaliation\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.retaliation`);\r\n abilitySpan.textContent = 'Retaliation';\r\n } else if (log.ability === \"damageOverTime\") {\r\n abilitySpan.setAttribute('data-i18n', `common:simulationResults.damageOverTime`);\r\n abilitySpan.textContent = 'Damage Over Time';\r\n } else {\r\n abilitySpan.setAttribute('data-i18n', `abilityNames.${log.ability}`);\r\n abilitySpan.textContent = log.ability;\r\n }\r\n\r\n const toSpan = document.createElement('span');\r\n toSpan.className = 'log-to';\r\n toSpan.setAttribute('data-i18n', `common:to`);\r\n toSpan.textContent = ' to ';\r\n\r\n const targetSpan = document.createElement('span');\r\n targetSpan.className = 'log-target';\r\n targetSpan.textContent = log.target;\r\n\r\n const dealDamageSpan = document.createElement('span');\r\n dealDamageSpan.className = 'log-deal-damage';\r\n dealDamageSpan.setAttribute('data-i18n', `common:dealDamage`);\r\n dealDamageSpan.textContent = ' deal damage ';\r\n\r\n const damageDoneSpan = document.createElement('span');\r\n damageDoneSpan.className = 'log-damage-done';\r\n damageDoneSpan.textContent = log.damage;\r\n if (log.isCrit) {\r\n damageDoneSpan.style.fontWeight = 'bold';\r\n damageDoneSpan.textContent += '!!!';\r\n }\r\n\r\n eventElement.appendChild(sourceSpan);\r\n eventElement.appendChild(castSpan);\r\n eventElement.appendChild(abilitySpan);\r\n eventElement.appendChild(toSpan);\r\n eventElement.appendChild(targetSpan);\r\n eventElement.appendChild(dealDamageSpan);\r\n eventElement.appendChild(damageDoneSpan);\r\n eventElement.appendChild(document.createTextNode(` , HP ${log.beforeHp} → ${log.afterHp}`));\r\n\r\n eventsList.appendChild(eventElement);\r\n });\r\n\r\n timeGroupElement.appendChild(eventsList);\r\n\r\n const lastLog = group.logs[group.logs.length - 1];\r\n const playersHpElement = document.createElement('div');\r\n\r\n const playerHpTitle = document.createElement('span');\r\n playerHpTitle.className = 'log-players-hp';\r\n playerHpTitle.setAttribute('data-i18n', `common:playersHp`);\r\n playerHpTitle.textContent = 'Players HP: ';\r\n playersHpElement.appendChild(playerHpTitle);\r\n\r\n lastLog.playersHp.forEach((player, idx) => {\r\n const playerElement = document.createElement('span');\r\n playerElement.className = 'log-player-hp';\r\n playerElement.textContent = `${player.hrid}: ${player.current}/${player.max}`;\r\n\r\n if (player.current <= 0) {\r\n playerElement.style.color = darkModeToggle.checked ? '#FF6347' : '#CC0000';\r\n } else if (damagedPlayers.has(player.hrid)) {\r\n playerElement.style.color = darkModeToggle.checked ? '#00BFFF' : '#007BFF';\r\n }\r\n\r\n if (idx > 0) {\r\n playersHpElement.appendChild(document.createTextNode(' | '));\r\n }\r\n playersHpElement.appendChild(playerElement);\r\n });\r\n const spacer = document.createElement('div');\r\n spacer.style.height = '15px';\r\n logsContainer.appendChild(spacer);\r\n timeGroupElement.appendChild(playersHpElement);\r\n logsContainer.appendChild(timeGroupElement);\r\n });\r\n\r\n // 更新汉化\r\n updateContent()\r\n}\r\n\r\n// 按时间分组日志\r\nfunction groupLogsByTime(logs) {\r\n const groups = [];\r\n let currentGroup = null;\r\n\r\n logs.forEach(log => {\r\n if (!currentGroup || currentGroup.time !== log.time) {\r\n currentGroup = {\r\n time: log.time,\r\n wave: log.wave,\r\n logs: [log]\r\n };\r\n groups.push(currentGroup);\r\n } else {\r\n currentGroup.logs.push(log);\r\n }\r\n });\r\n\r\n groups.forEach(group => {\r\n let hpMap = {};\r\n if (group.logs.length > 0) {\r\n group.logs[0].playersHp.forEach(p => {\r\n hpMap[p.hrid] = { current: p.current, max: p.max };\r\n });\r\n }\r\n group.logs.forEach(log => {\r\n if (hpMap[log.target]) {\r\n hpMap[log.target].current = log.afterHp;\r\n }\r\n });\r\n group.logs.forEach(log => {\r\n log.playersHp = Object.entries(hpMap).map(([hrid, val]) => ({\r\n hrid,\r\n current: val.current,\r\n max: val.max\r\n }));\r\n });\r\n });\r\n\r\n return groups;\r\n}\r\n\r\n// #endregion\r\n\r\n\r\n// #region Equipment Sets\r\n\r\nfunction initEquipmentSetsModal() {\r\n let equipmentSetsModal = document.getElementById(\"equipmentSetsModal\");\r\n equipmentSetsModal.addEventListener(\"show.bs.modal\", equipmentSetsModalShownHandler);\r\n\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n equipmentSetNameInput.addEventListener(\"input\", (event) => equipmentSetNameChangedHandler(event));\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.addEventListener(\"click\", createNewEquipmentSetHandler);\r\n}\r\n\r\nfunction equipmentSetsModalShownHandler() {\r\n resetNewEquipmentSetControls();\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction resetNewEquipmentSetControls() {\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n equipmentSetNameInput.value = \"\";\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.disabled = true;\r\n}\r\n\r\nfunction updateEquipmentSetList() {\r\n let newChildren = [];\r\n let equipmentSets = loadEquipmentSets();\r\n\r\n for (const equipmentSetName of Object.keys(equipmentSets)) {\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let nameCol = createElement(\"div\", \"col align-self-center\", equipmentSetName);\r\n row.appendChild(nameCol);\r\n\r\n let loadButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let loadButton = createElement(\"button\", \"btn btn-primary\", \"Load\");\r\n loadButton.setAttribute(\"data-i18n\", \"common:controls.load\");\r\n loadButton.setAttribute(\"type\", \"button\");\r\n loadButton.addEventListener(\"click\", (_) => loadEquipmentSetHandler(equipmentSetName));\r\n loadButtonCol.appendChild(loadButton);\r\n row.appendChild(loadButtonCol);\r\n\r\n let saveButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let saveButton = createElement(\"button\", \"btn btn-primary\", \"Save\");\r\n saveButton.setAttribute(\"data-i18n\", \"common:controls.save\");\r\n saveButton.setAttribute(\"type\", \"button\");\r\n saveButton.addEventListener(\"click\", (_) => updateEquipmentSetHandler(equipmentSetName));\r\n saveButtonCol.appendChild(saveButton);\r\n row.appendChild(saveButtonCol);\r\n\r\n let deleteButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let deleteButton = createElement(\"button\", \"btn btn-danger\", \"Delete\");\r\n deleteButton.setAttribute(\"data-i18n\", \"common:controls.delete\");\r\n deleteButton.setAttribute(\"type\", \"button\");\r\n deleteButton.addEventListener(\"click\", (_) => deleteEquipmentSetHandler(equipmentSetName));\r\n deleteButtonCol.appendChild(deleteButton);\r\n row.appendChild(deleteButtonCol);\r\n\r\n newChildren.push(row);\r\n }\r\n\r\n let equipmentSetList = document.getElementById(\"equipmentSetList\");\r\n equipmentSetList.replaceChildren(...newChildren);\r\n\r\n updateContent();\r\n}\r\n\r\nfunction equipmentSetNameChangedHandler(event) {\r\n let invalid = false;\r\n\r\n if (event.target.value.length == 0) {\r\n invalid = true;\r\n }\r\n\r\n let equipmentSets = loadEquipmentSets();\r\n if (equipmentSets[event.target.value]) {\r\n invalid = true;\r\n }\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.disabled = invalid;\r\n}\r\n\r\nfunction createNewEquipmentSetHandler() {\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n let equipmentSetName = equipmentSetNameInput.value;\r\n\r\n let equipmentSet = getEquipmentSetFromUI();\r\n let equipmentSets = loadEquipmentSets();\r\n equipmentSets[equipmentSetName] = equipmentSet;\r\n saveEquipmentSets(equipmentSets);\r\n\r\n resetNewEquipmentSetControls();\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction loadEquipmentSetHandler(name) {\r\n let equipmentSets = loadEquipmentSets();\r\n loadEquipmentSetIntoUI(equipmentSets[name]);\r\n}\r\n\r\nfunction updateEquipmentSetHandler(name) {\r\n let equipmentSet = getEquipmentSetFromUI();\r\n let equipmentSets = loadEquipmentSets();\r\n equipmentSets[name] = equipmentSet;\r\n saveEquipmentSets(equipmentSets);\r\n}\r\n\r\nfunction deleteEquipmentSetHandler(name) {\r\n let equipmentSets = loadEquipmentSets();\r\n delete equipmentSets[name];\r\n saveEquipmentSets(equipmentSets);\r\n\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction loadEquipmentSets() {\r\n return JSON.parse(localStorage.getItem(\"equipmentSets\")) ?? {};\r\n}\r\n\r\nfunction saveEquipmentSets(equipmentSets) {\r\n localStorage.setItem(\"equipmentSets\", JSON.stringify(equipmentSets));\r\n}\r\n\r\nfunction getEquipmentSetFromUI() {\r\n let equipmentSet = {\r\n levels: {},\r\n equipment: {},\r\n food: {},\r\n drinks: {},\r\n abilities: {},\r\n triggerMap: {},\r\n houseRooms: {},\r\n achievements: {},\r\n };\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n equipmentSet.levels[skill] = Number(levelInput.value);\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n\r\n equipmentSet.equipment[type] = {\r\n equipment: equipmentSelect.value,\r\n enhancementLevel: Number(enhancementLevelInput.value),\r\n };\r\n });\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n equipmentSet.food[i] = foodSelect.value;\r\n }\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n equipmentSet.drinks[i] = drinkSelect.value;\r\n }\r\n\r\n for (let i = 0; i < 5; i++) {\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n equipmentSet.abilities[i] = {\r\n ability: abilitySelect.value,\r\n level: Number(abilityLevelInput.value),\r\n };\r\n }\r\n\r\n equipmentSet.triggerMap = triggerMap;\r\n\r\n equipmentSet.houseRooms = player.houseRooms;\r\n equipmentSet.achievements = player.achievements;\r\n\r\n return equipmentSet;\r\n}\r\n\r\nfunction fixTriggerMap(triggerMap) {\r\n let delKeys = []\r\n for (const key of Object.keys(triggerMap)) {\r\n let err = false;\r\n if (null == triggerMap[key]) {\r\n triggerMap[key] = [];\r\n }\r\n for (const trigger of triggerMap[key]) {\r\n if (!combatTriggerConditionDetailMap[trigger.conditionHrid]) {\r\n err = true;\r\n break;\r\n }\r\n }\r\n if (err) {\r\n delKeys.push(key);\r\n }\r\n }\r\n for (const key of delKeys) {\r\n delete triggerMap[key];\r\n }\r\n}\r\n\r\nfunction loadEquipmentSetIntoUI(equipmentSet) {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !equipmentSet.levels[\"meleeLevel\"] && equipmentSet.levels[\"powerLevel\"]) {\r\n equipmentSet.levels[\"meleeLevel\"] = equipmentSet.levels[\"powerLevel\"];\r\n }\r\n levelInput.value = equipmentSet.levels[skill] ?? 1;\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n\r\n let currentEquipment = equipmentSet.equipment[type];\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.equipment;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n foodSelect.value = equipmentSet.food[i];\r\n }\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n drinkSelect.value = equipmentSet.drinks[i].replace(\"power\", \"melee\");\r\n }\r\n\r\n let hasSpecial = false;\r\n if (equipmentSet.abilities && Object.keys(equipmentSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n equipmentSet.abilities[i].ability == \"/abilities/aqua_aura\" ||\r\n equipmentSet.abilities[i].ability == \"/abilities/flame_aura\" ||\r\n equipmentSet.abilities[i].ability == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n equipmentSet.abilities[i].ability = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (equipmentSet.abilities[i].ability == \"/abilities/arcane_reflection\") {\r\n equipmentSet.abilities[i].ability = \"/abilities/retribution\";\r\n }\r\n\r\n abilitySelect.value = equipmentSet.abilities[i].ability;\r\n abilityLevelInput.value = equipmentSet.abilities[i].level;\r\n }\r\n\r\n triggerMap = equipmentSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n\r\n if (equipmentSet.houseRooms) {\r\n for (const room in equipmentSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (equipmentSet.houseRooms[room]) {\r\n field.value = equipmentSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = equipmentSet.houseRooms;\r\n } else {\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n\r\n if (equipmentSet.achievements) {\r\n for (const achievement in equipmentSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (equipmentSet.achievements[achievement]) {\r\n field.checked = true;\r\n } else {\r\n field.checked = false;\r\n }\r\n player.achievements[achievement] = field.checked;\r\n }\r\n } else {\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n refreshAchievementStatics();\r\n\r\n updateState();\r\n updateUI();\r\n\r\n updateContent();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Error Handling\r\n\r\nfunction initErrorHandling() {\r\n window.addEventListener(\"error\", (event) => {\r\n showErrorModal(event.message);\r\n });\r\n\r\n let copyErrorButton = document.getElementById(\"buttonCopyError\");\r\n copyErrorButton.addEventListener(\"click\", (event) => {\r\n let errorInput = document.getElementById(\"inputError\");\r\n navigator.clipboard.writeText(errorInput.value);\r\n });\r\n}\r\n\r\nfunction initImportExportModal() {\r\n let exportSetButton = document.getElementById(\"buttonExportSet\");\r\n exportSetButton.addEventListener(\"click\", (event) => {\r\n savePreviousPlayer(currentPlayerTabId);\r\n const activeTab = document.querySelector('#importTab .nav-link.active');\r\n if (activeTab.id === 'group-combat-tab') {\r\n doGroupExport();\r\n } else if (activeTab.id === 'solo-tab') {\r\n doSoloExport();\r\n }\r\n });\r\n\r\n let importSetButton = document.getElementById(\"buttonImportSet\");\r\n importSetButton.addEventListener(\"click\", (event) => {\r\n const activeTab = document.querySelector('#importTab .nav-link.active');\r\n if (activeTab.id === 'group-combat-tab') {\r\n doGroupImport();\r\n } else if (activeTab.id === 'solo-tab') {\r\n doSoloImport();\r\n }\r\n updateState();\r\n updateUI();\r\n resetImportInputs();\r\n });\r\n}\r\n\r\nfunction resetImportInputs() {\r\n document.getElementById('inputSetGroupCombatAll').value = '';\r\n document.getElementById('inputSetGroupCombatplayer1').value = '';\r\n document.getElementById('inputSetGroupCombatplayer2').value = '';\r\n document.getElementById('inputSetGroupCombatplayer3').value = '';\r\n document.getElementById('inputSetGroupCombatplayer4').value = '';\r\n document.getElementById('inputSetGroupCombatplayer5').value = '';\r\n document.getElementById('inputSetSolo').value = '';\r\n}\r\n\r\nfunction doGroupExport() {\r\n try {\r\n navigator.clipboard.writeText(JSON.stringify(playerDataMap)).then(() => alert(\"Current Group has been copied to clipboard.\"));\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction doSoloExport() {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let equipmentArray = [];\r\n for (const item in player.equipment) {\r\n if (player.equipment[item] != null) {\r\n equipmentArray.push({\r\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\r\n \"itemHrid\": player.equipment[item].hrid,\r\n \"enhancementLevel\": player.equipment[item].enhancementLevel\r\n });\r\n }\r\n }\r\n let playerArray = {\r\n \"attackLevel\": player.attackLevel,\r\n \"magicLevel\": player.magicLevel,\r\n \"meleeLevel\": player.meleeLevel,\r\n \"rangedLevel\": player.rangedLevel,\r\n \"defenseLevel\": player.defenseLevel,\r\n \"staminaLevel\": player.staminaLevel,\r\n \"intelligenceLevel\": player.intelligenceLevel,\r\n \"equipment\": equipmentArray\r\n };\r\n let abilitiesArray = [];\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let abilityName = document.getElementById(\"selectAbility_\" + i);\r\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\r\n }\r\n let drinksArray = [];\r\n for (let i = 0; i < drinks?.length; i++) {\r\n drinksArray.push({ \"itemHrid\": drinks[i] });\r\n }\r\n let foodArray = [];\r\n for (let i = 0; i < food?.length; i++) {\r\n foodArray.push({ \"itemHrid\": food[i] });\r\n }\r\n let state = {\r\n player: playerArray,\r\n food: { \"/action_types/combat\": foodArray },\r\n drinks: { \"/action_types/combat\": drinksArray },\r\n abilities: abilitiesArray,\r\n triggerMap: triggerMap,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n houseRooms: player.houseRooms,\r\n achievements: player.achievements\r\n };\r\n try {\r\n navigator.clipboard.writeText(JSON.stringify(state)).then(() => alert(\"Current set has been copied to clipboard.\"));\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction setPlayerData(playerId, inputElementId) {\r\n const inputElement = document.getElementById(inputElementId);\r\n const value = inputElement ? inputElement.value.trim() : \"\";\r\n\r\n // Only set the value in the map if it's not null, undefined, or empty\r\n if (value) {\r\n playerDataMap[playerId] = value;\r\n return true;\r\n }\r\n return false;\r\n}\r\n\r\nfunction doGroupImport() {\r\n let needUpdateCurrentTab = false;\r\n const value = document.getElementById(\"inputSetGroupCombatAll\")?.value || \"\";\r\n if (!value.trim()) {\r\n for (let i of ['1', '2', '3', '4', '5']) {\r\n if (setPlayerData(i, \"inputSetGroupCombatplayer\" + i) && currentPlayerTabId == i) {\r\n needUpdateCurrentTab = true;\r\n }\r\n }\r\n } else {\r\n playerDataMap = JSON.parse(value);\r\n needUpdateCurrentTab = true;\r\n }\r\n\r\n if (needUpdateCurrentTab) {\r\n updateNextPlayer(currentPlayerTabId);\r\n }\r\n}\r\n\r\nfunction doSoloImport() {\r\n let importSet = document.getElementById(\"inputSetSolo\").value;\r\n importSet = JSON.parse(importSet);\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\r\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\r\n }\r\n levelInput.value = importSet.player[skill + \"Level\"];\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.itemHrid;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\r\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\r\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\r\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\r\n if (mainhandWeapon !== undefined) {\r\n weaponSelect.value = mainhandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\r\n } else if (twohandWeapon !== undefined) {\r\n weaponSelect.value = twohandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\r\n } else {\r\n weaponSelect.value = \"\";\r\n weaponEnhancementLevelInput.value = 0;\r\n }\r\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\r\n importSet.food = importSet.food[\"/action_types/combat\"];\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n if (importSet.drinks[i] != null) {\r\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\r\n } else {\r\n drinkSelect.value = \"\";\r\n }\r\n if (importSet.food[i] != null) {\r\n foodSelect.value = importSet.food[i].itemHrid;\r\n } else {\r\n foodSelect.value = \"\";\r\n }\r\n }\r\n\r\n let hasSpecial = false;\r\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\r\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\r\n }\r\n\r\n if (importSet.abilities[i] != null) {\r\n abilitySelect.value = importSet.abilities[i].abilityHrid;\r\n abilityLevelInput.value = String(importSet.abilities[i].level);\r\n } else {\r\n abilitySelect.value = \"\";\r\n abilityLevelInput.value = \"1\";\r\n }\r\n }\r\n\r\n if (importSet.triggerMap) {\r\n triggerMap = importSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n }\r\n\r\n if (importSet.houseRooms) {\r\n for (const room in importSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (importSet.houseRooms[room]) {\r\n field.value = importSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = importSet.houseRooms;\r\n } else {\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n\r\n if (importSet.achievements) {\r\n for (const achievement in importSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (importSet.achievements[achievement]) {\r\n field.checked = true;\r\n } else {\r\n field.checked = false;\r\n }\r\n player.achievements[achievement] = field.checked;\r\n }\r\n } else {\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n refreshAchievementStatics();\r\n\r\n if (\"zone\" in importSet) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n zoneSelect.value = importSet[\"zone\"];\r\n }\r\n\r\n if (\"simulationTime\" in importSet) {\r\n let simulationDuration = document.getElementById(\"inputSimulationTime\");\r\n simulationDuration.value = importSet[\"simulationTime\"];\r\n }\r\n}\r\n\r\nfunction savePreviousPlayer(playerId) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let equipmentArray = [];\r\n for (const item in player.equipment) {\r\n if (player.equipment[item] != null) {\r\n equipmentArray.push({\r\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\r\n \"itemHrid\": player.equipment[item].hrid,\r\n \"enhancementLevel\": player.equipment[item].enhancementLevel\r\n });\r\n }\r\n }\r\n let playerArray = {\r\n \"attackLevel\": player.attackLevel,\r\n \"magicLevel\": player.magicLevel,\r\n \"meleeLevel\": player.meleeLevel,\r\n \"rangedLevel\": player.rangedLevel,\r\n \"defenseLevel\": player.defenseLevel,\r\n \"staminaLevel\": player.staminaLevel,\r\n \"intelligenceLevel\": player.intelligenceLevel,\r\n \"equipment\": equipmentArray\r\n };\r\n let abilitiesArray = [];\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let abilityName = document.getElementById(\"selectAbility_\" + i);\r\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\r\n }\r\n let drinksArray = [];\r\n for (let i = 0; i < drinks?.length; i++) {\r\n drinksArray.push({ \"itemHrid\": drinks[i] });\r\n }\r\n let foodArray = [];\r\n for (let i = 0; i < food?.length; i++) {\r\n foodArray.push({ \"itemHrid\": food[i] });\r\n }\r\n let state = {\r\n player: playerArray,\r\n food: { \"/action_types/combat\": foodArray },\r\n drinks: { \"/action_types/combat\": drinksArray },\r\n abilities: abilitiesArray,\r\n triggerMap: triggerMap,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n houseRooms: player.houseRooms,\r\n achievements: player.achievements\r\n };\r\n try {\r\n playerDataMap[playerId] = JSON.stringify(state);\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction updateNextPlayer(currentPlayerNumber) {\r\n let playerImportData = playerDataMap[currentPlayerNumber];\r\n let importSet = JSON.parse(playerImportData);\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\r\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\r\n }\r\n levelInput.value = importSet.player[skill + \"Level\"];\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.itemHrid;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\r\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\r\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\r\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\r\n if (mainhandWeapon !== undefined) {\r\n weaponSelect.value = mainhandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\r\n } else if (twohandWeapon !== undefined) {\r\n weaponSelect.value = twohandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\r\n } else {\r\n weaponSelect.value = \"\";\r\n weaponEnhancementLevelInput.value = 0;\r\n }\r\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\r\n importSet.food = importSet.food[\"/action_types/combat\"];\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n if (importSet.drinks[i] != null) {\r\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\r\n } else {\r\n drinkSelect.value = \"\";\r\n }\r\n if (importSet.food[i] != null) {\r\n foodSelect.value = importSet.food[i].itemHrid;\r\n } else {\r\n foodSelect.value = \"\";\r\n }\r\n }\r\n\r\n let hasSpecial = false;\r\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\r\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\r\n }\r\n\r\n if (importSet.abilities[i] != null) {\r\n abilitySelect.value = importSet.abilities[i].abilityHrid;\r\n abilityLevelInput.value = String(importSet.abilities[i].level);\r\n } else {\r\n abilitySelect.value = \"\";\r\n abilityLevelInput.value = \"1\";\r\n }\r\n }\r\n\r\n if (importSet.triggerMap) {\r\n triggerMap = importSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n }\r\n\r\n { // reset all houseRooms\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n if (importSet.houseRooms) {\r\n for (const room in importSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (importSet.houseRooms[room]) {\r\n field.value = importSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = importSet.houseRooms;\r\n }\r\n\r\n { // reset all achievements\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n if (importSet.achievements) {\r\n for (const achievement in importSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (importSet.achievements[achievement]) {\r\n field.checked = true;\r\n player.achievements[achievement] = true;\r\n } else {\r\n field.checked = false;\r\n player.achievements[achievement] = false;\r\n }\r\n }\r\n }\r\n refreshAchievementStatics();\r\n}\r\n\r\nfunction showErrorModal(error) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n\r\n let state = {\r\n error: error,\r\n player: player,\r\n food: food,\r\n drinks: drinks,\r\n abilities: abilities,\r\n triggerMap: triggerMap,\r\n modalTriggers: modalTriggers,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n };\r\n\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n state[\"abilityLevel\" + i] = abilityLevelInput.value;\r\n }\r\n\r\n let errorInput = document.getElementById(\"inputError\");\r\n errorInput.value = JSON.stringify(state);\r\n\r\n let errorModal = new bootstrap.Modal(document.getElementById(\"errorModal\"));\r\n errorModal.show();\r\n}\r\n\r\nwindow.prices;\r\n\r\nasync function fetchPrices() {\r\n let response = null;\r\n try {\r\n response = await fetch('https://www.milkywayidle.com/game_data/marketplace.json'\r\n , {\r\n mode: 'cors'\r\n }\r\n );\r\n if (!response.ok) {\r\n console.log('Error fetching prices');\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n\r\n if (response == null) {\r\n try {\r\n response = await fetch('https://www.milkywayidlecn.com/game_data/marketplace.json'\r\n , {\r\n mode: 'cors'\r\n }\r\n );\r\n if (!response.ok) {\r\n console.log('Error fetching prices');\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n }\r\n\r\n if (!response || !response.ok) {\r\n return;\r\n }\r\n\r\n try {\r\n\r\n let btn = document.querySelector('#buttonGetPrices');\r\n btn.style.backgroundColor = 'green';\r\n\r\n const pricesJson = await response.json();\r\n\r\n const priceTmp = pricesJson['marketData'];\r\n window.prices = {};\r\n for (const item in itemDetailMap) {\r\n const hrid = itemDetailMap[item].hrid;\r\n if (hrid in priceTmp) {\r\n window.prices[hrid] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (priceTmp[hrid]['0']) {\r\n window.prices[hrid].ask = priceTmp[hrid]['0'].a;\r\n window.prices[hrid].bid = priceTmp[hrid]['0'].b;\r\n }\r\n }\r\n } \r\n\r\n window.prices[\"/items/coin\"] = { \"ask\": 1, \"bid\": 1, \"vendor\": 1 };\r\n\r\n window.prices[\"/items/small_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n window.prices[\"/items/medium_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n window.prices[\"/items/large_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n}\r\n\r\ndocument.getElementById(\"buttonGetPrices\").onclick = async () => {\r\n await fetchPrices();\r\n};\r\n\r\ndocument.addEventListener(\"input\", (e) => {\r\n let element = e.target;\r\n if (element.tagName == \"TD\" && element.parentNode.parentNode.parentNode.classList.value.includes('profit-table')) {\r\n let tableId = element.parentNode.parentNode.parentNode.id;\r\n let row = element.parentNode.querySelectorAll('td');\r\n let item = row[0].getAttribute('data-i18n').split('.')[1];\r\n let newPrice = element.innerText;\r\n\r\n let revenueSetting = document.getElementById('selectPrices_drops').value;\r\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\r\n\r\n let expensesDifference = 0;\r\n let revenueDifference = 0;\r\n let noRngRevenueDifference = 0;\r\n\r\n if (tableId == 'expensesTable') {\r\n expensesDifference = updateTable('expensesTable', item, newPrice);\r\n if (revenueSetting == expensesSetting) {\r\n revenueDifference = updateTable('revenueTable', item, newPrice);\r\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\r\n }\r\n if (window.prices) {\r\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (expensesSetting == 'bid') {\r\n window.prices[item]['bid'] = newPrice;\r\n } else {\r\n window.prices[item]['ask'] = newPrice;\r\n }\r\n }\r\n } else {\r\n revenueDifference = updateTable('revenueTable', item, newPrice);\r\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\r\n if (revenueSetting == expensesSetting) {\r\n expensesDifference = updateTable('expensesTable', item, newPrice);\r\n }\r\n if (window.prices) {\r\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (revenueSetting == 'bid') {\r\n window.prices[item]['bid'] = newPrice;\r\n } else {\r\n window.prices[item]['ask'] = newPrice;\r\n }\r\n }\r\n }\r\n\r\n window.expenses += expensesDifference;\r\n document.getElementById('expensesSpan').innerText = window.expenses.toLocaleString();\r\n window.revenue += revenueDifference;\r\n document.getElementById('revenueSpan').innerText = window.revenue.toLocaleString();\r\n window.noRngRevenue += noRngRevenueDifference;\r\n document.getElementById('noRngRevenueSpan').innerText = window.noRngRevenue.toLocaleString();\r\n\r\n window.profit = window.revenue - window.expenses;\r\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\r\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\r\n window.noRngProfit = window.noRngRevenue - window.expenses;\r\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\r\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\r\n }\r\n});\r\n\r\nfunction updateTable(tableId, item, price) {\r\n let row = document.querySelector('#' + tableId + ' .' + CSS.escape(item));\r\n if (row == null) {\r\n return 0;\r\n }\r\n\r\n row = row.querySelectorAll('td');\r\n let priceTd = row[1];\r\n let amountTd = row[2];\r\n let totalTd = row[3];\r\n let oldTotal = totalTd.innerText;\r\n let newTotal = price * amountTd.innerText;\r\n\r\n if (priceTd.innerText != price) {\r\n priceTd.innerText = price;\r\n }\r\n totalTd.innerText = newTotal;\r\n\r\n return newTotal - oldTotal;\r\n}\r\n\r\n// #endregion\r\n\r\nfunction initPatchNotes() {\r\n const patchNotesRows = document.getElementById(\"patchNotes\");\r\n for (const pn in patchNote) {\r\n const patchNoteContainer = document.createElement(\"div\");\r\n patchNotesRows.setAttribute('class', 'col-12 mb-4');\r\n\r\n const patchNoteElement = document.createElement(\"h6\");\r\n patchNoteElement.innerHTML = pn;\r\n const patchNoteList = document.createElement(\"ul\");\r\n for (const note of patchNote[pn]) {\r\n const noteElement = document.createElement(\"li\");\r\n noteElement.innerHTML = note;\r\n patchNoteList.appendChild(noteElement);\r\n }\r\n patchNoteContainer.appendChild(patchNoteElement);\r\n patchNoteContainer.appendChild(patchNoteList);\r\n\r\n patchNotesRows.appendChild(patchNoteContainer);\r\n }\r\n}\r\n\r\nfunction initExtraBuffSection() {\r\n // mooPass\r\n let mooPassToggle = document.getElementById(\"mooPassToggle\");\r\n let mooPass = localStorage.getItem('mooPass');\r\n if (mooPass) {\r\n mooPassToggle.checked = Boolean(mooPass);\r\n }\r\n mooPassToggle.onchange = () => {\r\n localStorage.setItem('mooPass', mooPassToggle.checked);\r\n }\r\n \r\n // comExp\r\n let comExpToggle = document.getElementById(\"comExpToggle\");\r\n let comExpInput = document.getElementById(\"comExpInput\");\r\n let comExp = localStorage.getItem('comExp');\r\n if (comExp) {\r\n let comExpNumber = Number(comExp);\r\n if (comExpNumber > 0) {\r\n comExpToggle.checked = true;\r\n comExpInput.value = comExpNumber;\r\n } else {\r\n comExpToggle.checked = false;\r\n comExpInput.disabled = true;\r\n }\r\n }\r\n const updateComExp = () => {\r\n if (comExpToggle.checked) {\r\n let comExp = Number(comExpInput.value);\r\n localStorage.setItem('comExp', comExp); \r\n comExpInput.disabled = false;\r\n } else {\r\n localStorage.setItem('comExp', 0);\r\n comExpInput.disabled = true;\r\n }\r\n }\r\n comExpToggle.onchange = updateComExp;\r\n comExpInput.onchange = updateComExp;\r\n\r\n // comDrop\r\n let comDropToggle = document.getElementById(\"comDropToggle\");\r\n let comDropInput = document.getElementById(\"comDropInput\");\r\n let comDrop = localStorage.getItem('comDrop');\r\n if (comDrop) {\r\n let comDropNumber = Number(comDrop);\r\n if (comDropNumber > 0) {\r\n comDropToggle.checked = true;\r\n comDropInput.value = comDropNumber;\r\n } else {\r\n comDropToggle.checked = false;\r\n comDropInput.disabled = true;\r\n }\r\n }\r\n const updateComDrop = () => {\r\n if (comDropToggle.checked) {\r\n let comDrop = Number(comDropInput.value);\r\n localStorage.setItem('comDrop', comDrop); \r\n comDropInput.disabled = false;\r\n } else {\r\n localStorage.setItem('comDrop', 0);\r\n comDropInput.disabled = true;\r\n }\r\n }\r\n comDropToggle.onchange = updateComDrop;\r\n comDropInput.onchange = updateComDrop;\r\n}\r\n\r\n\r\nfunction updateState() {\r\n updateEquipmentState();\r\n updateLevels();\r\n updateFoodState();\r\n updateDrinksState();\r\n updateAbilityState();\r\n}\r\n\r\nfunction updateUI() {\r\n updateCombatStatsUI();\r\n updateFoodUI();\r\n updateDrinksUI();\r\n updateAbilityUI();\r\n\r\n updateContent();\r\n}\r\n\r\nconst darkModeToggle = document.getElementById('darkModeToggle');\r\nconst body = document.body;\r\n\r\nif (localStorage.getItem('darkModeEnabled') === 'true') {\r\n body.classList.add('dark-mode');\r\n const tables = document.getElementsByClassName('profit-table');\r\n for (const table of tables) {\r\n table.classList.toggle('table-striped');\r\n }\r\n darkModeToggle.checked = true;\r\n}\r\n\r\ndarkModeToggle.addEventListener('change', () => {\r\n body.classList.toggle('dark-mode');\r\n const tables = document.getElementsByClassName('profit-table');\r\n for (const table of tables) {\r\n table.classList.toggle('table-striped');\r\n }\r\n localStorage.setItem('darkModeEnabled', darkModeToggle.checked);\r\n});\r\n\r\nfunction updateContent() {\r\n document.querySelectorAll('[data-i18n]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n');\r\n if (key) {\r\n element.textContent = i18next.t(key);\r\n }\r\n });\r\n\r\n document.querySelectorAll('[data-i18n-placeholder]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n-placeholder');\r\n if (key) {\r\n element.placeholder = i18next.t(key);\r\n }\r\n });\r\n\r\n document.querySelectorAll('option[data-i18n]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n');\r\n if (key) {\r\n element.textContent = i18next.t(key);\r\n }\r\n });\r\n}\r\n\r\ninitEquipmentSection();\r\ninitHouseRoomsModal();\r\ninitAchievementsModal();\r\ninitLevelSection();\r\ninitFoodSection();\r\ninitDrinksSection();\r\ninitAbilitiesSection();\r\ninitZones();\r\ninitDungeons();\r\ninitTriggerModal();\r\ninitSimulationControls();\r\ninitEquipmentSetsModal();\r\ninitErrorHandling();\r\ninitImportExportModal();\r\ninitDamageDoneTaken();\r\ninitPatchNotes();\r\ninitExtraBuffSection();\r\n\r\nupdateState();\r\nupdateUI();\r\n"],"names":[],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"bundle.js","mappings":";;;;;;;;;;;;;;;;;AAA0B;AACkC;AAC5B;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,wDAAgB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,6CAAI;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;ACxNG;AACkD;AACR;AACpE;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,gEAAwB;AAChE;AACA,0CAA0C,4DAAoB;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,WAAW;;;;;;;;;;;;;;AC1B1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,IAAI,EAAC;;;;;;;;;;;;;;;ACdpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wDAAwD;AACtF,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,mBAAmB;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;;AC/gBA;AAC4B;AACtB;AAChC;AACA;AACA;AACA;AACA;AACA,6BAA6B,qDAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;ACtF4B;AACmD;AACzG;AACA;AACA;AACA;AACA,uBAAuB,qDAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,iFAAoC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS,EAAC;;;;;;;;;;;;;;;;;AC/CC;AACsC;AAChE;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,0DAAkB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS;;;;;;;;;;;;;;;;;;;;AC7BQ;AACM;AACA;AACF;AACA;AACI;AACxC;AACA,qBAAqB,mDAAU;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,kDAAS;AACrD;AACA;AACA,qDAAqD,mDAAU;AAC/D,2DAA2D,mDAAU;AACrE,qEAAqE,gDAAO;AAC5E;AACA;AACA,2CAA2C,kDAAS;AACpD;AACA,SAAS;AACT;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,MAAM,EAAC;;;;;;;;;;;;;;;;ACvLsE;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,wEAAgC;AAC5C;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UCjLvB;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;UAEA;UACA;;;;;WCzBA;WACA;WACA;WACA;WACA,yCAAyC,wCAAwC;WACjF;WACA;WACA;;;;;WCPA;WACA;WACA;WACA;WACA;;;;;WCJA;WACA;WACA;WACA;WACA,GAAG;WACH;WACA;WACA,CAAC;;;;;WCPD;;;;;WCAA;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D;;;;;WCNA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;;;;WClBA;;WAEA;WACA;WACA;WACA;WACA;WACA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrBuD;AACN;AAC2B;AACN;AACU;AAC7B;AACM;AACN;AACyD;AACF;AACE;AACA;AAClC;AACc;AACN;AACE;AACF;AACG;AACF;AACnF;AAC0C;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,2FAA4B;AAC5D,qCAAqC,qHAAiC;AACtE;AACA;AACA;AACA,iBAAiB,kEAAM;AACvB;AACA,IAAI,SAAI;AACR,IAAI,WAAM;AACV,IAAI,cAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA,sCAAsC,qEAAa;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,mCAAmC,0EAAkB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,iFAAkB;AAClD;AACA,4FAA4F,eAAe;AAC3G;AACA;AACA;AACA,uDAAuD,eAAe;AACtE,6BAA6B,KAAK,GAAG,MAAM;AAC3C;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,iFAAkB;AAClD;AACA,sCAAsC,6EAAoB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,eAAe;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oEAAoE,eAAe;AACnF,mCAAmC,eAAe;AAClD;AACA;AACA,gGAAgG,eAAe;AAC/G;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,gCAAgC,eAAe;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,iBAAiB;AACpD;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,iDAAiD,iBAAiB;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,qEAAa;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,qEAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,qEAAS;AACvD,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,6EAAoB;AACvD;AACA;AACA,qBAAqB,4EAAmB;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,sCAAsC,qEAAa;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,SAAI;AACZ,YAAY,SAAI,mBAAmB,SAAI;AACvC,2BAA2B,qEAAa,CAAC,SAAI;AAC7C,uBAAuB,SAAI;AAC3B;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,qFAAqF,SAAI;AACzF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,uCAAuC,qEAAa;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,WAAM;AACd,YAAY,WAAM,mBAAmB,WAAM;AAC3C,2BAA2B,qEAAa,CAAC,WAAM;AAC/C,uBAAuB,WAAM;AAC7B;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,sFAAsF,WAAM;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D,UAAU;AACV,0CAA0C,wEAAgB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,cAAS;AACjB,YAAY,cAAS,mBAAmB,cAAS;AACjD,8BAA8B,wEAAgB,CAAC,cAAS;AACxD,uBAAuB,cAAS;AAChC;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,4DAA4D,yFAAgC;AAC5F,2DAA2D,yFAAgC;AAC3F,4DAA4D,yFAAgC,YAAY,cAAS;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAS;AACjC,IAAI,cAAS,iBAAiB,cAAS;AACvC,IAAI,cAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,cAAS;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,SAAI;AAChC;AACA;AACA,4BAA4B,WAAM;AAClC;AACA;AACA,4BAA4B,cAAS;AACrC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,qEAAa;AACrD,MAAM;AACN,wCAAwC,wEAAgB;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,yFAAgC;AAC5C;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,wFAAgC;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,wFAAgC;AACrD;AACA;AACA;AACA,mCAAmC,uFAA+B;AAClE,MAAM;AACN,mCAAmC,uFAA+B;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uFAA+B;AACnD;AACA,qEAAqE,yFAAgC;AACrG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,wEAAe;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2GAA2G,WAAW;AACtH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,WAAW;AAC5C,yDAAyD,WAAW;AACpE;AACA;AACA;AACA,gEAAgE,WAAW;AAC3E;AACA;AACA,qGAAqG,WAAW;AAChH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qFAAqF,WAAW;AAChG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4GAA4G,WAAW;AACvH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,WAAW;AAC7C,0DAA0D,WAAW;AACrE;AACA;AACA;AACA,iEAAiE,WAAW;AAC5E;AACA;AACA,sGAAsG,WAAW;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sFAAsF,WAAW;AACjG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,OAAO;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uBAAuB;AAC3C,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,+EAAsB;AAClC,+BAA+B,+EAAsB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,iJAAiJ;AAC9L;AACA,gBAAgB,+EAAsB;AACtC,mCAAmC,+EAAsB;AACzD;AACA;AACA;AACA,qDAAqD,uIAAuI;AAC5L;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,+BAA+B;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,UAAU,kCAAkC;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uEAAuE,oDAAoD;AAC3H,iEAAiE,oDAAoD;AACrH,iFAAiF,oDAAoD;AACrI;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,eAAe;AACrC;AACA;AACA;AACA;AACA,0BAA0B,mBAAmB;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,SAAS;AAC3D;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,KAAK;AACL,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,iBAAiB;AACrC;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,+CAA+C,kBAAkB;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,+EAAsB;AACvC;AACA;AACA;AACA,SAAS;AACT;AACA,UAAU,kCAAkC,qEAAqE;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2EAA2E,wEAAgB;AAC3F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa,qEAAa;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,qEAAa;AACjC,iCAAiC,qEAAa;AAC9C;AACA,kBAAkB,SAAS,wEAAgB;AAC3C,iCAAiC,wEAAgB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,qEAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kFAAkF,IAAI;AACtF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,QAAQ;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,QAAQ;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wEAAgB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,6BAA6B;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qEAAqE,UAAU;AAC/E;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,sCAAsC,4BAA4B;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,qCAAqC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC,2FAA4B;AAChE;AACA;AACA;AACA;AACA,yCAAyC,qHAAiC;AAC1E;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,SAAI;AACxB,yCAAyC,sEAAU,CAAC,SAAI,gBAAgB,SAAI;AAC5E;AACA,kBAAkB;AAClB;AACA;AACA;AACA,oBAAoB,WAAM;AAC1B,yCAAyC,sEAAU,CAAC,WAAM,gBAAgB,WAAM;AAChF;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,cAAS,mCAAmC,yFAAgC;AAChG;AACA,sCAAsC,mEAAO,CAAC,cAAS,iDAAiD,cAAS;AACjH;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,oDAAoD;AACxE;AACA;AACA;AACA;AACA;AACA,wCAAwC,qHAAiC;AACzE;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;AACA;AACA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,wCAAwC;AACrF,kCAAkC,uDAAuD;AACzF;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,qHAAiC;AAC9E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,KAAK,SAAS,qEAAS;AAC5E;AACA,KAAK;AACL;AACA;AACA;AACA,yBAAyB,sEAAU;AACnC;AACA;AACA;AACA;AACA,0BAA0B,sEAAU;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,mEAAO;AAC1C;AACA;AACA;AACA,mBAAmB,kEAAM;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wEAAwE,UAAU;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,IAAI;AAC7D;AACA;AACA;AACA;AACA;AACA,sCAAsC,wEAAe;AACrD;AACA;AACA;AACA;AACA;AACA,4CAA4C,0BAA0B;AACtE;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,wCAAwC;AACjG,8CAA8C,uDAAuD;AACrG;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,oDAAoD;AACpF;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,WAAW,QAAQ,KAAK;AACzD;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,KAAK;AACxC,oCAAoC,KAAK,YAAY,UAAU;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,wBAAwB,WAAW,WAAW;AACnF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd,qEAAqE,WAAW;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd,sEAAsE,YAAY;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sEAAsE,cAAc,IAAI,YAAY;AACpG;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,YAAY,IAAI,eAAe,GAAG,WAAW;AACxF;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,kCAAkC;AAClC,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB,qBAAqB;AACrB,gBAAgB;AAChB,kBAAkB;AAClB,qBAAqB;AACrB,sBAAsB;AACtB,sBAAsB;AACtB,wBAAwB;AACxB;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,uFAA+B;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,SAAI;AAClB,gBAAgB,WAAM;AACtB,mBAAmB,cAAS;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,qEAAa;AACxC,yBAAyB,qEAAa;AACtC;AACA,wCAAwC,gCAAgC,qEAAa;AACrF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yCAAyC;AACzC;AACA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;AACA;AACA;AACA,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,sBAAsB,4GAAmD;AACzE;AACA,aAAa;AACb;AACA;AACA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,6CAAS;AAC9B;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,6CAAS;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sources":["webpack://mwicombatsimulator/./src/combatsimulator/ability.js","webpack://mwicombatsimulator/./src/combatsimulator/achievement.js","webpack://mwicombatsimulator/./src/combatsimulator/buff.js","webpack://mwicombatsimulator/./src/combatsimulator/combatUnit.js","webpack://mwicombatsimulator/./src/combatsimulator/consumable.js","webpack://mwicombatsimulator/./src/combatsimulator/equipment.js","webpack://mwicombatsimulator/./src/combatsimulator/houseRoom.js","webpack://mwicombatsimulator/./src/combatsimulator/player.js","webpack://mwicombatsimulator/./src/combatsimulator/trigger.js","webpack://mwicombatsimulator/webpack/bootstrap","webpack://mwicombatsimulator/webpack/runtime/define property getters","webpack://mwicombatsimulator/webpack/runtime/get javascript chunk filename","webpack://mwicombatsimulator/webpack/runtime/global","webpack://mwicombatsimulator/webpack/runtime/hasOwnProperty shorthand","webpack://mwicombatsimulator/webpack/runtime/make namespace object","webpack://mwicombatsimulator/webpack/runtime/publicPath","webpack://mwicombatsimulator/webpack/runtime/jsonp chunk loading","webpack://mwicombatsimulator/./src/main.js"],"sourcesContent":["import Buff from \"./buff\";\r\nimport abilityDetailMap from \"./data/abilityDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nconst abilityFromCombatStat = {\r\n \"blaze\":\r\n {\r\n \"hrid\": \"/abilities/blaze\",\r\n \"name\": \"Blaze\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"allEnemies\",\r\n \"effectType\": \"/ability_effect_types/damage\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"/damage_types/fire\",\r\n \"baseDamageFlat\": 0,\r\n \"baseDamageFlatLevelBonus\": 0.0,\r\n \"baseDamageRatio\": 0.3,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/number_of_active_units\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n },\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/current_hp\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n }\r\n ],\r\n },\r\n \"bloom\":\r\n {\r\n \"hrid\": \"/abilities/bloom\",\r\n \"name\": \"Bloom\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"lowestHpAlly\",\r\n \"effectType\": \"/ability_effect_types/heal\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"\",\r\n \"baseDamageFlat\": 10,\r\n \"baseDamageFlatLevelBonus\": 0,\r\n \"baseDamageRatio\": 0.15,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_allies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/lowest_hp_percentage\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/less_than_equal\",\r\n \"value\": 100\r\n }\r\n ],\r\n }\r\n}\r\n\r\nclass Ability {\r\n constructor(hrid, level = 1, triggers = null) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameAbility = abilityDetailMap[hrid];\r\n if (!gameAbility) {\r\n gameAbility = abilityFromCombatStat[hrid];\r\n }\r\n if (!gameAbility) {\r\n throw new Error(\"No ability found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.manaCost = gameAbility.manaCost;\r\n this.cooldownDuration = gameAbility.cooldownDuration;\r\n this.castDuration = gameAbility.castDuration;\r\n this.isSpecialAbility = gameAbility.isSpecialAbility;\r\n\r\n this.abilityEffects = [];\r\n\r\n for (const effect of gameAbility.abilityEffects) {\r\n let abilityEffect = {\r\n targetType: effect.targetType,\r\n effectType: effect.effectType,\r\n combatStyleHrid: effect.combatStyleHrid,\r\n damageType: effect.damageType,\r\n damageFlat: effect.baseDamageFlat + (this.level - 1) * effect.baseDamageFlatLevelBonus,\r\n damageRatio: effect.baseDamageRatio + (this.level - 1) * effect.baseDamageRatioLevelBonus,\r\n bonusAccuracyRatio: effect.bonusAccuracyRatio + (this.level - 1) * effect.bonusAccuracyRatioLevelBonus,\r\n damageOverTimeRatio: effect.damageOverTimeRatio,\r\n damageOverTimeDuration: effect.damageOverTimeDuration,\r\n armorDamageRatio: effect.armorDamageRatio + (this.level - 1) * effect.armorDamageRatioLevelBonus,\r\n hpDrainRatio: effect.hpDrainRatio,\r\n pierceChance: effect.pierceChance,\r\n blindChance: effect.blindChance,\r\n blindDuration: effect.blindDuration,\r\n silenceChance: effect.silenceChance,\r\n silenceDuration: effect.silenceDuration,\r\n stunChance: effect.stunChance,\r\n stunDuration: effect.stunDuration,\r\n spendHpRatio: effect.spendHpRatio,\r\n buffs: null,\r\n };\r\n if (effect.buffs) {\r\n abilityEffect.buffs = [];\r\n for (const buff of effect.buffs) {\r\n abilityEffect.buffs.push(new Buff(buff, this.level));\r\n }\r\n }\r\n this.abilityEffects.push(abilityEffect);\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameAbility.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let ability = new Ability(dto.hrid, dto.level, triggers);\r\n\r\n return ability;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n\r\n if (source.isSilenced) {\r\n return false;\r\n }\r\n\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = this.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Ability;\r\n","import Buff from \"./buff\";\r\nimport achievementTierDetailMap from \"./data/achievementTierDetailMap.json\";\r\nimport achievementDetailMap from \"./data/achievementDetailMap.json\";\r\n\r\nclass Achievement {\r\n constructor(achievements) {\r\n this.achievements = achievements;\r\n this.buffs = [];\r\n\r\n for(const tier of Object.values(achievementTierDetailMap)) {\r\n let isGetAll = true;\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid)\r\n for(const achievement of Object.values(detailMap)) {\r\n if(!this.achievements[achievement.hrid] || this.achievements[achievement.hrid] == false) {\r\n isGetAll = false;\r\n break;\r\n }\r\n }\r\n if(isGetAll) {\r\n let buff = new Buff(tier.buff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default Achievement;","class Buff {\r\n startTime;\r\n\r\n constructor(buff, level = 1) {\r\n this.uniqueHrid = buff.uniqueHrid;\r\n this.typeHrid = buff.typeHrid;\r\n this.ratioBoost = buff.ratioBoost + (level - 1) * buff.ratioBoostLevelBonus;\r\n this.flatBoost = buff.flatBoost + (level - 1) * buff.flatBoostLevelBonus;\r\n this.duration = buff.duration;\r\n this.multiplierForSkillHrid = buff.multiplierForSkillHrid ?? \"\";\r\n this.multiplierPerSkillLevel = buff.multiplierPerSkillLevel ?? 0;\r\n }\r\n}\r\n\r\nexport default Buff;\r\n","class CombatUnit {\r\n isPlayer;\r\n isStunned = false;\r\n stunExpireTime = null;\r\n isBlinded = false;\r\n blindExpireTime = null;\r\n isSilenced = false;\r\n silenceExpireTime = null;\r\n\r\n isOutOfMana = false;\r\n\r\n // Base levels which don't change after initialization\r\n staminaLevel = 1;\r\n intelligenceLevel = 1;\r\n attackLevel = 1;\r\n meleeLevel = 1;\r\n defenseLevel = 1;\r\n rangedLevel = 1;\r\n magicLevel = 1;\r\n\r\n experience = 0;\r\n experienceRate = 0;\r\n enrageTime = 0;\r\n\r\n abilities = [null, null, null, null];\r\n food = [null, null, null];\r\n drinks = [null, null, null];\r\n houseRooms = [];\r\n achievements = null;\r\n dropTable = [];\r\n rareDropTable = [];\r\n abilityManaCosts = new Map();\r\n\r\n // Calculated combat stats including temporary buffs\r\n combatDetails = {\r\n staminaLevel: 1,\r\n intelligenceLevel: 1,\r\n attackLevel: 1,\r\n meleeLevel: 1,\r\n defenseLevel: 1,\r\n rangedLevel: 1,\r\n magicLevel: 1,\r\n maxHitpoints: 110,\r\n currentHitpoints: 110,\r\n maxManapoints: 110,\r\n currentManapoints: 110,\r\n stabAccuracyRating: 11,\r\n slashAccuracyRating: 11,\r\n smashAccuracyRating: 11,\r\n rangedAccuracyRating: 11,\r\n magicAccuracyRating: 11,\r\n stabMaxDamage: 11,\r\n slashMaxDamage: 11,\r\n smashMaxDamage: 11,\r\n rangedMaxDamage: 11,\r\n magicMaxDamage: 11,\r\n stabEvasionRating: 11,\r\n slashEvasionRating: 11,\r\n smashEvasionRating: 11,\r\n rangedEvasionRating: 11,\r\n magicEvasionRating: 11,\r\n defensiveMaxDamage: 0,\r\n totalArmor: 0.2,\r\n totalWaterResistance: 0.4,\r\n totalNatureResistance: 0.4,\r\n totalFireResistance: 0.4,\r\n abilityHaste: 0,\r\n tenacity: 0,\r\n totalThreat: 100,\r\n combatStats: {\r\n combatStyleHrid: \"/combat_styles/smash\",\r\n damageType: \"/damage_types/physical\",\r\n attackInterval: 3000000000,\r\n autoAttackDamage: 0,\r\n abilityDamage: 0,\r\n criticalRate: 0,\r\n criticalDamage: 0,\r\n stabAccuracy: 0,\r\n slashAccuracy: 0,\r\n smashAccuracy: 0,\r\n rangedAccuracy: 0,\r\n magicAccuracy: 0,\r\n stabDamage: 0,\r\n slashDamage: 0,\r\n smashDamage: 0,\r\n rangedDamage: 0,\r\n magicDamage: 0,\r\n defensiveDamage: 0,\r\n taskDamage: 0,\r\n physicalAmplify: 0,\r\n waterAmplify: 0,\r\n natureAmplify: 0,\r\n fireAmplify: 0,\r\n healingAmplify: 0,\r\n physicalThorns: 0,\r\n elementalThorns: 0,\r\n maxHitpoints: 0,\r\n maxManapoints: 0,\r\n stabEvasion: 0,\r\n slashEvasion: 0,\r\n smashEvasion: 0,\r\n rangedEvasion: 0,\r\n magicEvasion: 0,\r\n armor: 0,\r\n waterResistance: 0,\r\n natureResistance: 0,\r\n fireResistance: 0,\r\n lifeSteal: 0,\r\n hpRegenPer10: 0.01,\r\n mpRegenPer10: 0.01,\r\n combatDropRate: 0,\r\n combatDropQuantity: 0,\r\n combatRareFind: 0,\r\n combatExperience: 0,\r\n foodSlots: 1,\r\n drinkSlots: 1,\r\n armorPenetration: 0,\r\n waterPenetration: 0,\r\n naturePenetration: 0,\r\n firePenetration: 0,\r\n manaLeech: 0,\r\n castSpeed: 0,\r\n threat: 100,\r\n parry: 0,\r\n mayhem: 0,\r\n pierce: 0,\r\n curse: 0,\r\n ripple: 0,\r\n bloom: 0,\r\n blaze: 0,\r\n weaken: 0,\r\n fury: 0,\r\n foodHaste: 0,\r\n drinkConcentration: 0,\r\n damageTaken: 0,\r\n attackSpeed: 0,\r\n armorDamageRatio: 0,\r\n hpDrainRatio: 0,\r\n primaryTraining: \"\",\r\n focusTraining: \"\",\r\n staminaExperience: 0,\r\n intelligenceExperience: 0,\r\n attackExperience: 0,\r\n defenseExperience: 0,\r\n meleeExperience: 0,\r\n rangedExperience: 0,\r\n magicExperience: 0,\r\n retaliation: 0,\r\n },\r\n };\r\n combatBuffs = {};\r\n permanentBuffs = {};\r\n zoneBuffs = {};\r\n extraBuffs = {};\r\n\r\n constructor() { }\r\n\r\n updateCombatDetails() {\r\n if (this.isPlayer) {\r\n if (this.combatDetails.combatStats.hpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01 + this.combatDetails.combatStats.hpRegenPer10;\r\n }\r\n if (this.combatDetails.combatStats.mpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01 + this.combatDetails.combatStats.mpRegenPer10;\r\n }\r\n }\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((stat) => {\r\n this.combatDetails[stat + \"Level\"] = this[stat + \"Level\"];\r\n let boosts = this.getBuffBoosts(\"/buff_types/\" + stat + \"_level\");\r\n boosts.forEach((buff) => {\r\n this.combatDetails[stat + \"Level\"] += (this[stat + \"Level\"] * buff.ratioBoost);\r\n this.combatDetails[stat + \"Level\"] += buff.flatBoost;\r\n });\r\n });\r\n\r\n this.combatDetails.maxHitpoints = Math.floor\r\n (10 * (10 + this.combatDetails.staminaLevel) + this.combatDetails.combatStats.maxHitpoints);\r\n this.combatDetails.maxManapoints = Math.floor\r\n (10 * (10 + this.combatDetails.intelligenceLevel) + this.combatDetails.combatStats.maxManapoints);\r\n\r\n let accuracyRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_accuracy\").ratioBoost;\r\n let damageRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_damage\").ratioBoost;\r\n // if (accuracyRatioBoostFromFury > 0) {\r\n // console.log(\"Fury Boost: \" + accuracyRatioBoostFromFury);\r\n // }\r\n\r\n let accuracyRatioBoost = this.getBuffBoost(\"/buff_types/accuracy\").ratioBoost;\r\n let damageRatioBoost = this.getBuffBoost(\"/buff_types/damage\").ratioBoost;\r\n\r\n [\"stab\", \"slash\", \"smash\"].forEach((style) => {\r\n this.combatDetails[style + \"AccuracyRating\"] =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Accuracy\"]) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails[style + \"MaxDamage\"] =\r\n (10 + this.combatDetails.meleeLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Damage\"]) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n let baseEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats[style + \"Evasion\"]);\r\n this.combatDetails[style + \"EvasionRating\"] = baseEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails[style + \"EvasionRating\"] += boost.flatBoost;\r\n this.combatDetails[style + \"EvasionRating\"] += baseEvasion * boost.ratioBoost;\r\n }\r\n });\r\n\r\n this.combatDetails.defensiveMaxDamage = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.defensiveDamage);\r\n\r\n // when equiped bulwark\r\n if (this.equipment?.['/equipment_types/two_hand']?.hrid.includes(\"bulwark\")) {\r\n this.combatDetails.smashMaxDamage += this.combatDetails.defensiveMaxDamage;\r\n }\r\n\r\n this.combatDetails.rangedAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.rangedAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.rangedMaxDamage =\r\n (10 + this.combatDetails.rangedLevel) *\r\n (1 + this.combatDetails.combatStats.rangedDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseRangedEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.rangedEvasion);\r\n this.combatDetails.rangedEvasionRating = baseRangedEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.rangedEvasionRating += boost.flatBoost;\r\n this.combatDetails.rangedEvasionRating += baseRangedEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.damageTaken = this.getBuffBoost(\"/buff_types/damage_taken\").flatBoost;\r\n // if (this.combatDetails.combatStats.damageTaken > 0) {\r\n // console.log(\"Damage taken: \" + this.combatDetails.combatStats.damageTaken);\r\n // }\r\n\r\n this.combatDetails.magicAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.magicAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.magicMaxDamage =\r\n (10 + this.combatDetails.magicLevel) *\r\n (1 + this.combatDetails.combatStats.magicDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseMagicEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.magicEvasion);\r\n this.combatDetails.magicEvasionRating = baseMagicEvasion;\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.magicEvasionRating += boost.flatBoost;\r\n this.combatDetails.magicEvasionRating += baseMagicEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.physicalAmplify += this.getBuffBoost(\"/buff_types/physical_amplify\").flatBoost;\r\n this.combatDetails.combatStats.waterAmplify += this.getBuffBoost(\"/buff_types/water_amplify\").flatBoost;\r\n this.combatDetails.combatStats.natureAmplify += this.getBuffBoost(\"/buff_types/nature_amplify\").flatBoost;\r\n this.combatDetails.combatStats.fireAmplify += this.getBuffBoost(\"/buff_types/fire_amplify\").flatBoost;\r\n\r\n this.combatDetails.combatStats.attackInterval /= (1 + (this.combatDetails.attackLevel / 2000));\r\n\r\n let baseAttackSpeed = this.combatDetails.combatStats.attackSpeed;\r\n this.combatDetails.combatStats.attackInterval /= (1 + baseAttackSpeed);\r\n let attackIntervalBoosts = this.getBuffBoosts(\"/buff_types/attack_speed\");\r\n let attackIntervalRatioBoost = attackIntervalBoosts\r\n .map((boost) => boost.ratioBoost)\r\n .reduce((prev, cur) => prev + cur, 0);\r\n this.combatDetails.combatStats.attackInterval /= (1 + attackIntervalRatioBoost);\r\n\r\n let baseArmor = 0.2 * this.combatDetails.defenseLevel + this.combatDetails.combatStats.armor;\r\n this.combatDetails.totalArmor = baseArmor;\r\n let armorBoosts = this.getBuffBoosts(\"/buff_types/armor\");\r\n for (const boost of armorBoosts) {\r\n this.combatDetails.totalArmor += boost.flatBoost;\r\n this.combatDetails.totalArmor += baseArmor * boost.ratioBoost;\r\n }\r\n\r\n let baseWaterResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.waterResistance;\r\n this.combatDetails.totalWaterResistance = baseWaterResistance;\r\n let waterResistanceBoosts = this.getBuffBoosts(\"/buff_types/water_resistance\");\r\n for (const boost of waterResistanceBoosts) {\r\n this.combatDetails.totalWaterResistance += boost.flatBoost;\r\n this.combatDetails.totalWaterResistance += baseWaterResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseNatureResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.natureResistance;\r\n this.combatDetails.totalNatureResistance = baseNatureResistance;\r\n let natureResistanceBoosts = this.getBuffBoosts(\"/buff_types/nature_resistance\");\r\n for (const boost of natureResistanceBoosts) {\r\n this.combatDetails.totalNatureResistance += boost.flatBoost;\r\n this.combatDetails.totalNatureResistance += baseNatureResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseFireResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.fireResistance;\r\n this.combatDetails.totalFireResistance = baseFireResistance;\r\n let fireResistanceBoosts = this.getBuffBoosts(\"/buff_types/fire_resistance\");\r\n for (const boost of fireResistanceBoosts) {\r\n this.combatDetails.totalFireResistance += boost.flatBoost;\r\n this.combatDetails.totalFireResistance += baseFireResistance * boost.ratioBoost;\r\n }\r\n\r\n let hpRegenBoosts = this.getBuffBoost(\"/buff_types/hp_regen\");\r\n this.combatDetails.combatStats.hpRegenPer10 += this.combatDetails.combatStats.hpRegenPer10 * hpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.hpRegenPer10 += hpRegenBoosts.flatBoost;\r\n\r\n let mpRegenBoosts = this.getBuffBoost(\"/buff_types/mp_regen\");\r\n this.combatDetails.combatStats.mpRegenPer10 += this.combatDetails.combatStats.mpRegenPer10 * mpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.mpRegenPer10 += mpRegenBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.lifeSteal += this.getBuffBoost(\"/buff_types/life_steal\").flatBoost;\r\n this.combatDetails.combatStats.physicalThorns += this.getBuffBoost(\r\n \"/buff_types/physical_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.elementalThorns += this.getBuffBoost(\r\n \"/buff_types/elemental_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.combatExperience += this.getBuffBoost(\"/buff_types/wisdom\").flatBoost;\r\n this.combatDetails.combatStats.criticalRate += this.getBuffBoost(\"/buff_types/critical_rate\").flatBoost;\r\n this.combatDetails.combatStats.criticalDamage += this.getBuffBoost(\"/buff_types/critical_damage\").flatBoost;\r\n\r\n this.combatDetails.combatStats.castSpeed += this.getBuffBoost(\"/buff_types/cast_speed\").flatBoost;\r\n this.combatDetails.combatStats.castSpeed += this.combatDetails[\"attackLevel\"] / 2000;\r\n\r\n let combatDropRateBoosts = this.getBuffBoost(\"/buff_types/combat_drop_rate\");\r\n this.combatDetails.combatStats.combatDropRate += (1 + this.combatDetails.combatStats.combatDropRate) * combatDropRateBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropRate += combatDropRateBoosts.flatBoost;\r\n let combatRareFindBoosts = this.getBuffBoost(\"/buff_types/rare_find\");\r\n this.combatDetails.combatStats.combatRareFind += (1 + this.combatDetails.combatStats.combatRareFind) * combatRareFindBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatRareFind += combatRareFindBoosts.flatBoost;\r\n let combatDropQuantityBoosts = this.getBuffBoost(\"/buff_types/combat_drop_quantity\");\r\n this.combatDetails.combatStats.combatDropQuantity += (1 + this.combatDetails.combatStats.combatDropQuantity) * combatDropQuantityBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropQuantity += combatDropQuantityBoosts.flatBoost;\r\n\r\n let baseThreat = 100 + this.combatDetails.combatStats.threat;\r\n this.combatDetails.totalThreat = baseThreat;\r\n let threatBoosts = this.getBuffBoost(\"/buff_types/threat\");\r\n if (threatBoosts.ratioBoost !== 0) {\r\n this.combatDetails.combatStats.threat += baseThreat * threatBoosts.ratioBoost;\r\n } else {\r\n this.combatDetails.combatStats.threat = baseThreat;\r\n }\r\n this.combatDetails.combatStats.threat += threatBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.retaliation += this.getBuffBoost(\"/buff_types/retaliation\").flatBoost;\r\n }\r\n\r\n addBuff(buff, currentTime) {\r\n buff.startTime = currentTime;\r\n this.combatBuffs[buff.uniqueHrid] = buff;\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n removeBuff(buff) {\r\n if (!this.combatBuffs[buff.uniqueHrid]) {\r\n return;\r\n }\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n addPermanentBuff(buff) {\r\n if (this.permanentBuffs[buff.typeHrid]) {\r\n this.permanentBuffs[buff.typeHrid].flatBoost += buff.flatBoost;\r\n this.permanentBuffs[buff.typeHrid].ratioBoost += buff.ratioBoost;\r\n } else {\r\n this.permanentBuffs[buff.typeHrid] = buff;\r\n }\r\n }\r\n\r\n generatePermanentBuffs() {\r\n for (let i = 0; i < this.houseRooms.length; i++) {\r\n const houseRoom = this.houseRooms[i];\r\n houseRoom.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n\r\n if (this.achievements) {\r\n this.achievements.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.zoneBuffs) {\r\n this.zoneBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.extraBuffs) {\r\n this.extraBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n }\r\n\r\n removeExpiredBuffs(currentTime) {\r\n let expiredBuffs = Object.values(this.combatBuffs).filter(\r\n (buff) => buff.startTime + buff.duration <= currentTime\r\n );\r\n expiredBuffs.forEach((buff) => {\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n });\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearBuffs() {\r\n this.combatBuffs = structuredClone(this.permanentBuffs);\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearCCs() {\r\n this.isStunned = false;\r\n this.stunExpireTime = null;\r\n this.isSilenced = false;\r\n this.silenceExpireTime = null;\r\n this.isBlinded = false;\r\n this.blindExpireTime = null;\r\n this.combatDetails.combatStats.damageTaken = 0;\r\n }\r\n\r\n getBuffBoosts(type) {\r\n let boosts = [];\r\n Object.values(this.combatBuffs)\r\n .filter((buff) => buff.typeHrid == type)\r\n .forEach((buff) => {\r\n boosts.push({ ratioBoost: buff.ratioBoost, flatBoost: buff.flatBoost });\r\n });\r\n\r\n return boosts;\r\n }\r\n\r\n getBuffBoost(type) {\r\n let boosts = this.getBuffBoosts(type);\r\n\r\n let boost = {\r\n ratioBoost: 0,\r\n flatBoost: 0,\r\n };\r\n\r\n for (let i = 0; i < boosts.length; i++) {\r\n boost.ratioBoost += boosts[i]?.ratioBoost ?? 0;\r\n boost.flatBoost += boosts[i]?.flatBoost ?? 0;\r\n }\r\n\r\n return boost;\r\n }\r\n\r\n reset(currentTime = 0) {\r\n this.clearCCs();\r\n this.clearBuffs();\r\n this.updateCombatDetails();\r\n this.resetCooldowns(currentTime);\r\n\r\n this.combatDetails.currentHitpoints = this.combatDetails.maxHitpoints;\r\n this.combatDetails.currentManapoints = this.combatDetails.maxManapoints;\r\n }\r\n\r\n resetCooldowns(currentTime = 0) {\r\n this.food.filter((food) => food != null).forEach((food) => (food.lastUsed = Number.MIN_SAFE_INTEGER));\r\n this.drinks.filter((drink) => drink != null).forEach((drink) => (drink.lastUsed = Number.MIN_SAFE_INTEGER));\r\n\r\n let haste = this.combatDetails.combatStats.abilityHaste;\r\n\r\n this.abilities\r\n .filter((ability) => ability != null)\r\n .forEach((ability) => {\r\n if (this.isPlayer) {\r\n ability.lastUsed = Number.MIN_SAFE_INTEGER;\r\n } else {\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n ability.lastUsed = currentTime - Math.floor(cooldownDuration * 0.5) + Math.floor(Math.random() * cooldownDuration * 0.5);\r\n }\r\n });\r\n }\r\n\r\n addHitpoints(hitpoints) {\r\n let hitpointsAdded = 0;\r\n\r\n if (this.combatDetails.currentHitpoints >= this.combatDetails.maxHitpoints) {\r\n return hitpointsAdded;\r\n }\r\n\r\n let newHitpoints = Math.min(this.combatDetails.currentHitpoints + hitpoints, this.combatDetails.maxHitpoints);\r\n hitpointsAdded = newHitpoints - this.combatDetails.currentHitpoints;\r\n this.combatDetails.currentHitpoints = newHitpoints;\r\n\r\n return hitpointsAdded;\r\n }\r\n\r\n addManapoints(manapoints) {\r\n let manapointsAdded = 0;\r\n\r\n if (this.combatDetails.currentManapoints >= this.combatDetails.maxManapoints) {\r\n return manapointsAdded;\r\n }\r\n\r\n let newManapoints = Math.min(\r\n this.combatDetails.currentManapoints + manapoints,\r\n this.combatDetails.maxManapoints\r\n );\r\n manapointsAdded = newManapoints - this.combatDetails.currentManapoints;\r\n this.combatDetails.currentManapoints = newManapoints;\r\n\r\n return manapointsAdded;\r\n }\r\n}\r\n\r\nexport default CombatUnit;\r\n","import Buff from \"./buff\";\r\nimport itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nclass Consumable {\r\n constructor(hrid, triggers = null) {\r\n this.hrid = hrid;\r\n\r\n let gameConsumable = itemDetailMap[this.hrid];\r\n if (!gameConsumable) {\r\n throw new Error(\"No consumable found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.cooldownDuration = gameConsumable.consumableDetail.cooldownDuration;\r\n this.hitpointRestore = gameConsumable.consumableDetail.hitpointRestore;\r\n this.manapointRestore = gameConsumable.consumableDetail.manapointRestore;\r\n this.recoveryDuration = gameConsumable.consumableDetail.recoveryDuration;\r\n this.catagoryHrid = gameConsumable.categoryHrid;\r\n\r\n this.buffs = [];\r\n if (gameConsumable.consumableDetail.buffs) {\r\n for (const consumableBuff of gameConsumable.consumableDetail.buffs) {\r\n let buff = new Buff(consumableBuff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameConsumable.consumableDetail.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let consumable = new Consumable(dto.hrid, triggers);\r\n\r\n return consumable;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n let consumableHaste;\r\n if (this.catagoryHrid.includes(\"food\")) {\r\n consumableHaste = source.combatDetails.combatStats.foodHaste\r\n } else {\r\n consumableHaste = source.combatDetails.combatStats.drinkConcentration;\r\n }\r\n let cooldownDuration = this.cooldownDuration;\r\n if (consumableHaste > 0) {\r\n cooldownDuration = cooldownDuration / (1 + consumableHaste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Consumable;\r\n","import itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport enhancementLevelTotalMultiplierTable from \"./data/enhancementLevelTotalBonusMultiplierTable.json\";\r\n\r\nclass Equipment {\r\n constructor(hrid, enhancementLevel) {\r\n this.hrid = hrid;\r\n let gameItem = itemDetailMap[this.hrid];\r\n if (!gameItem) {\r\n throw new Error(\"No equipment found for hrid: \" + this.hrid);\r\n }\r\n this.gameItem = gameItem;\r\n this.enhancementLevel = enhancementLevel;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let equipment = new Equipment(dto.hrid, dto.enhancementLevel);\r\n\r\n return equipment;\r\n }\r\n\r\n getCombatStat(combatStat) {\r\n let multiplier = enhancementLevelTotalMultiplierTable[this.enhancementLevel];\r\n if(this.gameItem.equipmentDetail.combatStats[combatStat]) {\r\n let enhancementBonus = this.gameItem.equipmentDetail.combatEnhancementBonuses[combatStat] || 0;\r\n let stat = this.gameItem.equipmentDetail.combatStats[combatStat] + multiplier * enhancementBonus;\r\n return stat;\r\n }\r\n return 0;\r\n }\r\n\r\n getCombatStyle() {\r\n return this.gameItem.equipmentDetail.combatStats.combatStyleHrids[0];\r\n }\r\n\r\n getDamageType() {\r\n return this.gameItem.equipmentDetail.combatStats.damageType;\r\n }\r\n\r\n getPrimaryTraining() {\r\n return this.gameItem.equipmentDetail.combatStats.primaryTraining;\r\n }\r\n\r\n getFocusTraining(){\r\n return this.gameItem.equipmentDetail.combatStats.focusTraining;\r\n }\r\n}\r\n\r\nexport default Equipment;\r\n","import Buff from \"./buff\";\r\nimport houseRoomDetailMap from \"./data/houseRoomDetailMap.json\";\r\n\r\nclass HouseRoom {\r\n constructor(hrid, level) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameHouseRoom = houseRoomDetailMap[this.hrid];\r\n if (!gameHouseRoom) {\r\n throw new Error(\"No house room found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.buffs = [];\r\n if (gameHouseRoom.actionBuffs) {\r\n for (const actionBuff of gameHouseRoom.actionBuffs) {\r\n let buff = new Buff(actionBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n if (gameHouseRoom.globalBuffs) {\r\n for (const globalBuff of gameHouseRoom.globalBuffs) {\r\n let buff = new Buff(globalBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default HouseRoom;","import Ability from \"./ability\";\r\nimport CombatUnit from \"./combatUnit\";\r\nimport Consumable from \"./consumable\";\r\nimport Equipment from \"./equipment\";\r\nimport HouseRoom from \"./houseRoom\";\r\nimport Achievement from \"./achievement\";\r\n\r\nclass Player extends CombatUnit {\r\n equipment = {\r\n \"/equipment_types/head\": null,\r\n \"/equipment_types/body\": null,\r\n \"/equipment_types/legs\": null,\r\n \"/equipment_types/feet\": null,\r\n \"/equipment_types/hands\": null,\r\n \"/equipment_types/main_hand\": null,\r\n \"/equipment_types/two_hand\": null,\r\n \"/equipment_types/off_hand\": null,\r\n \"/equipment_types/pouch\": null,\r\n \"/equipment_types/back\": null,\r\n };\r\n\r\n constructor() {\r\n super();\r\n\r\n this.isPlayer = true;\r\n this.hrid = \"player\";\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let player = new Player();\r\n\r\n player.staminaLevel = dto.staminaLevel;\r\n player.intelligenceLevel = dto.intelligenceLevel;\r\n player.attackLevel = dto.attackLevel;\r\n player.meleeLevel = dto.meleeLevel;\r\n player.defenseLevel = dto.defenseLevel;\r\n player.rangedLevel = dto.rangedLevel;\r\n player.magicLevel = dto.magicLevel;\r\n\r\n player.hrid = dto.hrid;\r\n\r\n for (const [key, value] of Object.entries(dto.equipment)) {\r\n player.equipment[key] = value ? Equipment.createFromDTO(value) : null;\r\n }\r\n\r\n player.food = dto.food.map((food) => (food ? Consumable.createFromDTO(food) : null));\r\n player.drinks = dto.drinks.map((drink) => (drink ? Consumable.createFromDTO(drink) : null));\r\n player.abilities = dto.abilities.map((ability) => (ability ? Ability.createFromDTO(ability) : null));\r\n Object.entries(dto.houseRooms).forEach(houseRoom => {\r\n if (houseRoom[1] > 0) {\r\n player.houseRooms.push(new HouseRoom(houseRoom[0], houseRoom[1]))\r\n }\r\n });\r\n\r\n player.achievements = new Achievement(dto.achievements);\r\n\r\n player.debuffOnLevelGap = dto.debuffOnLevelGap;\r\n\r\n return player;\r\n }\r\n\r\n updateCombatDetails() {\r\n if (this.equipment[\"/equipment_types/main_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/main_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/main_hand\"].getPrimaryTraining();\r\n } else if (this.equipment[\"/equipment_types/two_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/two_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/two_hand\"].getPrimaryTraining();\r\n } else {\r\n this.combatDetails.combatStats.combatStyleHrid = \"/combat_styles/smash\";\r\n this.combatDetails.combatStats.damageType = \"/damage_types/physical\";\r\n this.combatDetails.combatStats.attackInterval = 3000000000;\r\n this.combatDetails.combatStats.primaryTraining = \"/skills/melee\";\r\n }\r\n\r\n if (this.equipment[\"/equipment_types/charm\"]) {\r\n this.combatDetails.combatStats.focusTraining = this.equipment[\"/equipment_types/charm\"].getFocusTraining();\r\n } else {\r\n this.combatDetails.combatStats.focusTraining = \"\";\r\n }\r\n\r\n [\r\n \"stabAccuracy\",\r\n \"slashAccuracy\",\r\n \"smashAccuracy\",\r\n \"rangedAccuracy\",\r\n \"magicAccuracy\",\r\n \"stabDamage\",\r\n \"slashDamage\",\r\n \"smashDamage\",\r\n \"rangedDamage\",\r\n \"magicDamage\",\r\n \"defensiveDamage\",\r\n \"taskDamage\",\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"stabEvasion\",\r\n \"slashEvasion\",\r\n \"smashEvasion\",\r\n \"rangedEvasion\",\r\n \"magicEvasion\",\r\n \"armor\",\r\n \"waterResistance\",\r\n \"natureResistance\",\r\n \"fireResistance\",\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"combatDropRate\",\r\n \"combatRareFind\",\r\n \"combatDropQuantity\",\r\n \"combatExperience\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"abilityHaste\",\r\n \"tenacity\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"threat\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"foodHaste\",\r\n \"drinkConcentration\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\",\r\n \"retaliation\"\r\n ].forEach((stat) => {\r\n this.combatDetails.combatStats[stat] = Object.values(this.equipment)\r\n .filter((equipment) => equipment != null)\r\n .map((equipment) => equipment.getCombatStat(stat))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n });\r\n\r\n if (this.equipment[\"/equipment_types/pouch\"]) {\r\n this.combatDetails.combatStats.foodSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"foodSlots\");\r\n this.combatDetails.combatStats.drinkSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"drinkSlots\");\r\n } else {\r\n this.combatDetails.combatStats.foodSlots = 1;\r\n this.combatDetails.combatStats.drinkSlots = 1;\r\n }\r\n\r\n super.updateCombatDetails();\r\n }\r\n}\r\n\r\nexport default Player;\r\n","import combatTriggerDependencyDetailMap from \"./data/combatTriggerDependencyDetailMap.json\";\r\n\r\nclass Trigger {\r\n constructor(dependencyHrid, conditionHrid, comparatorHrid, value = 0) {\r\n this.dependencyHrid = dependencyHrid;\r\n this.conditionHrid = conditionHrid;\r\n this.comparatorHrid = comparatorHrid;\r\n this.value = value;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let trigger = new Trigger(dto.dependencyHrid, dto.conditionHrid, dto.comparatorHrid, dto.value);\r\n\r\n return trigger;\r\n }\r\n\r\n isActive(source, target, friendlies, enemies, currentTime) {\r\n if (combatTriggerDependencyDetailMap[this.dependencyHrid].isSingleTarget) {\r\n return this.isActiveSingleTarget(source, target, currentTime);\r\n } else {\r\n return this.isActiveMultiTarget(friendlies, enemies, currentTime);\r\n }\r\n }\r\n\r\n isActiveSingleTarget(source, target, currentTime) {\r\n let dependencyValue;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/self\":\r\n dependencyValue = this.getDependencyValue(source, currentTime);\r\n break;\r\n case \"/combat_trigger_dependencies/targeted_enemy\":\r\n if (!target) {\r\n return false;\r\n }\r\n dependencyValue = this.getDependencyValue(target, currentTime);\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n isActiveMultiTarget(friendlies, enemies, currentTime) {\r\n let dependency;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/all_allies\":\r\n dependency = friendlies;\r\n break;\r\n case \"/combat_trigger_dependencies/all_enemies\":\r\n if (!enemies) {\r\n return false;\r\n }\r\n dependency = enemies;\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n let dependencyValue;\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/number_of_active_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints > 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/number_of_dead_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints <= 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/lowest_hp_percentage\":\r\n dependencyValue = dependency.reduce((prev, curr) => {\r\n let currentHpPercentage = curr.combatDetails.currentHitpoints / curr.combatDetails.maxHitpoints;\r\n return currentHpPercentage < prev ? currentHpPercentage : prev;\r\n }, 2) * 100;\r\n break;\r\n default:\r\n dependencyValue = dependency\r\n .map((unit) => this.getDependencyValue(unit, currentTime))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n break;\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n getDependencyValue(source, currentTime) {\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/berserk\":\r\n case \"/combat_trigger_conditions/frenzy\":\r\n case \"/combat_trigger_conditions/precision\":\r\n case \"/combat_trigger_conditions/vampirism\":\r\n case \"/combat_trigger_conditions/attack_coffee\":\r\n case \"/combat_trigger_conditions/defense_coffee\":\r\n case \"/combat_trigger_conditions/lucky_coffee\":\r\n case \"/combat_trigger_conditions/magic_coffee\":\r\n case \"/combat_trigger_conditions/melee_coffee\":\r\n case \"/combat_trigger_conditions/ranged_coffee\":\r\n case \"/combat_trigger_conditions/swiftness_coffee\":\r\n case \"/combat_trigger_conditions/wisdom_coffee\":\r\n case \"/combat_trigger_conditions/ice_spear\":\r\n case \"/combat_trigger_conditions/puncture\":\r\n case \"/combat_trigger_conditions/frost_surge\":\r\n case \"/combat_trigger_conditions/elusiveness\":\r\n case \"/combat_trigger_conditions/channeling_coffee\":\r\n case \"/combat_trigger_conditions/fierce_aura\":\r\n case \"/combat_trigger_conditions/invincible_armor\":\r\n case \"/combat_trigger_conditions/invincible_fire_resistance\":\r\n case \"/combat_trigger_conditions/invincible_nature_resistance\":\r\n case \"/combat_trigger_conditions/invincible_water_resistance\":\r\n case \"/combat_trigger_conditions/provoke\":\r\n case \"/combat_trigger_conditions/taunt\":\r\n case \"/combat_trigger_conditions/crippling_slash\":\r\n case \"/combat_trigger_conditions/mana_spring\":\r\n case \"/combat_trigger_conditions/retribution\":\r\n case \"/combat_trigger_conditions/fracturing_impact\":\r\n case \"/combat_trigger_conditions/maim\":\r\n case \"/combat_trigger_conditions/curse\":\r\n case \"/combat_trigger_conditions/weaken\":\r\n let buffHrid = \"/buff_uniques\";\r\n buffHrid += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n return source.combatBuffs[buffHrid];\r\n case \"/combat_trigger_conditions/critical_aura\":\r\n case \"/combat_trigger_conditions/critical_coffee\":\r\n case \"/combat_trigger_conditions/intelligence_coffee\":\r\n case \"/combat_trigger_conditions/stamina_coffee\":\r\n case \"/combat_trigger_conditions/elemental_affinity\":\r\n case \"/combat_trigger_conditions/fury\":\r\n case \"/combat_trigger_conditions/guardian_aura\":\r\n case \"/combat_trigger_conditions/insanity\":\r\n case \"/combat_trigger_conditions/spike_shell\":\r\n case \"/combat_trigger_conditions/toxic_pollen\":\r\n case \"/combat_trigger_conditions/invincible\":\r\n case \"/combat_trigger_conditions/mystic_aura\":\r\n case \"/combat_trigger_conditions/pestilent_shot\":\r\n case \"/combat_trigger_conditions/smoke_burst\":\r\n case \"/combat_trigger_conditions/speed_aura\":\r\n case \"/combat_trigger_conditions/toughness\":\r\n case \"/combat_trigger_conditions/enrage\":\r\n let buffPrefix = \"/buff_uniques\";\r\n buffPrefix += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n let buffs = Object.keys(source.combatBuffs).filter(buff => buff.startsWith(buffPrefix));\r\n return source.combatBuffs[buffs?.[0]];\r\n case \"/combat_trigger_conditions/current_hp\":\r\n return source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/current_mp\":\r\n return source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/missing_hp\":\r\n return source.combatDetails.maxHitpoints - source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/missing_mp\":\r\n return source.combatDetails.maxManapoints - source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/stun_status\":\r\n // Replicate the game's behaviour of \"stun status active\" triggers activating\r\n // immediately after the stun has worn off\r\n return source.isStunned || source.stunExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/blind_status\":\r\n return source.isBlinded || source.blindExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/silence_status\":\r\n return source.isSilenced || source.silenceExpireTime == currentTime;\r\n default:\r\n throw new Error(\"Unknown conditionHrid in trigger: \" + this.conditionHrid);\r\n }\r\n }\r\n\r\n compareValue(dependencyValue) {\r\n switch (this.comparatorHrid) {\r\n case \"/combat_trigger_comparators/greater_than_equal\":\r\n return dependencyValue >= this.value;\r\n case \"/combat_trigger_comparators/less_than_equal\":\r\n return dependencyValue <= this.value;\r\n case \"/combat_trigger_comparators/is_active\":\r\n return !!dependencyValue;\r\n case \"/combat_trigger_comparators/is_inactive\":\r\n return !dependencyValue;\r\n default:\r\n throw new Error(\"Unknown comparatorHrid in trigger: \" + this.comparatorHrid);\r\n }\r\n }\r\n}\r\n\r\nexport default Trigger;\r\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","// This function allow to reference async chunks\n__webpack_require__.u = (chunkId) => {\n\t// return url for filenames based on template\n\treturn \"\" + chunkId + \".bundle.js\";\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","var scriptUrl;\nif (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + \"\";\nvar document = __webpack_require__.g.document;\nif (!scriptUrl && document) {\n\tif (document.currentScript)\n\t\tscriptUrl = document.currentScript.src;\n\tif (!scriptUrl) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tif(scripts.length) {\n\t\t\tvar i = scripts.length - 1;\n\t\t\twhile (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;\n\t\t}\n\t}\n}\n// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration\n// or pass an empty string (\"\") and set the __webpack_public_path__ variable from your code to use your own logic.\nif (!scriptUrl) throw new Error(\"Automatic publicPath is not supported in this browser\");\nscriptUrl = scriptUrl.replace(/#.*$/, \"\").replace(/\\?.*$/, \"\").replace(/\\/[^\\/]+$/, \"/\");\n__webpack_require__.p = scriptUrl;","__webpack_require__.b = document.baseURI || self.location.href;\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t\"main\": 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n// no on chunks loaded\n\n// no jsonp function","import Equipment from \"./combatsimulator/equipment.js\";\r\nimport Player from \"./combatsimulator/player.js\";\r\nimport abilityDetailMap from \"./combatsimulator/data/abilityDetailMap.json\";\r\nimport itemDetailMap from \"./combatsimulator/data/itemDetailMap.json\";\r\nimport houseRoomDetailMap from \"./combatsimulator/data/houseRoomDetailMap.json\";\r\nimport Ability from \"./combatsimulator/ability.js\";\r\nimport Consumable from \"./combatsimulator/consumable.js\";\r\nimport HouseRoom from \"./combatsimulator/houseRoom\"\r\nimport combatTriggerDependencyDetailMap from \"./combatsimulator/data/combatTriggerDependencyDetailMap.json\";\r\nimport combatTriggerConditionDetailMap from \"./combatsimulator/data/combatTriggerConditionDetailMap.json\";\r\nimport combatTriggerComparatorDetailMap from \"./combatsimulator/data/combatTriggerComparatorDetailMap.json\";\r\nimport abilitySlotsLevelRequirementList from \"./combatsimulator/data/abilitySlotsLevelRequirementList.json\";\r\nimport actionDetailMap from \"./combatsimulator/data/actionDetailMap.json\";\r\nimport combatMonsterDetailMap from \"./combatsimulator/data/combatMonsterDetailMap.json\";\r\nimport damageTypeDetailMap from \"./combatsimulator/data/damageTypeDetailMap.json\";\r\nimport combatStyleDetailMap from \"./combatsimulator/data/combatStyleDetailMap.json\";\r\nimport openableLootDropMap from \"./combatsimulator/data/openableLootDropMap.json\";\r\nimport achievementTierMap from \"./combatsimulator/data/achievementTierDetailMap.json\"\r\nimport achievementDetailMap from \"./combatsimulator/data/achievementDetailMap.json\"\r\n\r\nimport patchNote from \"../patchNote.json\";\r\n\r\nconst ONE_SECOND = 1e9;\r\nconst ONE_HOUR = 60 * 60 * ONE_SECOND;\r\n\r\nlet buttonStartSimulation = document.getElementById(\"buttonStartSimulation\");\r\nlet buttonStopSimulation = document.getElementById(\"buttonStopSimulation\");\r\nlet progressbar = document.getElementById(\"simulationProgressBar\");\r\nlet simStartTime = 0;\r\n\r\nlet worker = new Worker(new URL(\"worker.js\", import.meta.url));\r\nlet multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n\r\n\r\n\r\nlet player = new Player();\r\nlet selectedPlayers = [];\r\nlet food = [null, null, null];\r\nlet drinks = [null, null, null];\r\nlet abilities = [null, null, null, null];\r\nlet triggerMap = {};\r\nlet modalTriggers = [];\r\nlet currentSimResults = {};\r\n\r\nlet currentPlayerTabId = '1';\r\nlet playerDataMap = {\r\n \"1\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"2\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"3\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"4\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"5\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\"\r\n};\r\nwindow.revenue = 0;\r\nwindow.noRngRevenue = 0;\r\nwindow.expenses = 0;\r\nwindow.profit = 0;\r\nwindow.noRngProfit = 0;\r\n\r\n// #region Worker\r\n\r\nfunction onWorkerMessage(event) {\r\n switch (event.data.type) {\r\n case \"simulation_result\":\r\n progressbar.style.width = \"100%\";\r\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n //console.log(\"SIM RESULTS: \", event.data.simResult);\r\n showSimulationResult(event.data.simResult);\r\n updateContent();\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n document.getElementById('buttonShowAllSimData').style.display = 'none';\r\n break;\r\n case \"simulation_progress\":\r\n let progress = Math.floor(100 * event.data.progress);\r\n progressbar.style.width = progress + \"%\";\r\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n break;\r\n case \"simulation_error\":\r\n showErrorModal(event.data.error.toString());\r\n break;\r\n }\r\n};\r\n\r\nfunction onMultiWorkerMessage(event) {\r\n switch (event.data.type) {\r\n case \"simulation_result_allZones\":\r\n progressbar.style.width = \"100%\";\r\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n showAllSimulationResults(event.data.simResults);\r\n updateContent();\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n document.getElementById('buttonShowAllSimData').style.display = 'block';\r\n break;\r\n case \"simulation_progress\":\r\n let progress = Math.floor(100 * event.data.progress);\r\n progressbar.style.width = progress + \"%\";\r\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n break;\r\n case \"simulation_error\":\r\n showErrorModal(event.data.error.toString());\r\n break;\r\n }\r\n};\r\n\r\n// #endregion\r\n\r\n// #region Equipment\r\n\r\nfunction initEquipmentSection() {\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n initEquipmentSelect(type);\r\n initEnhancementLevelInput(type);\r\n });\r\n}\r\n\r\nfunction initEquipmentSelect(equipmentType) {\r\n let selectId = \"selectEquipment_\";\r\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\r\n selectId += \"weapon\";\r\n } else {\r\n selectId += equipmentType;\r\n }\r\n let selectElement = document.getElementById(selectId);\r\n\r\n let gameEquipment = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/equipment\")\r\n .filter((item) => item.equipmentDetail.type == \"/equipment_types/\" + equipmentType)\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const equipment of Object.values(gameEquipment)) {\r\n let opt = new Option(equipment.name, equipment.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + equipment.hrid);\r\n selectElement.add(opt);\r\n }\r\n\r\n selectElement.addEventListener(\"change\", (event) => {\r\n equipmentSelectHandler(event, equipmentType);\r\n });\r\n}\r\n\r\nfunction initHouseRoomsModal() {\r\n let houseRoomsList = document.getElementById(\"houseRoomsList\");\r\n let newChildren = [];\r\n let houseRooms = Object.values(houseRoomDetailMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n player.houseRooms = {};\r\n\r\n for (const room of Object.values(houseRooms)) {\r\n player.houseRooms[room.hrid] = 0;\r\n\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let nameCol = createElement(\"div\", \"col-md-4 offset-md-3 align-self-center\", room.name);\r\n nameCol.setAttribute(\"data-i18n\", \"houseRoomNames.\" + room.hrid);\r\n row.appendChild(nameCol);\r\n\r\n let levelCol = createElement(\"div\", \"col-md-2\");\r\n let levelInput = createHouseInput(room.hrid);\r\n\r\n levelInput.addEventListener(\"input\", function (e) {\r\n let inputValue = e.target.value;\r\n const hrid = e.target.dataset.houseHrid;\r\n player.houseRooms[hrid] = parseInt(inputValue);\r\n });\r\n\r\n levelCol.appendChild(levelInput);\r\n row.appendChild(levelCol);\r\n\r\n newChildren.push(row);\r\n }\r\n\r\n houseRoomsList.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction createHouseInput(hrid) {\r\n let levelInput = document.createElement(\"input\");\r\n levelInput.className = \"form-control\";\r\n levelInput.type = \"number\";\r\n levelInput.placeholder = 0;\r\n levelInput.min = 0;\r\n levelInput.max = 8;\r\n levelInput.step = 1;\r\n levelInput.dataset.houseHrid = hrid;\r\n\r\n return levelInput;\r\n}\r\n\r\nfunction refreshAchievementStatics() {\r\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n for(const tier of Object.values(tierMap)) {\r\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\r\n const done = Array.from(checks).filter(cb => cb.checked).length;\r\n const total = checks.length;\r\n\r\n const stat = document.getElementById(`AchTier${tier.sortIndex}Statics`);\r\n stat.innerText = `(${done}/${total})`;\r\n if (done == total) {\r\n // set to green\r\n stat.classList.remove(\"text-secondary\");\r\n stat.classList.add(\"text-success\");\r\n } else {\r\n // set to secondary\r\n stat.classList.remove(\"text-success\");\r\n stat.classList.add(\"text-secondary\");\r\n }\r\n }\r\n}\r\n\r\nfunction initAchievementsModal(){\r\n let achievementsList = document.getElementById(\"achievementsList\");\r\n let newChildren = [];\r\n player.achievements = {};\r\n\r\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n for(const tier of Object.values(tierMap)) {\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid).sort((a, b) => a.sortIndex - b.sortIndex);\r\n let detailMapCount = detailMap.length;\r\n if (detailMapCount <= 0) continue;\r\n\r\n let card = createElement(\"div\", \"card\");\r\n let cardHeader = createElement(\"div\", \"card-header d-flex align-items-center\");\r\n\r\n let cardTitle = createElement(\"a\", \"btn\", tier.name);\r\n cardTitle.setAttribute(\"data-bs-toggle\",\"collapse\");\r\n cardTitle.setAttribute(\"href\", `#AchTier${tier.sortIndex}`);\r\n cardTitle.setAttribute(\"data-i18n\", \"achievementTierNames.\"+tier.hrid);\r\n cardHeader.appendChild(cardTitle);\r\n\r\n let bufDesc = createElement(\"div\", \"small text-secondary\");\r\n let buffName = createElement(\"i\", \"\");\r\n buffName.setAttribute(\"data-i18n\", \"buffTypeNames.\"+tier[\"buff\"].typeHrid);\r\n bufDesc.appendChild(buffName);\r\n let buffValue = createElement(\"i\", \"\");\r\n buffValue.innerText = \":+\" + parseFloat(tier[\"buff\"].ratioBoost==0?tier[\"buff\"].flatBoost:tier[\"buff\"].ratioBoost)*100 + \"%\";\r\n bufDesc.appendChild(buffValue);\r\n cardHeader.appendChild(bufDesc);\r\n\r\n let cardStatics = createElement(\"div\", \"ms-auto btn\", `(0/${detailMapCount})`);\r\n cardStatics.id = `AchTier${tier.sortIndex}Statics`;\r\n cardStatics.dataset.checked = \"true\";\r\n cardStatics.addEventListener(\"click\", function (e) {\r\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\r\n for (const check of checks) {\r\n check.checked = cardStatics.dataset.checked == \"true\";\r\n const hrid = check.dataset.achievementHrid;\r\n player.achievements[hrid] = check.checked;\r\n }\r\n cardStatics.dataset.checked = cardStatics.dataset.checked == \"true\" ? \"false\" : \"true\";\r\n refreshAchievementStatics();\r\n });\r\n cardHeader.appendChild(cardStatics);\r\n\r\n card.appendChild(cardHeader);\r\n\r\n let cardMain = createElement(\"div\", \"collapse\");\r\n cardMain.id = `AchTier${tier.sortIndex}`;\r\n let cardBody = createElement(\"div\", \"card-body\");\r\n\r\n for (const detail of Object.values(detailMap)) {\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let formCheck = createElement(\"div\", \"form-check\");\r\n let input = createElement(\"input\", \"form-check-input\");\r\n input.setAttribute(\"type\", \"checkbox\");\r\n input.setAttribute(\"data-tier\", tier.sortIndex);\r\n input.id = `AchDetail${detail.sortIndex}`;\r\n input.dataset.achievementHrid = detail.hrid;\r\n input.addEventListener(\"change\", function (e) {\r\n const hrid = e.target.dataset.achievementHrid;\r\n player.achievements[hrid] = e.target.checked;\r\n\r\n refreshAchievementStatics();\r\n });\r\n formCheck.appendChild(input);\r\n\r\n let name = createElement(\"label\", \"form-check-label\", detail.name);\r\n name.setAttribute(\"data-i18n\", \"achievementNames.\" + detail.hrid);\r\n name.setAttribute(\"for\", `AchDetail${detail.sortIndex}`);\r\n formCheck.appendChild(name);\r\n row.appendChild(formCheck);\r\n cardBody.appendChild(row);\r\n }\r\n cardMain.appendChild(cardBody);\r\n card.appendChild(cardMain);\r\n\r\n newChildren.push(card);\r\n }\r\n\r\n achievementsList.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction initEnhancementLevelInput(equipmentType) {\r\n let inputId = \"inputEquipmentEnhancementLevel_\";\r\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\r\n inputId += \"weapon\";\r\n } else {\r\n inputId += equipmentType;\r\n }\r\n\r\n let inputElement = document.getElementById(inputId);\r\n inputElement.value = 0;\r\n inputElement.addEventListener(\"change\", enhancementLevelInputHandler);\r\n}\r\n\r\nfunction equipmentSelectHandler(event, type) {\r\n let equipmentType = \"/equipment_types/\" + type;\r\n\r\n if (!event.target.value) {\r\n updateEquipmentState();\r\n updateUI();\r\n return;\r\n }\r\n\r\n let gameItem = itemDetailMap[event.target.value];\r\n\r\n // Weapon select has two handlers because of mainhand and twohand weapons. Ignore the handler with the wrong type\r\n if (gameItem.equipmentDetail.type != equipmentType) {\r\n return;\r\n }\r\n\r\n if (type == \"two_hand\") {\r\n document.getElementById(\"selectEquipment_off_hand\").value = \"\";\r\n document.getElementById(\"inputEquipmentEnhancementLevel_off_hand\").value = 0;\r\n }\r\n if (type == \"off_hand\" && player.equipment[\"/equipment_types/two_hand\"]) {\r\n document.getElementById(\"selectEquipment_weapon\").value = \"\";\r\n document.getElementById(\"inputEquipmentEnhancementLevel_weapon\").value = 0;\r\n }\r\n\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\nfunction enhancementLevelInputHandler() {\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\nfunction updateEquipmentState() {\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentType = \"/equipment_types/\" + type;\r\n let selectType = type;\r\n if (type == \"main_hand\" || type == \"two_hand\") {\r\n selectType = \"weapon\";\r\n }\r\n\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + selectType);\r\n let equipmentHrid = equipmentSelect.value;\r\n\r\n if (!equipmentHrid) {\r\n player.equipment[equipmentType] = null;\r\n return;\r\n }\r\n\r\n let gameItem = itemDetailMap[equipmentHrid];\r\n\r\n // Clear old weapon if a weapon of a different type is equipped\r\n if (gameItem.equipmentDetail.type != equipmentType) {\r\n player.equipment[equipmentType] = null;\r\n return;\r\n }\r\n\r\n let enhancementLevel = Number(document.getElementById(\"inputEquipmentEnhancementLevel_\" + selectType).value);\r\n player.equipment[equipmentType] = new Equipment(gameItem.hrid, enhancementLevel);\r\n });\r\n}\r\n\r\ndocument.getElementById(\"selectEquipment_set\").onchange = changeEquipmentSetListener;\r\n\r\nfunction changeEquipmentSetListener() {\r\n let value = this.value\r\n let optgroupType = this.options[this.selectedIndex].parentNode.label;\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\"].forEach((type) => {\r\n let selectType = type;\r\n\r\n let currentEquipment = document.getElementById(\"selectEquipment_\" + selectType);\r\n if (type === \"feet\") {\r\n type = \"_boots\";\r\n }\r\n if (type === \"hands\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_bracers\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_gloves\";\r\n } else {\r\n type = \"_gauntlets\";\r\n }\r\n }\r\n if (type === \"head\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_hood\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_hat\";\r\n } else {\r\n type = \"_helmet\";\r\n }\r\n }\r\n if (type === \"legs\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_chaps\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_robe_bottoms\";\r\n } else {\r\n type = \"_plate_legs\";\r\n }\r\n }\r\n if (type === \"body\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_tunic\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_robe_top\";\r\n } else {\r\n type = \"_plate_body\";\r\n }\r\n }\r\n currentEquipment.value = \"/items/\" + value.toLowerCase() + type;\r\n });\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Combat Stats\r\n\r\nfunction updateCombatStatsUI() {\r\n player.updateCombatDetails();\r\n\r\n let combatStyleElement = document.getElementById(\"combatStat_combatStyleHrid\");\r\n let combatStyle = player.combatDetails.combatStats.combatStyleHrid;\r\n combatStyleElement.setAttribute(\"data-i18n\", \"combatStyleNames.\" + combatStyle);\r\n combatStyleElement.innerHTML = combatStyleDetailMap[combatStyle].name;\r\n\r\n let damageTypeElement = document.getElementById(\"combatStat_damageType\");\r\n let damageType = damageTypeDetailMap[player.combatDetails.combatStats.damageType];\r\n damageTypeElement.setAttribute(\"data-i18n\", \"damageTypeNames.\" + damageType.hrid);\r\n damageTypeElement.innerHTML = damageType.name;\r\n\r\n let attackIntervalElement = document.getElementById(\"combatStat_attackInterval\");\r\n attackIntervalElement.innerHTML = (player.combatDetails.combatStats.attackInterval / 1e9).toLocaleString() + \"s\";\r\n\r\n let primaryTrainingElement = document.getElementById(\"combatStat_primaryTraining\");\r\n let primaryTraining = player.combatDetails.combatStats.primaryTraining;\r\n primaryTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + primaryTraining);\r\n primaryTrainingElement.innerHTML = primaryTraining;\r\n\r\n let focusTrainingElement = document.getElementById(\"combatStat_focusTraining\");\r\n let focusTraining = player.combatDetails.combatStats.focusTraining;\r\n if (focusTraining) {\r\n focusTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + focusTraining);\r\n } else {\r\n focusTrainingElement.setAttribute(\"data-i18n\", \"characterSelectPage.slots.empty\");\r\n }\r\n focusTrainingElement.innerHTML = focusTraining;\r\n\r\n [\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"stabAccuracyRating\",\r\n \"stabMaxDamage\",\r\n \"slashAccuracyRating\",\r\n \"slashMaxDamage\",\r\n \"smashAccuracyRating\",\r\n \"smashMaxDamage\",\r\n \"rangedAccuracyRating\",\r\n \"rangedMaxDamage\",\r\n \"magicAccuracyRating\",\r\n \"magicMaxDamage\",\r\n \"defensiveMaxDamage\",\r\n \"stabEvasionRating\",\r\n \"slashEvasionRating\",\r\n \"smashEvasionRating\",\r\n \"rangedEvasionRating\",\r\n \"magicEvasionRating\",\r\n \"totalArmor\",\r\n \"totalWaterResistance\",\r\n \"totalNatureResistance\",\r\n \"totalFireResistance\",\r\n \"totalThreat\"\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n element.innerHTML = Math.floor(player.combatDetails[stat]);\r\n });\r\n\r\n [\r\n \"abilityHaste\",\r\n \"tenacity\"\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n element.innerHTML = Math.floor(player.combatDetails.combatStats[stat]);\r\n });\r\n\r\n [\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"combatExperience\",\r\n \"taskDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"drinkConcentration\",\r\n \"foodHaste\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\"\r\n\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n let value = (100 * player.combatDetails.combatStats[stat]).toLocaleString([], {\r\n minimumFractionDigits: 0,\r\n maximumFractionDigits: 4,\r\n });\r\n element.innerHTML = value + \"%\";\r\n });\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Level\r\n\r\nfunction initLevelSection() {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n levelInput.value = 1;\r\n levelInput.addEventListener(\"change\", levelInputHandler);\r\n });\r\n}\r\n\r\nfunction levelInputHandler() {\r\n updateLevels();\r\n updateUI();\r\n}\r\n\r\nfunction updateLevels() {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n player[skill + \"Level\"] = Number(levelInput.value);\r\n });\r\n updateCombatLevel();\r\n}\r\n\r\nfunction calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel) {\r\n return Math.floor(\r\n 0.1 * (staminaLevel + intelligenceLevel + attackLevel + defenseLevel + Math.max(meleeLevel, rangedLevel, magicLevel))\r\n + 0.5 * Math.max(attackLevel, defenseLevel, meleeLevel, rangedLevel, magicLevel)\r\n );\r\n}\r\n\r\n\r\nfunction updateCombatLevel() {\r\n let staminaLevel = player[\"staminaLevel\"];\r\n let intelligenceLevel = player[\"intelligenceLevel\"];\r\n let defenseLevel = player[\"defenseLevel\"];\r\n let attackLevel = player[\"attackLevel\"];\r\n let meleeLevel = player[\"meleeLevel\"];\r\n let rangedLevel = player[\"rangedLevel\"];\r\n let magicLevel = player[\"magicLevel\"];\r\n\r\n let levelInput = document.getElementById(\"inputLevel_combat\");\r\n levelInput.value = calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel);;\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Food\r\n\r\nfunction initFoodSection() {\r\n for (let i = 0; i < 3; i++) {\r\n let element = document.getElementById(\"selectFood_\" + i);\r\n\r\n let gameFoods = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/food\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const food of Object.values(gameFoods)) {\r\n let opt = new Option(food.name, food.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + food.hrid);\r\n element.add(opt);\r\n }\r\n\r\n element.addEventListener(\"change\", foodSelectHandler);\r\n }\r\n}\r\n\r\nfunction foodSelectHandler() {\r\n updateFoodState();\r\n updateUI();\r\n}\r\n\r\nfunction updateFoodState() {\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n food[i] = foodSelect.value;\r\n if (food[i] && !triggerMap[food[i]]) {\r\n let gameItem = itemDetailMap[food[i]];\r\n triggerMap[food[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateFoodUI() {\r\n for (let i = 0; i < 3; i++) {\r\n let selectElement = document.getElementById(\"selectFood_\" + i);\r\n let triggerButton = document.getElementById(\"buttonFoodTrigger_\" + i);\r\n\r\n selectElement.disabled = i >= player.combatDetails.combatStats.foodSlots;\r\n triggerButton.disabled = i >= player.combatDetails.combatStats.foodSlots || !food[i];\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Drinks\r\n\r\nfunction initDrinksSection() {\r\n for (let i = 0; i < 3; i++) {\r\n let element = document.getElementById(\"selectDrink_\" + i);\r\n\r\n let gameDrinks = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/drink\")\r\n .filter((item) => item.consumableDetail.usableInActionTypeMap[\"/action_types/combat\"])\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const drink of Object.values(gameDrinks)) {\r\n let opt = new Option(drink.name, drink.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + drink.hrid);\r\n element.add(opt);\r\n }\r\n\r\n element.addEventListener(\"change\", drinkSelectHandler);\r\n }\r\n}\r\n\r\nfunction drinkSelectHandler() {\r\n updateDrinksState();\r\n updateDrinksUI();\r\n}\r\n\r\nfunction updateDrinksState() {\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n drinks[i] = drinkSelect.value;\r\n if (drinks[i] && !triggerMap[drinks[i]]) {\r\n let gameItem = itemDetailMap[drinks[i]];\r\n triggerMap[drinks[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateDrinksUI() {\r\n for (let i = 0; i < 3; i++) {\r\n let selectElement = document.getElementById(\"selectDrink_\" + i);\r\n let triggerButton = document.getElementById(\"buttonDrinkTrigger_\" + i);\r\n\r\n selectElement.disabled = i >= player.combatDetails.combatStats.drinkSlots;\r\n triggerButton.disabled = i >= player.combatDetails.combatStats.drinkSlots || !drinks[i];\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Abilities\r\n\r\nfunction initAbilitiesSection() {\r\n for (let i = 0; i < 5; i++) {\r\n let selectElement = document.getElementById(\"selectAbility_\" + i);\r\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\r\n\r\n inputElement.value = 1;\r\n\r\n let gameAbilities;\r\n if (i == 0) {\r\n gameAbilities = Object.values(abilityDetailMap).filter(x => x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\r\n } else {\r\n gameAbilities = Object.values(abilityDetailMap).filter(x => !x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\r\n }\r\n\r\n\r\n for (const ability of Object.values(gameAbilities)) {\r\n let opt = new Option(ability.name, ability.hrid);\r\n opt.setAttribute(\"data-i18n\", \"abilityNames.\" + ability.hrid);\r\n selectElement.add(opt);\r\n }\r\n\r\n selectElement.addEventListener(\"change\", abilitySelectHandler);\r\n }\r\n}\r\n\r\nfunction abilitySelectHandler() {\r\n updateAbilityState();\r\n updateAbilityUI();\r\n}\r\n\r\nfunction updateAbilityState() {\r\n for (let i = 0; i < 5; i++) {\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\r\n abilities[i] = abilitySelect.value;\r\n if (abilities[i] && !triggerMap[abilities[i]]) {\r\n let gameAbility = abilityDetailMap[abilities[i]];\r\n triggerMap[abilities[i]] = structuredClone(gameAbility.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateAbilityUI() {\r\n for (let i = 0; i < 5; i++) {\r\n let selectElement = document.getElementById(\"selectAbility_\" + i);\r\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let triggerButton = document.getElementById(\"buttonAbilityTrigger_\" + i);\r\n\r\n selectElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\r\n inputElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\r\n triggerButton.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1] || !abilities[i];\r\n let moveUpButton = document.getElementById(\"selectAbilityMoveUp_\" + i);\r\n moveUpButton.onclick = () => swapAbilityOrder(i, -1);\r\n }\r\n}\r\n\r\nfunction swapAbilityOrder(abilityIndex, step) {\r\n const swapIndex = abilityIndex + step;\r\n if (swapIndex < 0 || swapIndex > 4) {\r\n return;\r\n }\r\n\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilityIndex);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilityIndex);\r\n\r\n const tempAbility = abilities[abilityIndex];\r\n abilities[abilityIndex] = abilities[swapIndex];\r\n abilities[swapIndex] = tempAbility;\r\n\r\n const tempLevel = abilityLevelInput.value;\r\n abilityLevelInput.value = document.getElementById(\"inputAbilityLevel_\" + swapIndex).value;\r\n document.getElementById(\"inputAbilityLevel_\" + swapIndex).value = tempLevel;\r\n\r\n abilitySelect.value = document.getElementById(\"selectAbility_\" + (swapIndex)).value;\r\n document.getElementById(\"selectAbility_\" + swapIndex).value = abilities[swapIndex];\r\n\r\n updateAbilityState();\r\n updateAbilityUI();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Trigger\r\n\r\nfunction initTriggerModal() {\r\n let modal = document.getElementById(\"triggerModal\");\r\n modal.addEventListener(\"show.bs.modal\", (event) => triggerModalShownHandler(event));\r\n\r\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\r\n triggerSaveButton.addEventListener(\"click\", (event) => triggerModalSaveHandler(event));\r\n\r\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\r\n triggerAddButton.addEventListener(\"click\", (event) => triggerAddButtonHandler(event));\r\n\r\n let triggerDefaultButton = document.getElementById(\"buttonDefaultTrigger\");\r\n triggerDefaultButton.addEventListener(\"click\", (event) => triggerDefaultButtonHandler(event));\r\n\r\n for (let i = 0; i < 4; i++) {\r\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\r\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\r\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\r\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\r\n let triggerRemoveButton = document.getElementById(\"buttonRemoveTrigger_\" + i);\r\n\r\n triggerDependencySelect.addEventListener(\"change\", (event) => triggerDependencySelectHandler(event, i));\r\n triggerConditionSelect.addEventListener(\"change\", (event) => triggerConditionSelectHandler(event, i));\r\n triggerComparatorSelect.addEventListener(\"change\", (event) => triggerComparatorSelectHander(event, i));\r\n triggerValueInput.addEventListener(\"change\", (event) => triggerValueInputHandler(event, i));\r\n triggerRemoveButton.addEventListener(\"click\", (event) => triggerRemoveButtonHandler(event, i));\r\n }\r\n}\r\n\r\nfunction triggerModalShownHandler(event) {\r\n let triggerButton = event.relatedTarget;\r\n\r\n let triggerType = triggerButton.getAttribute(\"data-bs-triggertype\");\r\n let triggerIndex = Number(triggerButton.getAttribute(\"data-bs-triggerindex\"));\r\n\r\n let triggerTarget;\r\n switch (triggerType) {\r\n case \"food\":\r\n triggerTarget = food[triggerIndex];\r\n break;\r\n case \"drink\":\r\n triggerTarget = drinks[triggerIndex];\r\n break;\r\n case \"ability\":\r\n triggerTarget = abilities[triggerIndex];\r\n break;\r\n }\r\n\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n triggerTargetnput.value = triggerTarget;\r\n modalTriggers = triggerMap[triggerTarget];\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerModalSaveHandler(event) {\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n let triggerTarget = triggerTargetnput.value;\r\n\r\n triggerMap[triggerTarget] = modalTriggers;\r\n}\r\n\r\nfunction triggerDependencySelectHandler(event, index) {\r\n modalTriggers[index].dependencyHrid = event.target.value;\r\n modalTriggers[index].conditionHrid = \"\";\r\n modalTriggers[index].comparatorHrid = \"\";\r\n modalTriggers[index].value = 0;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerConditionSelectHandler(event, index) {\r\n modalTriggers[index].conditionHrid = event.target.value;\r\n modalTriggers[index].comparatorHrid = \"\";\r\n modalTriggers[index].value = 0;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerComparatorSelectHander(event, index) {\r\n modalTriggers[index].comparatorHrid = event.target.value;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerValueInputHandler(event, index) {\r\n modalTriggers[index].value = Number(event.target.value);\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerRemoveButtonHandler(event, index) {\r\n modalTriggers.splice(index, 1);\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerAddButtonHandler(event) {\r\n if (modalTriggers.length == 4) {\r\n return;\r\n }\r\n\r\n modalTriggers.push({\r\n dependencyHrid: \"\",\r\n conditionHrid: \"\",\r\n comparatorHrid: \"\",\r\n value: 0,\r\n });\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerDefaultButtonHandler(event) {\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n let triggerTarget = triggerTargetnput.value;\r\n\r\n if (triggerTarget.startsWith(\"/items/\")) {\r\n modalTriggers = structuredClone(itemDetailMap[triggerTarget].consumableDetail.defaultCombatTriggers);\r\n } else {\r\n modalTriggers = structuredClone(abilityDetailMap[triggerTarget].defaultCombatTriggers);\r\n }\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction updateTriggerModal() {\r\n let triggerStartTextElement = document.getElementById(\"triggerStartText\");\r\n if (modalTriggers.length == 0) {\r\n triggerStartTextElement.innerHTML = \"Activate as soon as it's off cooldown\";\r\n } else {\r\n triggerStartTextElement.innerHTML = \"Activate when:\";\r\n }\r\n\r\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\r\n triggerAddButton.disabled = modalTriggers.length == 4;\r\n\r\n let triggersValid = true;\r\n\r\n for (let i = 0; i < 4; i++) {\r\n let triggerElement = document.getElementById(\"modalTrigger_\" + i);\r\n\r\n if (!modalTriggers[i]) {\r\n hideElement(triggerElement);\r\n continue;\r\n }\r\n\r\n showElement(triggerElement);\r\n\r\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\r\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\r\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\r\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\r\n\r\n showElement(triggerDependencySelect);\r\n fillTriggerDependencySelect(triggerDependencySelect);\r\n\r\n if (modalTriggers[i].dependencyHrid == \"\") {\r\n hideElement(triggerConditionSelect);\r\n hideElement(triggerComparatorSelect);\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerDependencySelect.value = modalTriggers[i].dependencyHrid;\r\n showElement(triggerConditionSelect);\r\n fillTriggerConditionSelect(triggerConditionSelect, modalTriggers[i].dependencyHrid);\r\n\r\n if (modalTriggers[i].conditionHrid == \"\") {\r\n hideElement(triggerComparatorSelect);\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerConditionSelect.value = modalTriggers[i].conditionHrid;\r\n showElement(triggerComparatorSelect);\r\n fillTriggerComparatorSelect(triggerComparatorSelect, modalTriggers[i].conditionHrid);\r\n\r\n if (modalTriggers[i].comparatorHrid == \"\") {\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerComparatorSelect.value = modalTriggers[i].comparatorHrid;\r\n\r\n if (combatTriggerComparatorDetailMap[modalTriggers[i].comparatorHrid].allowValue) {\r\n showElement(triggerValueInput);\r\n triggerValueInput.value = modalTriggers[i].value;\r\n } else {\r\n hideElement(triggerValueInput);\r\n }\r\n }\r\n\r\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\r\n triggerSaveButton.disabled = !triggersValid;\r\n\r\n updateContent();\r\n}\r\n\r\nfunction fillTriggerDependencySelect(element) {\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const dependency of Object.values(combatTriggerDependencyDetailMap).sort(\r\n (a, b) => a.sortIndex - b.sortIndex\r\n )) {\r\n let opt = new Option(dependency.name, dependency.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerDependencyNames.\" + dependency.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction fillTriggerConditionSelect(element, dependencyHrid) {\r\n let dependency = combatTriggerDependencyDetailMap[dependencyHrid];\r\n\r\n let conditions;\r\n if (dependency.isSingleTarget) {\r\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isSingleTarget);\r\n } else {\r\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isMultiTarget);\r\n }\r\n\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const condition of Object.values(conditions).sort((a, b) => a.sortIndex - b.sortIndex)) {\r\n let opt = new Option(condition.name, condition.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerConditionNames.\" + condition.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction fillTriggerComparatorSelect(element, conditionHrid) {\r\n let condition = combatTriggerConditionDetailMap[conditionHrid];\r\n\r\n let comparators = condition.allowedComparatorHrids.map((hrid) => combatTriggerComparatorDetailMap[hrid]);\r\n\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const comparator of Object.values(comparators).sort((a, b) => a.sortIndex - b.sortIndex)) {\r\n let opt = new Option(comparator.name, comparator.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerComparatorNames.\" + comparator.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction hideElement(element) {\r\n element.classList.remove(\"d-flex\");\r\n element.classList.add(\"d-none\");\r\n}\r\n\r\nfunction showElement(element) {\r\n element.classList.remove(\"d-none\");\r\n element.classList.add(\"d-flex\");\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Zones\r\n\r\nfunction initZones() {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n\r\n // TOOD dungeon wave spawns\r\n let gameZones = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const zone of Object.values(gameZones)) {\r\n let opt = new Option(zone.name, zone.hrid);\r\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + zone.hrid);\r\n zoneSelect.add(opt);\r\n }\r\n\r\n\r\n let zoneCheckBox = document.getElementById(\"zoneCheckBox\");\r\n let checkAllZonesToggle = document.getElementById('checkAllZones');\r\n\r\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\r\n simAllZonesToggle.addEventListener(\"change\", (event) => {\r\n if (simAllZonesToggle.checked) {\r\n zoneCheckBox.classList.remove(\"d-none\");\r\n zoneCheckBox.querySelectorAll(\".zone-checkbox\").forEach(checkbox => checkbox.checked = true);\r\n checkAllZonesToggle.checked = true;\r\n } else {\r\n zoneCheckBox.classList.add(\"d-none\");\r\n }\r\n });\r\n\r\n let zoneHrids = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .flat();\r\n\r\n for (const zoneHrid of zoneHrids) {\r\n const newZone = document.createElement('div');\r\n newZone.classList.add('form-check');\r\n newZone.innerHTML = `\r\n \r\n \r\n `;\r\n zoneCheckBox.append(newZone);\r\n }\r\n\r\n let checkZoneToggles = document.querySelectorAll('.zone-checkbox');\r\n checkAllZonesToggle.addEventListener('change', () => {\r\n checkZoneToggles.forEach(cb => cb.checked = checkAllZonesToggle.checked);\r\n });\r\n\r\n checkZoneToggles.forEach(cb =>\r\n cb.addEventListener('change', () => {\r\n checkAllZonesToggle.checked = [...checkZoneToggles].every(x => x.checked);\r\n })\r\n );\r\n\r\n\r\n let soloCheckBox = document.getElementById(\"soloCheckBox\");\r\n let checkAllSolosToggle = document.getElementById('checkAllSolos');\r\n\r\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\r\n simAllSoloToggle.addEventListener(\"change\", (event) => {\r\n if (simAllSoloToggle.checked) {\r\n soloCheckBox.classList.remove(\"d-none\");\r\n soloCheckBox.querySelectorAll(\".solo-checkbox\").forEach(checkbox => checkbox.checked = true);\r\n checkAllSolosToggle.checked = true;\r\n } else {\r\n soloCheckBox.classList.add(\"d-none\");\r\n }\r\n });\r\n\r\n let soloHrids = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount == 1)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .flat();\r\n\r\n for (const zoneHrid of soloHrids) {\r\n const newZone = document.createElement('div');\r\n newZone.classList.add('form-check');\r\n newZone.innerHTML = `\r\n \r\n \r\n `;\r\n soloCheckBox.append(newZone);\r\n }\r\n\r\n let checkSoloToggles = document.querySelectorAll('.solo-checkbox');\r\n checkAllSolosToggle.addEventListener('change', () => {\r\n checkSoloToggles.forEach(cb => cb.checked = checkAllSolosToggle.checked);\r\n });\r\n\r\n checkSoloToggles.forEach(cb =>\r\n cb.addEventListener('change', () => {\r\n checkAllSolosToggle.checked = [...checkSoloToggles].every(x => x.checked);\r\n })\r\n );\r\n}\r\n\r\nfunction initDungeons() {\r\n let dungeonSelect = document.getElementById(\"selectDungeon\");\r\n\r\n let gameDungeons = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category == \"/action_categories/combat/dungeons\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const dungeon of Object.values(gameDungeons)) {\r\n let opt = new Option(dungeon.name, dungeon.hrid);\r\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + dungeon.hrid);\r\n dungeonSelect.add(opt);\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Simulation Result\r\n\r\nfunction createDamageDoneAccordion(enemyIndex) {\r\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageDoneAccordionEnemy${enemyIndex}`);\r\n\r\n const colDiv = createElement('div', 'col');\r\n const accordionMainDiv = createElement('div', 'accordion');\r\n const accordionItemDiv = createElement('div', 'accordion-item');\r\n\r\n const headerH2 = createElement('h2', 'accordion-header');\r\n const button = createElement('button', 'accordion-button collapsed',\r\n `Damage Done (Enemy ${enemyIndex})`,\r\n `buttonSimulationResultDamageDoneAccordionEnemy${enemyIndex}`\r\n );\r\n button.setAttribute('type', 'button');\r\n button.setAttribute('data-bs-toggle', 'collapse');\r\n button.setAttribute('data-bs-target', `#collapseDamageDone${enemyIndex}`);\r\n button.style.padding = '0.5em';\r\n\r\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageDone${enemyIndex}`);\r\n const accordionBodyDiv = createElement('div', 'accordion-body');\r\n\r\n const headerRow = createElement('div', 'row');\r\n headerRow.innerHTML = `\r\n
Source
\r\n
Hitchance
\r\n
DPS
\r\n
%
\r\n `;\r\n\r\n const resultDiv = createElement('div', '', '', `simulationResultDamageDoneEnemy${enemyIndex}`);\r\n\r\n accordionBodyDiv.appendChild(headerRow);\r\n accordionBodyDiv.appendChild(resultDiv);\r\n collapseDiv.appendChild(accordionBodyDiv);\r\n headerH2.appendChild(button);\r\n accordionItemDiv.appendChild(headerH2);\r\n accordionItemDiv.appendChild(collapseDiv);\r\n accordionMainDiv.appendChild(accordionItemDiv);\r\n colDiv.appendChild(accordionMainDiv);\r\n accordionDiv.appendChild(colDiv);\r\n\r\n return accordionDiv;\r\n}\r\nfunction createDamageTakenAccordion(enemyIndex) {\r\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageTakenAccordionEnemy${enemyIndex}`);\r\n\r\n const colDiv = createElement('div', 'col');\r\n const accordionMainDiv = createElement('div', 'accordion');\r\n const accordionItemDiv = createElement('div', 'accordion-item');\r\n\r\n const headerH2 = createElement('h2', 'accordion-header');\r\n const button = createElement('button', 'accordion-button collapsed',\r\n `Damage Taken (Enemy ${enemyIndex})`,\r\n `buttonSimulationResultDamageTakenAccordionEnemy${enemyIndex}`\r\n );\r\n button.setAttribute('type', 'button');\r\n button.setAttribute('data-bs-toggle', 'collapse');\r\n button.setAttribute('data-bs-target', `#collapseDamageTaken${enemyIndex}`);\r\n button.style.padding = '0.5em';\r\n\r\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageTaken${enemyIndex}`);\r\n const accordionBodyDiv = createElement('div', 'accordion-body');\r\n\r\n const headerRow = createElement('div', 'row');\r\n headerRow.innerHTML = `\r\n
Source
\r\n
Hitchance
\r\n
DPS
\r\n
%
\r\n `;\r\n\r\n const resultDiv = createElement('div', '', '', `simulationResultDamageTakenEnemy${enemyIndex}`);\r\n\r\n accordionBodyDiv.appendChild(headerRow);\r\n accordionBodyDiv.appendChild(resultDiv);\r\n collapseDiv.appendChild(accordionBodyDiv);\r\n headerH2.appendChild(button);\r\n accordionItemDiv.appendChild(headerH2);\r\n accordionItemDiv.appendChild(collapseDiv);\r\n accordionMainDiv.appendChild(accordionItemDiv);\r\n colDiv.appendChild(accordionMainDiv);\r\n accordionDiv.appendChild(colDiv);\r\n\r\n return accordionDiv;\r\n}\r\n\r\n\r\nfunction initDamageDoneTaken() {\r\n for (let i = 64; i > 0; i--) {\r\n document.getElementById(\"simulationResultTotalDamageDone\").insertAdjacentElement('afterend', createDamageDoneAccordion(i));\r\n document.getElementById(\"simulationResultTotalDamageTaken\").insertAdjacentElement('afterend', createDamageTakenAccordion(i));\r\n }\r\n}\r\n\r\nfunction showSimulationResult(simResult) {\r\n currentSimResults = simResult;\r\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\r\n expensesModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\r\n revenueModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let noRngRevenueModalTable = document.querySelector(\"#noRngRevenueTable > tbody\");\r\n noRngRevenueModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let playerToDisplay = \"player1\";\r\n if (selectedPlayers.includes(parseInt(currentPlayerTabId))) {\r\n playerToDisplay = \"player\" + currentPlayerTabId;\r\n }\r\n if (!simResult.dropRateMultiplier[playerToDisplay]) {\r\n return;\r\n }\r\n\r\n showKills(simResult, playerToDisplay);\r\n showDeaths(simResult, playerToDisplay);\r\n showExperienceGained(simResult, playerToDisplay);\r\n showConsumablesUsed(simResult, playerToDisplay);\r\n showHpSpent(simResult, playerToDisplay);\r\n showManaUsed(simResult, playerToDisplay);\r\n showHitpointsGained(simResult, playerToDisplay);\r\n showManapointsGained(simResult, playerToDisplay);\r\n showDamageDone(simResult, playerToDisplay);\r\n showDamageTaken(simResult, playerToDisplay);\r\n renderWipeEvents(simResult);\r\n window.profit = window.revenue - window.expenses;\r\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\r\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\r\n window.noRngProfit = window.noRngRevenue - window.expenses;\r\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\r\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\r\n}\r\n\r\nfunction showAllSimulationResults(simResults) {\r\n let displaySimResults = manipulateSimResultsDataForDisplay(simResults);\r\n updateAllSimsModal(displaySimResults);\r\n}\r\n\r\nfunction manipulateSimResultsDataForDisplay(simResults) {\r\n let displaySimResults = [];\r\n for (let i = 0; i < simResults.length; i++) {\r\n for (let j = 0; j < selectedPlayers.length; j++) {\r\n let playerToDisplay = \"player\" + selectedPlayers[j].toString();\r\n let simResult = simResults[i];\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let zoneName = simResult.zoneName;\r\n let difficultyTier = simResult.difficultyTier;\r\n let encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\r\n let playerDeaths = simResult.deaths[playerToDisplay] ?? 0;\r\n let deathsPerHour = (playerDeaths / hoursSimulated).toFixed(2);\r\n\r\n let totalExperience = 0;\r\n if (simResult.experienceGained[playerToDisplay]) {\r\n totalExperience = Object.values(simResult.experienceGained[playerToDisplay]).reduce((prev, cur) => prev + cur, 0);\r\n }\r\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\r\n\r\n let experiencePerHour = {};\r\n const skills = [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"];\r\n skills.forEach((skill) => {\r\n const skillLower = skill.toLowerCase();\r\n let experience = simResult.experienceGained[playerToDisplay]?.[skillLower] ?? 0;\r\n let experiencePerHourValue = 0;\r\n if (experience != 0) {\r\n experiencePerHourValue = (experience / hoursSimulated).toFixed(0);\r\n }\r\n experiencePerHour[skill] = experiencePerHourValue;\r\n });\r\n getDropProfit(simResult, playerToDisplay);\r\n let noRngRevenue = simResult[\"noRngRevenue\"];\r\n let noRngProfit = simResult[\"noRngProfit\"];\r\n let expenses = simResult[\"expenses\"];\r\n\r\n let displaySimRow = {\r\n \"ZoneName\": zoneName, \"DifficultyTier\": difficultyTier, \"Player\": playerToDisplay, \"Encounters\": encountersPerHour, \"Deaths\": deathsPerHour,\r\n \"TotalExperience\": totalExperiencePerHour, \"Stamina\": experiencePerHour[\"Stamina\"],\r\n \"Intelligence\": experiencePerHour[\"Intelligence\"], \"Attack\": experiencePerHour[\"Attack\"],\r\n \"Magic\": experiencePerHour[\"Magic\"], \"Ranged\": experiencePerHour[\"Ranged\"],\r\n \"Melee\": experiencePerHour[\"Melee\"], \"Defense\": experiencePerHour[\"Defense\"],\r\n \"noRngRevenue\": noRngRevenue,\r\n \"expenses\": expenses,\r\n \"noRngProfit\": noRngProfit\r\n };\r\n displaySimResults.push(displaySimRow);\r\n }\r\n }\r\n return displaySimResults;\r\n}\r\n\r\nfunction fidDropAmount(dropAmount) {\r\n if (Number.isInteger(dropAmount)) return dropAmount;\r\n\r\n const intPart = Math.floor(dropAmount);\r\n const fracPart = dropAmount - intPart;\r\n return Math.random() < fracPart ? intPart + 1 : intPart;\r\n}\r\n\r\nfunction calcDropMaps(simResult, playerToDisplay) {\r\n let dropRateMultiplier = simResult.dropRateMultiplier[playerToDisplay];\r\n let rareFindMultiplier = simResult.rareFindMultiplier[playerToDisplay];\r\n let combatDropQuantity = simResult.combatDropQuantity[playerToDisplay];\r\n let debuffOnLevelGap = simResult.debuffOnLevelGap[playerToDisplay];\r\n\r\n let numberOfPlayers = simResult.numberOfPlayers;\r\n let monsters = Object.keys(simResult.deaths)\r\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\r\n .sort();\r\n\r\n const totalDropMap = new Map();\r\n const noRngTotalDropMap = new Map();\r\n for (const monster of monsters) {\r\n const dropMap = new Map();\r\n const rareDropMap = new Map();\r\n if (combatMonsterDetailMap[monster].dropTable) {\r\n for (const drop of combatMonsterDetailMap[monster].dropTable) {\r\n if (drop.minDifficultyTier > simResult.difficultyTier) {\r\n continue;\r\n }\r\n\r\n let multiplier = 1.0 + 0.1 * simResult.difficultyTier;\r\n let dropRate = Math.min(1.0, multiplier * (drop.dropRate + (drop.dropRatePerDifficultyTier ?? 0) * simResult.difficultyTier));\r\n if (dropRate <= 0) continue;\r\n\r\n dropMap.set(drop.itemHrid, { \"dropRate\": Math.min(1.0, dropRate * dropRateMultiplier), \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\r\n }\r\n if (combatMonsterDetailMap[monster].rareDropTable)\r\n for (const drop of combatMonsterDetailMap[monster].rareDropTable) {\r\n if (drop.minDifficultyTier > simResult.difficultyTier) {\r\n continue;\r\n }\r\n rareDropMap.set(drop.itemHrid, { \"dropRate\": drop.dropRate * rareFindMultiplier, \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\r\n }\r\n\r\n for (let dropObject of dropMap.values()) {\r\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\r\n\r\n }\r\n for (let dropObject of rareDropMap.values()) {\r\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\r\n }\r\n\r\n for (let i = 0; i < simResult.deaths[monster]; i++) {\r\n for (let dropObject of dropMap.values()) {\r\n let chance = Math.random();\r\n if (chance <= dropObject.dropRate / numberOfPlayers) {\r\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\r\n dropObject.number = dropObject.number + fidDropAmount(amount);\r\n }\r\n }\r\n for (let dropObject of rareDropMap.values()) {\r\n let chance = Math.random();\r\n if (chance <= dropObject.dropRate / numberOfPlayers) {\r\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\r\n dropObject.number = dropObject.number + fidDropAmount(amount);\r\n }\r\n }\r\n }\r\n for (let [name, dropObject] of dropMap.entries()) {\r\n if (totalDropMap.has(name)) {\r\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\r\n } else {\r\n totalDropMap.set(name, dropObject.number);\r\n }\r\n if (noRngTotalDropMap.has(name)) {\r\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\r\n } else {\r\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\r\n }\r\n }\r\n for (let [name, dropObject] of rareDropMap.entries()) {\r\n if (totalDropMap.has(name)) {\r\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\r\n } else {\r\n totalDropMap.set(name, dropObject.number);\r\n }\r\n if (noRngTotalDropMap.has(name)) {\r\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\r\n } else {\r\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return { totalDropMap, noRngTotalDropMap };\r\n}\r\n\r\nfunction getDropProfit(simResult, playerToDisplay) {\r\n let { totalDropMap, noRngTotalDropMap } = calcDropMaps(simResult, playerToDisplay);\r\n\r\n let noRngTotal = 0;\r\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\r\n let price = -1;\r\n let revenueSetting = document.getElementById('selectPrices_drops').value;\r\n if (window.prices) {\r\n let item = window.prices[name];\r\n if (item) {\r\n if (revenueSetting == 'bid') {\r\n if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n } else if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n }\r\n } else if (revenueSetting == 'ask') {\r\n if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n } else if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n }\r\n }\r\n if (price == -1) {\r\n price = item['vendor'];\r\n }\r\n }\r\n }\r\n noRngTotal += price * dropAmount;\r\n }\r\n\r\n let consumablesUsed = simResult.consumablesUsed?.[playerToDisplay];\r\n\r\n if (consumablesUsed) {\r\n consumablesUsed = Object.entries(consumablesUsed).sort((a, b) => b[1] - a[1]);\r\n } else {\r\n consumablesUsed = [];\r\n }\r\n\r\n let expenses = 0;\r\n for (const [consumable, amount] of consumablesUsed) {\r\n let price = -1;\r\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\r\n if (window.prices) {\r\n let item = window.prices[consumable];\r\n if (item) {\r\n if (expensesSetting == 'bid') {\r\n if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n } else if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n }\r\n } else if (expensesSetting == 'ask') {\r\n if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n } else if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n }\r\n }\r\n if (price == -1) {\r\n price = item['vendor'];\r\n }\r\n }\r\n }\r\n expenses += price * amount;\r\n }\r\n\r\n simResult[\"noRngRevenue\"] = (noRngTotal).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n simResult[\"expenses\"] = (expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n simResult[\"noRngProfit\"] = (noRngTotal - expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n}\r\n\r\nfunction updateAllSimsModal(data) {\r\n const tableBody = document.getElementById('allZonesData').getElementsByTagName('tbody')[0];\r\n tableBody.innerHTML = '';\r\n data.forEach(item => {\r\n const row = document.createElement('tr');\r\n\r\n Object.keys(item).forEach(key => {\r\n const cell = document.createElement('td');\r\n cell.textContent = item[key];\r\n if (key === 'ZoneName') {\r\n cell.setAttribute(\"data-i18n\", \"actionNames.\" + item[key]);\r\n }\r\n row.appendChild(cell);\r\n });\r\n\r\n tableBody.appendChild(row);\r\n });\r\n\r\n const table = document.getElementById('allZonesData');\r\n const rows = table.getElementsByTagName('tr');\r\n const numCols = rows[0].cells.length;\r\n\r\n // 遍历每一列\r\n for (let col = 5; col < numCols; col++) {\r\n let max = -Infinity;\r\n let maxCell = null;\r\n\r\n // 找到最大值及其单元格\r\n for (let row = 1; row < rows.length; row++) {\r\n const cell = rows[row].cells[col];\r\n const value = parseFloat(cell.textContent.replace(/,/g, ''));\r\n if (value > max) {\r\n max = value;\r\n maxCell = cell;\r\n }\r\n }\r\n\r\n // 将最大值单元格的背景色设置为绿色\r\n if (maxCell && max != 0) {\r\n maxCell.style.backgroundColor = 'green';\r\n maxCell.style.color = 'white'; // 设置文字颜色为白色以提高可读性\r\n }\r\n }\r\n}\r\n\r\nlet currentSortColumn = null;\r\nlet currentSortDirection = 'desc';\r\n\r\nfunction sortTable(tableId, columnIndex, direction) {\r\n const table = document.getElementById(tableId);\r\n const tbody = table.querySelector('tbody');\r\n const rows = Array.from(tbody.querySelectorAll('tr'));\r\n\r\n const sortedRows = rows.sort((rowA, rowB) => {\r\n const cellA = rowA.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\r\n const cellB = rowB.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\r\n\r\n const valueA = parseFloat(cellA.replace(/,/g, ''));\r\n const valueB = parseFloat(cellB.replace(/,/g, ''));\r\n\r\n return direction === 'asc' ? valueA - valueB : valueB - valueA;\r\n });\r\n\r\n sortedRows.forEach(row => tbody.appendChild(row));\r\n updateSortIndicators(tableId, columnIndex, direction);\r\n}\r\n\r\nfunction updateSortIndicators(tableId, columnIndex, direction) {\r\n const headers = document.querySelectorAll(`#${tableId} th`);\r\n headers.forEach((header, index) => {\r\n header.classList.remove('sort-asc', 'sort-desc');\r\n if (index === columnIndex) {\r\n header.classList.add(direction === 'asc' ? 'sort-asc' : 'sort-desc');\r\n }\r\n });\r\n}\r\n\r\ndocument.querySelectorAll('#allZonesData th').forEach((header, index) => {\r\n if (index === 0) return;\r\n if (index === 1) return;\r\n if (index === 2) return;\r\n\r\n header.addEventListener('click', () => {\r\n if (currentSortColumn === index) {\r\n currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';\r\n } else {\r\n currentSortColumn = index;\r\n currentSortDirection = 'desc';\r\n }\r\n sortTable('allZonesData', currentSortColumn, currentSortDirection);\r\n });\r\n});\r\n\r\ndocument.getElementById('buttonExportResults').addEventListener('click', function () {\r\n var table = document.getElementById('allZonesData');\r\n var csv = [];\r\n var rows = table.querySelectorAll('tr');\r\n\r\n for (var i = 0; i < rows.length; i++) {\r\n var row = rows[i];\r\n var cols = row.querySelectorAll('th, td');\r\n var csvRow = [];\r\n\r\n cols.forEach(function (col) {\r\n csvRow.push('\"' + col.innerText.replace(/\"/g, '\"\"') + '\"');\r\n });\r\n\r\n csv.push(csvRow.join(','));\r\n }\r\n\r\n var csvFile = new Blob([csv.join('\\n')], { type: 'text/csv' });\r\n var downloadLink = document.createElement('a');\r\n downloadLink.download = 'simData.csv';\r\n downloadLink.href = URL.createObjectURL(csvFile);\r\n downloadLink.style.display = 'none';\r\n document.body.appendChild(downloadLink);\r\n downloadLink.click();\r\n document.body.removeChild(downloadLink);\r\n});\r\n\r\nfunction showKills(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultKills\");\r\n let dropsResultDiv = document.getElementById(\"simulationResultDrops\");\r\n let noRngDropsResultDiv = document.getElementById(\"noRngDrops\");\r\n let newChildren = [];\r\n let newDropChildren = [];\r\n let newNoRngDropChildren = [];\r\n\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let encountersPerHour = 0;\r\n let encountersRow = null;\r\n if (simResult.isDungeon) {\r\n let wavesCompletedRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Wave Reached\", simResult.maxWaveReached]);\r\n wavesCompletedRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxWaveReached\");\r\n newChildren.push(wavesCompletedRow);\r\n let completedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Completed Dungeons\", simResult.dungeonsCompleted]);\r\n completedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsCompleted\");\r\n newChildren.push(completedDungeonsRow);\r\n if (simResult.dungeonsFailed > 0) {\r\n let failedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Failed Dungeons\", simResult.dungeonsFailed]);\r\n failedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsFailed\");\r\n newChildren.push(failedDungeonsRow);\r\n }\r\n encountersPerHour = (simResult.dungeonsCompleted / hoursSimulated).toFixed(1);\r\n let averageTime = (hoursSimulated * 60 / simResult.dungeonsCompleted).toFixed(1);\r\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Average Time\", averageTime]);\r\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.averageTime\");\r\n } else {\r\n encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\r\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Encounters\", encountersPerHour]);\r\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.encounters\");\r\n }\r\n\r\n if (simResult.maxEnrageStack > 0) {\r\n let enrageRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Enrage Stack\", simResult.maxEnrageStack]);\r\n enrageRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxEnrageStack\");\r\n newChildren.push(enrageRow);\r\n }\r\n\r\n if (simResult.debuffOnLevelGap[playerToDisplay] != 0) {\r\n let debuffOnLevelGapRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Debuff on Level Gap\", Math.round(simResult.debuffOnLevelGap[playerToDisplay] * 100) + \"%\"]);\r\n debuffOnLevelGapRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.debuffOnLevelGap\");\r\n newChildren.push(debuffOnLevelGapRow);\r\n }\r\n\r\n newChildren.push(encountersRow);\r\n\r\n Object.keys(simResult.deaths)\r\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\r\n .sort()\r\n .forEach(monster => {\r\n let killsPerHour = (simResult.deaths[monster] / hoursSimulated).toFixed(1);\r\n let monsterRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [combatMonsterDetailMap[monster].name, killsPerHour]\r\n );\r\n monsterRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + monster);\r\n newChildren.push(monsterRow);\r\n });\r\n\r\n let { totalDropMap, noRngTotalDropMap } = !simResult.isDungeon ? calcDropMaps(simResult, playerToDisplay) : {totalDropMap:new Map(), noRngTotalDropMap:new Map()};\r\n\r\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\r\n let total = 0;\r\n for (let [name, dropAmount] of totalDropMap.entries()) {\r\n let dropRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [name, dropAmount.toLocaleString()]\r\n );\r\n dropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\r\n newDropChildren.push(dropRow);\r\n\r\n let tableRow = ' tbody\");\r\n let noRngTotal = 0;\r\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\r\n let noRngDropRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [name, dropAmount.toLocaleString()]\r\n );\r\n noRngDropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\r\n newNoRngDropChildren.push(noRngDropRow);\r\n\r\n let tableRow = ' prev + cur, 0);\r\n }\r\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\r\n let totalRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Total\", totalExperiencePerHour]);\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"].forEach((skill) => {\r\n let experience = simResult.experienceGained[playerToDisplay]?.[skill.toLowerCase()] ?? 0;\r\n if (experience == 0) {\r\n return;\r\n }\r\n let experiencePerHour = (experience / hoursSimulated).toFixed(0);\r\n let experienceRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [skill, experiencePerHour]);\r\n experienceRow.firstElementChild.setAttribute(\"data-i18n\", \"leaderboardCategoryNames.\" + skill.toLowerCase());\r\n newChildren.push(experienceRow);\r\n });\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showHpSpent(simResult, playerToDisplay) {\r\n let hpSpentHeadingDiv = document.getElementById(\"simulationHpSpentHeading\");\r\n hpSpentHeadingDiv.classList.add(\"d-none\");\r\n let hpSpentDiv = document.getElementById(\"simulationHpSpent\");\r\n hpSpentDiv.classList.add(\"d-none\");\r\n\r\n if (simResult.hitpointsSpent[playerToDisplay]) {\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let hpSpentSources = [];\r\n for (const source of Object.keys(simResult.hitpointsSpent[playerToDisplay])) {\r\n let hpSpentPerHour = (simResult.hitpointsSpent[playerToDisplay][source] / hoursSimulated).toFixed(2);\r\n let hpSpentRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [abilityDetailMap[source].name, hpSpentPerHour]);\r\n hpSpentRow.firstElementChild.setAttribute(\"data-i18n\", \"abilityNames.\" + source);\r\n hpSpentSources.push(hpSpentRow);\r\n }\r\n hpSpentDiv.replaceChildren(...hpSpentSources);\r\n hpSpentHeadingDiv.classList.remove(\"d-none\");\r\n hpSpentDiv.classList.remove(\"d-none\");\r\n }\r\n}\r\n\r\nfunction showConsumablesUsed(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultConsumablesUsed\");\r\n let newChildren = [];\r\n\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n\r\n if (!simResult.consumablesUsed[playerToDisplay]) {\r\n resultDiv.replaceChildren(...newChildren);\r\n window.expenses = 0;\r\n return;\r\n }\r\n\r\n let consumablesUsed = Object.entries(simResult.consumablesUsed[playerToDisplay]).sort((a, b) => b[1] - a[1]);\r\n\r\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\r\n let total = 0;\r\n for (const [consumable, amount] of consumablesUsed) {\r\n let consumablesPerHour = (amount / hoursSimulated).toFixed(0);\r\n let consumableRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [itemDetailMap[consumable].name, consumablesPerHour]\r\n );\r\n consumableRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + consumable);\r\n newChildren.push(consumableRow);\r\n\r\n let tableRow = ' b[1] - a[1]);\r\n\r\n let totalHitpointsGained = hitpointsGained.reduce((prev, cur) => prev + cur[1], 0);\r\n let totalHitpointsPerSecond = (totalHitpointsGained / secondsSimulated).toFixed(2);\r\n let totalRow = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [\"Total\", totalHitpointsPerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [source, amount] of hitpointsGained) {\r\n if (amount == 0) {\r\n continue;\r\n }\r\n\r\n let sourceText;\r\n let sourceFullHrid;\r\n switch (source) {\r\n case \"regen\":\r\n sourceText = \"Regen\";\r\n sourceFullHrid = \"combatStats.hpRegenPer10\";\r\n break;\r\n case \"lifesteal\":\r\n sourceText = \"Life Steal\";\r\n sourceFullHrid = \"combatStats.lifeSteal\";\r\n break;\r\n case \"bloom\":\r\n sourceText = \"Bloom\";\r\n sourceFullHrid = \"combatStats.bloom\";\r\n break;\r\n default:\r\n if (itemDetailMap[source]) {\r\n sourceText = itemDetailMap[source].name;\r\n sourceFullHrid = \"itemNames.\" + source;\r\n } else if (abilityDetailMap[source]) {\r\n sourceText = abilityDetailMap[source].name;\r\n sourceFullHrid = \"abilityNames.\" + source;\r\n }\r\n break;\r\n }\r\n let hitpointsPerSecond = (amount / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * amount) / totalHitpointsGained).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [sourceText, hitpointsPerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showManapointsGained(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultManaRestored\");\r\n let newChildren = [];\r\n\r\n let secondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n if (!simResult.manapointsGained[playerToDisplay]) {\r\n resultDiv.replaceChildren(...newChildren);\r\n return;\r\n }\r\n\r\n let manapointsGained = Object.entries(simResult.manapointsGained[playerToDisplay]).sort((a, b) => b[1] - a[1]);\r\n\r\n let totalManapointsGained = manapointsGained.reduce((prev, cur) => prev + cur[1], 0);\r\n let totalManapointsPerSecond = (totalManapointsGained / secondsSimulated).toFixed(2);\r\n let totalRow = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [\"Total\", totalManapointsPerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [source, amount] of manapointsGained) {\r\n if (amount == 0) {\r\n continue;\r\n }\r\n\r\n let sourceText;\r\n let sourceFullHrid;\r\n switch (source) {\r\n case \"regen\":\r\n sourceText = \"Regen\";\r\n sourceFullHrid = \"combatStats.mpRegenPer10\";\r\n break;\r\n case \"manaLeech\":\r\n sourceText = \"Mana Leech\";\r\n sourceFullHrid = \"combatStats.manaLeech\";\r\n break;\r\n case \"ripple\":\r\n sourceText = \"Ripple\";\r\n sourceFullHrid = \"combatStats.ripple\";\r\n break;\r\n default:\r\n sourceText = itemDetailMap[source].name;\r\n sourceFullHrid = \"itemNames.\" + source;\r\n break;\r\n }\r\n let manapointsPerSecond = (amount / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * amount) / totalManapointsGained).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [sourceText, manapointsPerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n let ranOutOfManaText = simResult.playerRanOutOfMana[playerToDisplay] ? \"Yes\" : \"No\";\r\n let ranOutOfManaRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Ran out of mana\", ranOutOfManaText]);\r\n ranOutOfManaRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfMana\");\r\n ranOutOfManaRow.lastElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.\" + ranOutOfManaText);\r\n newChildren.push(ranOutOfManaRow);\r\n\r\n if (simResult.playerRanOutOfMana[playerToDisplay]) {\r\n let ranOutOfManaStat = simResult.playerRanOutOfManaTime[playerToDisplay]; // {isOutOfMana: false, startTimeForOutOfMana:0, totalTimeForOutOfMana:0};\r\n let totalTimeForOut = ranOutOfManaStat.totalTimeForOutOfMana + (ranOutOfManaStat.isOutOfMana ? (simResult.simulatedTime - ranOutOfManaStat.startTimeForOutOfMana) : 0);\r\n\r\n let ranOutOfManaStatRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [\r\n \"Run Out Ratio\",\r\n (totalTimeForOut / simResult.simulatedTime * 100).toFixed(2) + \"%\"\r\n ]\r\n );\r\n ranOutOfManaStatRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfManaRatio\");\r\n newChildren.push(ranOutOfManaStatRow);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showDamageDone(simResult, playerToDisplay) {\r\n let totalDamageDone = {};\r\n let enemyIndex = 1;\r\n\r\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n for (let i = 1; i < 64; i++) {\r\n let accordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + i);\r\n hideElement(accordion);\r\n }\r\n\r\n let bossTimeHeadingDiv = document.getElementById(\"simulationBossTimeHeading\");\r\n bossTimeHeadingDiv.classList.add(\"d-none\");\r\n let bossTimeDiv = document.getElementById(\"simulationBossTime\");\r\n bossTimeDiv.classList.add(\"d-none\");\r\n\r\n if (!simResult.attacks[playerToDisplay]) {\r\n return;\r\n }\r\n\r\n for (const [target, abilities] of Object.entries(simResult.attacks[playerToDisplay])) {\r\n let targetDamageDone = {};\r\n\r\n const i = simResult.timeSpentAlive.findIndex(e => e.name === target);\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\r\n\r\n for (const [ability, abilityCasts] of Object.entries(abilities)) {\r\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\r\n let misses = abilityCasts[\"miss\"] ?? 0;\r\n let damage = Object.entries(abilityCasts)\r\n .filter((entry) => entry[0] != \"miss\")\r\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\r\n\r\n targetDamageDone[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n if (totalDamageDone[ability]) {\r\n totalDamageDone[ability].casts += casts;\r\n totalDamageDone[ability].misses += misses;\r\n totalDamageDone[ability].damage += damage;\r\n } else {\r\n totalDamageDone[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n }\r\n }\r\n\r\n let resultDiv = document.getElementById(\"simulationResultDamageDoneEnemy\" + enemyIndex);\r\n createDamageTable(resultDiv, targetDamageDone, aliveSecondsSimulated);\r\n\r\n let resultAccordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + enemyIndex);\r\n showElement(resultAccordion);\r\n\r\n let resultAccordionButton = document.getElementById(\r\n \"buttonSimulationResultDamageDoneAccordionEnemy\" + enemyIndex\r\n );\r\n let targetName = combatMonsterDetailMap[target].name;\r\n resultAccordionButton.innerHTML = \"Damage Done (\" + \"\" + targetName + \"\" + \")\";\r\n\r\n if (simResult.bossSpawns.includes(target)) {\r\n let hoursSpentOnBoss = (aliveSecondsSimulated / 60 / 60).toFixed(2);\r\n let percentSpentOnBoss = (aliveSecondsSimulated / totalSecondsSimulated * 100).toFixed(2);\r\n\r\n let bossRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [targetName, hoursSpentOnBoss + \"h(\" + percentSpentOnBoss + \"%)\"]);\r\n bossRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + target);\r\n bossTimeDiv.replaceChildren(bossRow);\r\n\r\n bossTimeHeadingDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.classList.remove(\"d-none\");\r\n }\r\n\r\n enemyIndex++;\r\n }\r\n\r\n if (simResult.isDungeon) {\r\n let newChildren = [];\r\n for (const waveName of simResult.bossSpawns) {\r\n // waveName is something like \"#15,/monsters/jackalope,/monsters/butterjerry\"\r\n let waveNumber = waveName.split(\",\")[0];\r\n const idx = simResult.timeSpentAlive.findIndex(e => e.name === waveNumber);\r\n if (idx == -1 || simResult.timeSpentAlive[idx].count == 0) {\r\n continue;\r\n }\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[idx].timeSpentAlive / ONE_SECOND / simResult.timeSpentAlive[idx].count;\r\n let bossRow = createRow([\"col-md-6\", \"col-md-2\", \"col-md-4 text-end\"], [waveNumber, simResult.timeSpentAlive[idx].count, aliveSecondsSimulated.toFixed(1) + \"s\"]);\r\n newChildren.push(bossRow);\r\n }\r\n if (newChildren.length > 0) {\r\n bossTimeHeadingDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.replaceChildren(...newChildren);\r\n }\r\n }\r\n\r\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageDone\");\r\n createDamageTable(totalResultDiv, totalDamageDone, totalSecondsSimulated);\r\n}\r\n\r\nfunction showDamageTaken(simResult, playerToDisplay) {\r\n let totalDamageTaken = {};\r\n let enemyIndex = 1;\r\n\r\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n for (let i = 1; i < 64; i++) {\r\n let accordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + i);\r\n hideElement(accordion);\r\n }\r\n\r\n for (const [source, targets] of Object.entries(simResult.attacks)) {\r\n const validSources = [\"player1\", \"player2\", \"player3\", \"player4\", \"player5\"];\r\n if (validSources.includes(source)) {\r\n continue;\r\n }\r\n const i = simResult.timeSpentAlive.findIndex(e => e.name === source);\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\r\n let sourceDamageTaken = {};\r\n if (targets[playerToDisplay] && Object.keys(targets[playerToDisplay]).length > 0) {\r\n for (const [ability, abilityCasts] of Object.entries(targets[playerToDisplay])) {\r\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\r\n let misses = abilityCasts[\"miss\"] ?? 0;\r\n let damage = Object.entries(abilityCasts)\r\n .filter((entry) => entry[0] != \"miss\")\r\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\r\n\r\n sourceDamageTaken[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n if (totalDamageTaken[ability]) {\r\n totalDamageTaken[ability].casts += casts;\r\n totalDamageTaken[ability].misses += misses;\r\n totalDamageTaken[ability].damage += damage;\r\n } else {\r\n totalDamageTaken[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n }\r\n }\r\n }\r\n\r\n let resultDiv = document.getElementById(\"simulationResultDamageTakenEnemy\" + enemyIndex);\r\n createDamageTable(resultDiv, sourceDamageTaken, aliveSecondsSimulated);\r\n\r\n let resultAccordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + enemyIndex);\r\n showElement(resultAccordion);\r\n\r\n let resultAccordionButton = document.getElementById(\r\n \"buttonSimulationResultDamageTakenAccordionEnemy\" + enemyIndex\r\n );\r\n let sourceName = combatMonsterDetailMap[source].name;\r\n resultAccordionButton.innerHTML = \"Damage Taken (\" + \"\" + sourceName + \"\" + \")\";\r\n\r\n enemyIndex++;\r\n }\r\n\r\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageTaken\");\r\n createDamageTable(totalResultDiv, totalDamageTaken, totalSecondsSimulated);\r\n}\r\n\r\nfunction createDamageTable(resultDiv, damageDone, secondsSimulated) {\r\n let newChildren = [];\r\n\r\n let sortedDamageDone = Object.entries(damageDone).sort((a, b) => b[1].damage - a[1].damage);\r\n\r\n let totalCasts = sortedDamageDone.reduce((prev, cur) => prev + cur[1].casts, 0);\r\n let totalMisses = sortedDamageDone.reduce((prev, cur) => prev + cur[1].misses, 0);\r\n let totalDamage = sortedDamageDone.reduce((prev, cur) => prev + cur[1].damage, 0);\r\n let totalHitChance = ((100 * (totalCasts - totalMisses)) / totalCasts).toFixed(1);\r\n let totalDamagePerSecond = (totalDamage / secondsSimulated).toFixed(2);\r\n\r\n let totalRow = createRow(\r\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\r\n [\"Total\", totalHitChance + \"%\", totalDamagePerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [ability, damageInfo] of sortedDamageDone) {\r\n let abilityText;\r\n let abilityFullHrid;\r\n switch (ability) {\r\n case \"autoAttack\":\r\n abilityText = \"Auto Attack\";\r\n abilityFullHrid = \"combatUnit.autoAttack\";\r\n break;\r\n case \"parry\":\r\n abilityText = \"Parry Attack\";\r\n abilityFullHrid = \"common:simulationResults.parryAttack\";\r\n break;\r\n case \"damageOverTime\":\r\n abilityText = \"Damage Over Time\";\r\n abilityFullHrid = \"common:simulationResults.damageOverTime\";\r\n break;\r\n case \"physicalThorns\":\r\n abilityText = \"Physical Thorns\";\r\n abilityFullHrid = \"combatStats.physicalThorns\";\r\n break;\r\n case \"elementalThorns\":\r\n abilityText = \"Elemental Thorns\";\r\n abilityFullHrid = \"combatStats.elementalThorns\";\r\n break;\r\n case \"retaliation\":\r\n abilityText = \"Retaliation\";\r\n abilityFullHrid = \"combatStats.retaliation\";\r\n break;\r\n case 'blaze':\r\n abilityText = \"Blaze\";\r\n abilityFullHrid = \"combatStats.blaze\";\r\n break;\r\n default:\r\n abilityText = abilityDetailMap[ability].name;\r\n abilityFullHrid = \"abilityNames.\" + ability;\r\n break;\r\n }\r\n\r\n let hitChance = ((100 * (damageInfo.casts - damageInfo.misses)) / damageInfo.casts).toFixed(1);\r\n let damagePerSecond = (damageInfo.damage / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * damageInfo.damage) / totalDamage).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\r\n [abilityText, hitChance + \"%\", damagePerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", abilityFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction createRow(columnClassNames, columnValues) {\r\n let row = createElement(\"div\", \"row\");\r\n\r\n for (let i = 0; i < columnClassNames.length; i++) {\r\n let column = createElement(\"div\", columnClassNames[i], columnValues[i]);\r\n row.appendChild(column);\r\n }\r\n\r\n return row;\r\n}\r\n\r\nfunction createElement(tagName, className, innerHTML = \"\", id = \"\") {\r\n let element = document.createElement(tagName);\r\n element.className = className;\r\n element.innerHTML = innerHTML;\r\n if (id) element.id = id;\r\n return element;\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Simulation Controls\r\n\r\ndocument.addEventListener('DOMContentLoaded', function () {\r\n const simDungeonToggle = document.getElementById('simDungeonToggle');\r\n const playerContainer = document.getElementById('playerCheckBox');\r\n\r\n function addPlayers() {\r\n const player4 = document.createElement('div');\r\n player4.classList.add('form-check');\r\n player4.innerHTML = `\r\n \r\n \r\n `;\r\n\r\n const player5 = document.createElement('div');\r\n player5.classList.add('form-check');\r\n player5.innerHTML = `\r\n \r\n \r\n `;\r\n\r\n playerContainer.appendChild(player4);\r\n playerContainer.appendChild(player5);\r\n }\r\n\r\n function removePlayers() {\r\n const player4 = document.getElementById('player4');\r\n const player5 = document.getElementById('player5');\r\n if (player4) player4.parentElement.remove();\r\n if (player5) player5.parentElement.remove();\r\n }\r\n\r\n function updatePlayerNames() {\r\n const tabLinks = document.querySelectorAll('#playerTab .nav-link');\r\n tabLinks.forEach((tabLink, index) => {\r\n const label = document.querySelector(`label[for=\"player${index + 1}\"]`);\r\n if (label) {\r\n label.textContent = tabLink.textContent.trim();\r\n }\r\n });\r\n }\r\n\r\n function updatePlayersCheckbox(isCheck) {\r\n const boxes = playerContainer.querySelectorAll('.player-checkbox');\r\n boxes.forEach((checkBox) => { checkBox.checked = isCheck });\r\n }\r\n\r\n function updateDifficultySelect(isCheck) {\r\n const difficultySelect = document.getElementById('selectDifficulty');\r\n // disable last four option\r\n if (isCheck && Number(difficultySelect.value) >= 3) {\r\n difficultySelect.value = 0;\r\n }\r\n for (let i = 3; i < difficultySelect.options.length; i++) {\r\n difficultySelect.options[i].disabled = isCheck;\r\n }\r\n }\r\n\r\n simDungeonToggle.addEventListener('change', function () {\r\n if (simDungeonToggle.checked) {\r\n addPlayers();\r\n updatePlayersCheckbox(true);\r\n updateDifficultySelect(true);\r\n } else {\r\n removePlayers();\r\n updatePlayersCheckbox(false);\r\n updateDifficultySelect(false);\r\n }\r\n updatePlayerNames();\r\n });\r\n\r\n document.getElementById('buttonSimulationSetup').addEventListener('click', function () {\r\n updatePlayerNames();\r\n });\r\n});\r\n\r\nfunction onTabChange(event) {\r\n const nextPlayerTabId = event.target.getAttribute('href').substring(7);\r\n savePreviousPlayer(currentPlayerTabId);\r\n updateNextPlayer(nextPlayerTabId);\r\n currentPlayerTabId = nextPlayerTabId;\r\n updateState();\r\n updateUI();\r\n if (Object.keys(currentSimResults).length !== 0) {\r\n showSimulationResult(currentSimResults);\r\n }\r\n\r\n updateContent();\r\n}\r\n\r\ndocument.querySelectorAll('#playerTab .nav-link').forEach(tab => {\r\n tab.addEventListener('shown.bs.tab', onTabChange);\r\n});\r\n\r\nfunction initSimulationControls() {\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n simulationTimeInput.value = 24;\r\n\r\n buttonStartSimulation.addEventListener(\"click\", (event) => {\r\n let invalidElements = document.querySelectorAll(\":invalid\");\r\n if (invalidElements.length > 0) {\r\n invalidElements.forEach((element) => element.reportValidity());\r\n return;\r\n }\r\n savePreviousPlayer(currentPlayerTabId);\r\n\r\n const simDungeonToggle = document.getElementById(\"simDungeonToggle\");\r\n const checkboxes = document.querySelectorAll('.player-checkbox');\r\n selectedPlayers = [];\r\n checkboxes.forEach(checkbox => {\r\n if (checkbox.checked) {\r\n const playerNumber = parseInt(checkbox.id.replace('player', ''));\r\n selectedPlayers.push(playerNumber);\r\n }\r\n });\r\n\r\n if (selectedPlayers.length === 0) {\r\n alert(\"You need to select at least one player to sim.\");\r\n return;\r\n }\r\n // buttonStartSimulation.disabled = true;\r\n buttonStopSimulation.style.display = 'block';\r\n startSimulation(selectedPlayers);\r\n });\r\n\r\n buttonStopSimulation.style.display = 'none';\r\n buttonStopSimulation.addEventListener(\"click\", (event) => {\r\n progressbar.style.width = \"0%\";\r\n progressbar.innerHTML = \"0%\";\r\n if (worker) {\r\n worker.terminate();\r\n }\r\n worker = new Worker(new URL(\"worker.js\", import.meta.url));\r\n\r\n if (multiWorker) {\r\n multiWorker.terminate();\r\n }\r\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n\r\n for (let worker of workerPool) {\r\n worker.worker.terminate();\r\n }\r\n\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n });\r\n}\r\n\r\nfunction startSimulation(selectedPlayers) {\r\n let playersToSim = [];\r\n for (let j = 1; j < 6; j++) {\r\n if (selectedPlayers.includes(j)) {\r\n updateNextPlayer(j);\r\n updateState();\r\n updateUI();\r\n player.hrid = \"player\" + j.toString();\r\n for (let i = 0; i < 3; i++) {\r\n if (food[i] && i < player.combatDetails.combatStats.foodSlots) {\r\n let consumable = new Consumable(food[i], triggerMap[food[i]]);\r\n player.food[i] = consumable;\r\n } else {\r\n player.food[i] = null;\r\n }\r\n\r\n if (drinks[i] && i < player.combatDetails.combatStats.drinkSlots) {\r\n let consumable = new Consumable(drinks[i], triggerMap[drinks[i]]);\r\n player.drinks[i] = consumable;\r\n } else {\r\n player.drinks[i] = null;\r\n }\r\n }\r\n\r\n for (let i = 0; i < 5; i++) {\r\n if (abilities[i] && player.intelligenceLevel >= abilitySlotsLevelRequirementList[i + 1]) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let ability = new Ability(abilities[i], Number(abilityLevelInput.value), triggerMap[abilities[i]]);\r\n player.abilities[i] = ability;\r\n } else {\r\n player.abilities[i] = null;\r\n }\r\n }\r\n\r\n playersToSim.push(structuredClone(player));\r\n }\r\n }\r\n updateNextPlayer(currentPlayerTabId);\r\n updateState();\r\n updateUI();\r\n\r\n let maxPlayerCombatLevel = 1;\r\n for (let player of playersToSim) {\r\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\r\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\r\n }\r\n\r\n for (let player of playersToSim) {\r\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\r\n const maxDebuffOnLevelGap = 0.9;\r\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\r\n\r\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\r\n\r\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\r\n }\r\n else {\r\n player.debuffOnLevelGap = 0;\r\n }\r\n }\r\n\r\n let extra = {};\r\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\r\n extra.comExp = 0;\r\n if (document.getElementById(\"comExpToggle\").checked) {\r\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\r\n }\r\n extra.comDrop = 0;\r\n if (document.getElementById(\"comDropToggle\").checked) {\r\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\r\n }\r\n\r\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\r\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\r\n let simDungeonToggle = document.getElementById(\"simDungeonToggle\");\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let dungeonSelect = document.getElementById(\"selectDungeon\");\r\n let difficultySelect = document.getElementById(\"selectDifficulty\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let simulationTimeLimit = Number(simulationTimeInput.value) * ONE_HOUR;\r\n buttonStopSimulation.style.display = 'block';\r\n if (!simAllZonesToggle.checked && !simAllSoloToggle.checked) {\r\n let zoneHrid = zoneSelect.value;\r\n let difficultyTier = Number(difficultySelect.value);\r\n if (simDungeonToggle.checked) {\r\n zoneHrid = dungeonSelect.value;\r\n }\r\n let workerMessage = {\r\n type: \"start_simulation\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n simStartTime = Date.now();\r\n if (!worker) {\r\n worker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n }\r\n worker.onmessage = onWorkerMessage;\r\n worker.postMessage(workerMessage);\r\n } else {\r\n let targetHrids = {};\r\n\r\n if (simAllZonesToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1 &&\r\n document.getElementById(a.hrid)?.checked\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n if (simAllSoloToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount === 1 &&\r\n document.getElementById(a.hrid)?.checked\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n let simHrids = Object.values(targetHrids)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .map(action => {\r\n let result = [];\r\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\r\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\r\n }\r\n return result;\r\n })\r\n .flat();\r\n\r\n let workerMessage = {\r\n type: \"start_simulation_all_zones\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zones: simHrids,\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra: extra\r\n };\r\n simStartTime = Date.now();\r\n if (!multiWorker) {\r\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n }\r\n multiWorker.onmessage = onMultiWorkerMessage;\r\n multiWorker.postMessage(workerMessage);\r\n }\r\n}\r\n\r\nfunction parsePlayerJson(playerJson, hrid) {\r\n let playerData = {\r\n hrid: hrid,\r\n food: [],\r\n drinks: [],\r\n abilities: [],\r\n ...playerJson.player,\r\n houseRooms: playerJson.houseRooms,\r\n };\r\n playerData.equipment = {};\r\n const triggerMap = playerJson.triggerMap;\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"main_hand\", \"two_hand\", \"charm\"].forEach((type) => {\r\n let currentEquipment = playerJson.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment){\r\n playerData.equipment[`/equipment_types/${type}`] = new Equipment(currentEquipment.itemHrid, currentEquipment.enhancementLevel);\r\n }\r\n });\r\n\r\n for (const foodHrid of playerJson.food[\"/action_types/combat\"]) {\r\n if (foodHrid.itemHrid === \"\") continue;\r\n const food = new Consumable(foodHrid.itemHrid, triggerMap[foodHrid.itemHrid]);\r\n playerData.food.push(food);\r\n }\r\n for (const drinkHrid of playerJson.drinks[\"/action_types/combat\"]) {\r\n if (drinkHrid.itemHrid === \"\") continue;\r\n const drink = new Consumable(drinkHrid.itemHrid, triggerMap[drinkHrid.itemHrid]);\r\n playerData.drinks.push(drink);\r\n }\r\n for (const ability of playerJson.abilities) {\r\n if (ability.abilityHrid === \"\") continue;\r\n const abilityLevel = Number(ability.level);\r\n const abilityHrid = ability.abilityHrid;\r\n if (abilityLevel > 0) {\r\n const abilityObj = new Ability(abilityHrid, abilityLevel, triggerMap[abilityHrid]);\r\n playerData.abilities.push(abilityObj);\r\n }\r\n }\r\n const player = Player.createFromDTO(playerData)\r\n player.updateCombatDetails();\r\n player.houseRooms = playerJson.houseRooms;\r\n player.achievements = playerJson.achievements ?? {};\r\n return player;\r\n}\r\n// read JSON file to simulate\r\ndocument.getElementById(\"buttonUploadJSONSimulate\").addEventListener(\"click\", (event) => {\r\n let extra = {};\r\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\r\n extra.comExp = 0;\r\n if (document.getElementById(\"comExpToggle\").checked) {\r\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\r\n }\r\n extra.comDrop = 0;\r\n if (document.getElementById(\"comDropToggle\").checked) {\r\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\r\n }\r\n\r\n let fileInput = document.getElementById(\"inputUploadJSONSimulation\");\r\n let file = fileInput.files[0];\r\n if (!file) {\r\n alert(\"Please select a file to upload.\");\r\n return;\r\n }\r\n\r\n let reader = new FileReader();\r\n reader.onload = function (event) {\r\n let fileContent = event.target.result;\r\n const jsonDataList = JSON.parse(fileContent);\r\n try {\r\n const simDataList = [];\r\n for (const key in jsonDataList) {\r\n if (jsonDataList[key].cases) {\r\n const cases = getProductCases(jsonDataList[key], jsonDataList[key].cases);\r\n simDataList.push(...cases);\r\n } else {\r\n simDataList.push(jsonDataList[key]);\r\n }\r\n }\r\n for (const key in simDataList) {\r\n const jsonData = simDataList[key];\r\n if (!jsonData || !jsonData.zone || !jsonData.players) {\r\n alert(\"Invalid JSON file format. Please ensure it contains a 'simulationResult' property.\");\r\n return;\r\n }\r\n const playersToSim = Object.values(jsonData.players).map(\r\n (player, index) => parsePlayerJson(player, `player${index + 1}`)\r\n );\r\n\r\n let maxPlayerCombatLevel = 1;\r\n for (let player of playersToSim) {\r\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\r\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\r\n }\r\n\r\n for (let player of playersToSim) {\r\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\r\n const maxDebuffOnLevelGap = 0.9;\r\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\r\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\r\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\r\n }\r\n else {\r\n player.debuffOnLevelGap = 0;\r\n }\r\n }\r\n\r\n const simulationTimeLimit = (jsonData.simulationTimeLimit || 24) * ONE_HOUR;\r\n const simName = jsonData.name || `Json ${key}`;\r\n const zoneHrid = jsonData.zone;\r\n if (zoneHrid === \"all\") {\r\n let targetHrids = {};\r\n\r\n if (simAllZonesToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n let simHrids = Object.values(targetHrids)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .map(action => {\r\n let result = [];\r\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\r\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\r\n }\r\n return result;\r\n })\r\n .flat();\r\n\r\n let workerMessage = {\r\n simulationName: simName,\r\n type: \"start_simulation_all_zones\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zones: simHrids,\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \r\n worker.onmessage = mainWorkerOnMessage;\r\n worker.postMessage(workerMessage);\r\n customAlert(\"Simulation task Created\", \"info\")\r\n workerPool.push({\r\n workerId: workerMessage.workerId,\r\n worker: worker,\r\n });\r\n } else {\r\n let difficultyTier = jsonData.difficultyTier || 0;\r\n let workerMessage = {\r\n simulationName: simName,\r\n type: \"start_simulation\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \r\n worker.onmessage = mainWorkerOnMessage;\r\n worker.postMessage(workerMessage);\r\n customAlert(\"Simulation task Created\", \"info\")\r\n workerPool.push({\r\n workerId: workerMessage.workerId,\r\n worker: worker,\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n // alert(\"Error parsing JSON file: \" + error.message);\r\n customAlert(\"Error parsing JSON file: \" + error.message, \"danger\");\r\n }\r\n }\r\n reader.readAsText(file);\r\n});\r\n\r\n\r\n// #endregion\r\n\r\n// #region WipeEvents\r\n\r\nfunction renderWipeEvents(simResult) {\r\n const selector = document.getElementById('wipeEventSelector');\r\n const logsContainer = document.getElementById('wipeLogsContainer');\r\n const waveBadge = document.getElementById('wipeWaveBadge');\r\n const timeInfo = document.getElementById('wipeTimeInfo');\r\n\r\n selector.innerHTML = '';\r\n logsContainer.innerHTML = '';\r\n\r\n if (!simResult.wipeEvents || simResult.wipeEvents.length === 0) {\r\n selector.innerHTML = ``;\r\n logsContainer.innerHTML = `
No Wipe Events Detected
`;\r\n waveBadge.textContent = '';\r\n timeInfo.textContent = '';\r\n return;\r\n }\r\n\r\n simResult.wipeEvents.forEach((event, index) => {\r\n const wave = event.wave || '?';\r\n // const time = (event.simulationTime / 1e9).toFixed(2);\r\n // const timestamp = new Date(event.timestamp).toLocaleTimeString();\r\n\r\n const option = document.createElement('option');\r\n option.value = index;\r\n option.textContent = `#${index + 1} - 波次: ${wave}`;\r\n selector.appendChild(option);\r\n });\r\n\r\n selector.value = 0;\r\n renderSelectedWipeEvent(0, simResult);\r\n\r\n selector.addEventListener('change', () => {\r\n renderSelectedWipeEvent(selector.value, simResult);\r\n });\r\n}\r\n\r\n// 渲染选中的团灭事件\r\nfunction renderSelectedWipeEvent(index, simResult) {\r\n const logsContainer = document.getElementById('wipeLogsContainer');\r\n const waveBadge = document.getElementById('wipeWaveBadge');\r\n const timeInfo = document.getElementById('wipeTimeInfo');\r\n\r\n logsContainer.innerHTML = '';\r\n\r\n if (index < 0 || index >= simResult.wipeEvents.length) {\r\n logsContainer.innerHTML = `
No Wipe Events
`;\r\n waveBadge.textContent = '';\r\n timeInfo.textContent = '';\r\n return;\r\n }\r\n\r\n const wipeEvent = simResult.wipeEvents[index];\r\n const wave = wipeEvent.wave || '?';\r\n const time = (wipeEvent.simulationTime / 1e9).toFixed(2);\r\n const timestamp = new Date(wipeEvent.timestamp).toLocaleString();\r\n\r\n waveBadge.textContent = `波次: ${wave}`;\r\n timeInfo.textContent = `模拟时间: ${time}s | 记录时间: ${timestamp}`;\r\n\r\n const logsByTime = groupLogsByTime(wipeEvent.logs);\r\n\r\n const baseTime = logsByTime.length > 0 ? logsByTime[0].time : 0;\r\n\r\n logsByTime.forEach(group => {\r\n const timeGroupElement = document.createElement('div');\r\n timeGroupElement.className = 'log-time-group';\r\n\r\n const relativeTime = (group.time - baseTime) / 1e9;\r\n\r\n // 时间标题\r\n const timeHeader = document.createElement('div');\r\n timeHeader.className = 'log-time-header';\r\n timeHeader.textContent = `[${relativeTime.toFixed(2)}s] [Wave#${group.wave}]`;\r\n timeGroupElement.appendChild(timeHeader);\r\n\r\n // 事件列表\r\n const eventsList = document.createElement('div');\r\n eventsList.className = 'log-events';\r\n\r\n const damagedPlayers = new Set();\r\n\r\n group.logs.forEach(log => {\r\n const eventElement = document.createElement('div');\r\n eventElement.className = 'log-event';\r\n\r\n damagedPlayers.add(log.target);\r\n\r\n const sourceSpan = document.createElement('span');\r\n sourceSpan.className = 'log-source';\r\n if (log.ability === \"damageOverTime\") {\r\n sourceSpan.textContent = log.target;\r\n } else if(log.source == 'UNKNOWN_SOURCE') {\r\n sourceSpan.textContent = 'UNKNOWN';\r\n } else {\r\n sourceSpan.setAttribute('data-i18n', `monsterNames.${log.source}`);\r\n sourceSpan.textContent = log.source;\r\n }\r\n\r\n const castSpan = document.createElement('span');\r\n castSpan.className = 'log-cast';\r\n castSpan.setAttribute('data-i18n', `common:cast`);\r\n castSpan.textContent = ' cast ';\r\n\r\n const abilitySpan = document.createElement('span');\r\n abilitySpan.className = 'log-ability';\r\n if (log.ability === \"autoAttack\") {\r\n abilitySpan.setAttribute('data-i18n', 'combatUnit.autoAttack');\r\n abilitySpan.textContent = 'Auto Attack';\r\n } else if (log.ability === \"physicalThorns\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.physicalThorns`);\r\n abilitySpan.textContent = 'Physical Thorns';\r\n } else if (log.ability === \"elementalThorns\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.elementalThorns`);\r\n abilitySpan.textContent = 'Elemental Thorns';\r\n } else if (log.ability === \"retaliation\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.retaliation`);\r\n abilitySpan.textContent = 'Retaliation';\r\n } else if (log.ability === \"damageOverTime\") {\r\n abilitySpan.setAttribute('data-i18n', `common:simulationResults.damageOverTime`);\r\n abilitySpan.textContent = 'Damage Over Time';\r\n } else {\r\n abilitySpan.setAttribute('data-i18n', `abilityNames.${log.ability}`);\r\n abilitySpan.textContent = log.ability;\r\n }\r\n\r\n const toSpan = document.createElement('span');\r\n toSpan.className = 'log-to';\r\n toSpan.setAttribute('data-i18n', `common:to`);\r\n toSpan.textContent = ' to ';\r\n\r\n const targetSpan = document.createElement('span');\r\n targetSpan.className = 'log-target';\r\n targetSpan.textContent = log.target;\r\n\r\n const dealDamageSpan = document.createElement('span');\r\n dealDamageSpan.className = 'log-deal-damage';\r\n dealDamageSpan.setAttribute('data-i18n', `common:dealDamage`);\r\n dealDamageSpan.textContent = ' deal damage ';\r\n\r\n const damageDoneSpan = document.createElement('span');\r\n damageDoneSpan.className = 'log-damage-done';\r\n damageDoneSpan.textContent = log.damage;\r\n if (log.isCrit) {\r\n damageDoneSpan.style.fontWeight = 'bold';\r\n damageDoneSpan.textContent += '!!!';\r\n }\r\n\r\n eventElement.appendChild(sourceSpan);\r\n eventElement.appendChild(castSpan);\r\n eventElement.appendChild(abilitySpan);\r\n eventElement.appendChild(toSpan);\r\n eventElement.appendChild(targetSpan);\r\n eventElement.appendChild(dealDamageSpan);\r\n eventElement.appendChild(damageDoneSpan);\r\n eventElement.appendChild(document.createTextNode(` , HP ${log.beforeHp} → ${log.afterHp}`));\r\n\r\n eventsList.appendChild(eventElement);\r\n });\r\n\r\n timeGroupElement.appendChild(eventsList);\r\n\r\n const lastLog = group.logs[group.logs.length - 1];\r\n const playersHpElement = document.createElement('div');\r\n\r\n const playerHpTitle = document.createElement('span');\r\n playerHpTitle.className = 'log-players-hp';\r\n playerHpTitle.setAttribute('data-i18n', `common:playersHp`);\r\n playerHpTitle.textContent = 'Players HP: ';\r\n playersHpElement.appendChild(playerHpTitle);\r\n\r\n lastLog.playersHp.forEach((player, idx) => {\r\n const playerElement = document.createElement('span');\r\n playerElement.className = 'log-player-hp';\r\n playerElement.textContent = `${player.hrid}: ${player.current}/${player.max}`;\r\n\r\n if (player.current <= 0) {\r\n playerElement.style.color = darkModeToggle.checked ? '#FF6347' : '#CC0000';\r\n } else if (damagedPlayers.has(player.hrid)) {\r\n playerElement.style.color = darkModeToggle.checked ? '#00BFFF' : '#007BFF';\r\n }\r\n\r\n if (idx > 0) {\r\n playersHpElement.appendChild(document.createTextNode(' | '));\r\n }\r\n playersHpElement.appendChild(playerElement);\r\n });\r\n const spacer = document.createElement('div');\r\n spacer.style.height = '15px';\r\n logsContainer.appendChild(spacer);\r\n timeGroupElement.appendChild(playersHpElement);\r\n logsContainer.appendChild(timeGroupElement);\r\n });\r\n\r\n // 更新汉化\r\n updateContent()\r\n}\r\n\r\n// 按时间分组日志\r\nfunction groupLogsByTime(logs) {\r\n const groups = [];\r\n let currentGroup = null;\r\n\r\n logs.forEach(log => {\r\n if (!currentGroup || currentGroup.time !== log.time) {\r\n currentGroup = {\r\n time: log.time,\r\n wave: log.wave,\r\n logs: [log]\r\n };\r\n groups.push(currentGroup);\r\n } else {\r\n currentGroup.logs.push(log);\r\n }\r\n });\r\n\r\n groups.forEach(group => {\r\n let hpMap = {};\r\n if (group.logs.length > 0) {\r\n group.logs[0].playersHp.forEach(p => {\r\n hpMap[p.hrid] = { current: p.current, max: p.max };\r\n });\r\n }\r\n group.logs.forEach(log => {\r\n if (hpMap[log.target]) {\r\n hpMap[log.target].current = log.afterHp;\r\n }\r\n });\r\n group.logs.forEach(log => {\r\n log.playersHp = Object.entries(hpMap).map(([hrid, val]) => ({\r\n hrid,\r\n current: val.current,\r\n max: val.max\r\n }));\r\n });\r\n });\r\n\r\n return groups;\r\n}\r\n\r\n// #endregion\r\n\r\n\r\n// #region Equipment Sets\r\n\r\nfunction initEquipmentSetsModal() {\r\n let equipmentSetsModal = document.getElementById(\"equipmentSetsModal\");\r\n equipmentSetsModal.addEventListener(\"show.bs.modal\", equipmentSetsModalShownHandler);\r\n\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n equipmentSetNameInput.addEventListener(\"input\", (event) => equipmentSetNameChangedHandler(event));\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.addEventListener(\"click\", createNewEquipmentSetHandler);\r\n}\r\n\r\nfunction equipmentSetsModalShownHandler() {\r\n resetNewEquipmentSetControls();\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction resetNewEquipmentSetControls() {\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n equipmentSetNameInput.value = \"\";\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.disabled = true;\r\n}\r\n\r\nfunction updateEquipmentSetList() {\r\n let newChildren = [];\r\n let equipmentSets = loadEquipmentSets();\r\n\r\n for (const equipmentSetName of Object.keys(equipmentSets)) {\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let nameCol = createElement(\"div\", \"col align-self-center\", equipmentSetName);\r\n row.appendChild(nameCol);\r\n\r\n let loadButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let loadButton = createElement(\"button\", \"btn btn-primary\", \"Load\");\r\n loadButton.setAttribute(\"data-i18n\", \"common:controls.load\");\r\n loadButton.setAttribute(\"type\", \"button\");\r\n loadButton.addEventListener(\"click\", (_) => loadEquipmentSetHandler(equipmentSetName));\r\n loadButtonCol.appendChild(loadButton);\r\n row.appendChild(loadButtonCol);\r\n\r\n let saveButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let saveButton = createElement(\"button\", \"btn btn-primary\", \"Save\");\r\n saveButton.setAttribute(\"data-i18n\", \"common:controls.save\");\r\n saveButton.setAttribute(\"type\", \"button\");\r\n saveButton.addEventListener(\"click\", (_) => updateEquipmentSetHandler(equipmentSetName));\r\n saveButtonCol.appendChild(saveButton);\r\n row.appendChild(saveButtonCol);\r\n\r\n let deleteButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let deleteButton = createElement(\"button\", \"btn btn-danger\", \"Delete\");\r\n deleteButton.setAttribute(\"data-i18n\", \"common:controls.delete\");\r\n deleteButton.setAttribute(\"type\", \"button\");\r\n deleteButton.addEventListener(\"click\", (_) => deleteEquipmentSetHandler(equipmentSetName));\r\n deleteButtonCol.appendChild(deleteButton);\r\n row.appendChild(deleteButtonCol);\r\n\r\n newChildren.push(row);\r\n }\r\n\r\n let equipmentSetList = document.getElementById(\"equipmentSetList\");\r\n equipmentSetList.replaceChildren(...newChildren);\r\n\r\n updateContent();\r\n}\r\n\r\nfunction equipmentSetNameChangedHandler(event) {\r\n let invalid = false;\r\n\r\n if (event.target.value.length == 0) {\r\n invalid = true;\r\n }\r\n\r\n let equipmentSets = loadEquipmentSets();\r\n if (equipmentSets[event.target.value]) {\r\n invalid = true;\r\n }\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.disabled = invalid;\r\n}\r\n\r\nfunction createNewEquipmentSetHandler() {\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n let equipmentSetName = equipmentSetNameInput.value;\r\n\r\n let equipmentSet = getEquipmentSetFromUI();\r\n let equipmentSets = loadEquipmentSets();\r\n equipmentSets[equipmentSetName] = equipmentSet;\r\n saveEquipmentSets(equipmentSets);\r\n\r\n resetNewEquipmentSetControls();\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction loadEquipmentSetHandler(name) {\r\n let equipmentSets = loadEquipmentSets();\r\n loadEquipmentSetIntoUI(equipmentSets[name]);\r\n}\r\n\r\nfunction updateEquipmentSetHandler(name) {\r\n let equipmentSet = getEquipmentSetFromUI();\r\n let equipmentSets = loadEquipmentSets();\r\n equipmentSets[name] = equipmentSet;\r\n saveEquipmentSets(equipmentSets);\r\n}\r\n\r\nfunction deleteEquipmentSetHandler(name) {\r\n let equipmentSets = loadEquipmentSets();\r\n delete equipmentSets[name];\r\n saveEquipmentSets(equipmentSets);\r\n\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction loadEquipmentSets() {\r\n return JSON.parse(localStorage.getItem(\"equipmentSets\")) ?? {};\r\n}\r\n\r\nfunction saveEquipmentSets(equipmentSets) {\r\n localStorage.setItem(\"equipmentSets\", JSON.stringify(equipmentSets));\r\n}\r\n\r\nfunction getEquipmentSetFromUI() {\r\n let equipmentSet = {\r\n levels: {},\r\n equipment: {},\r\n food: {},\r\n drinks: {},\r\n abilities: {},\r\n triggerMap: {},\r\n houseRooms: {},\r\n achievements: {},\r\n };\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n equipmentSet.levels[skill] = Number(levelInput.value);\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n\r\n equipmentSet.equipment[type] = {\r\n equipment: equipmentSelect.value,\r\n enhancementLevel: Number(enhancementLevelInput.value),\r\n };\r\n });\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n equipmentSet.food[i] = foodSelect.value;\r\n }\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n equipmentSet.drinks[i] = drinkSelect.value;\r\n }\r\n\r\n for (let i = 0; i < 5; i++) {\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n equipmentSet.abilities[i] = {\r\n ability: abilitySelect.value,\r\n level: Number(abilityLevelInput.value),\r\n };\r\n }\r\n\r\n equipmentSet.triggerMap = triggerMap;\r\n\r\n equipmentSet.houseRooms = player.houseRooms;\r\n equipmentSet.achievements = player.achievements;\r\n\r\n return equipmentSet;\r\n}\r\n\r\nfunction fixTriggerMap(triggerMap) {\r\n let delKeys = []\r\n for (const key of Object.keys(triggerMap)) {\r\n let err = false;\r\n if (null == triggerMap[key]) {\r\n triggerMap[key] = [];\r\n }\r\n for (const trigger of triggerMap[key]) {\r\n if (!combatTriggerConditionDetailMap[trigger.conditionHrid]) {\r\n err = true;\r\n break;\r\n }\r\n }\r\n if (err) {\r\n delKeys.push(key);\r\n }\r\n }\r\n for (const key of delKeys) {\r\n delete triggerMap[key];\r\n }\r\n}\r\n\r\nfunction loadEquipmentSetIntoUI(equipmentSet) {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !equipmentSet.levels[\"meleeLevel\"] && equipmentSet.levels[\"powerLevel\"]) {\r\n equipmentSet.levels[\"meleeLevel\"] = equipmentSet.levels[\"powerLevel\"];\r\n }\r\n levelInput.value = equipmentSet.levels[skill] ?? 1;\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n\r\n let currentEquipment = equipmentSet.equipment[type];\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.equipment;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n foodSelect.value = equipmentSet.food[i];\r\n }\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n drinkSelect.value = equipmentSet.drinks[i].replace(\"power\", \"melee\");\r\n }\r\n\r\n let hasSpecial = false;\r\n if (equipmentSet.abilities && Object.keys(equipmentSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n equipmentSet.abilities[i].ability == \"/abilities/aqua_aura\" ||\r\n equipmentSet.abilities[i].ability == \"/abilities/flame_aura\" ||\r\n equipmentSet.abilities[i].ability == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n equipmentSet.abilities[i].ability = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (equipmentSet.abilities[i].ability == \"/abilities/arcane_reflection\") {\r\n equipmentSet.abilities[i].ability = \"/abilities/retribution\";\r\n }\r\n\r\n abilitySelect.value = equipmentSet.abilities[i].ability;\r\n abilityLevelInput.value = equipmentSet.abilities[i].level;\r\n }\r\n\r\n triggerMap = equipmentSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n\r\n if (equipmentSet.houseRooms) {\r\n for (const room in equipmentSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (equipmentSet.houseRooms[room]) {\r\n field.value = equipmentSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = equipmentSet.houseRooms;\r\n } else {\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n\r\n if (equipmentSet.achievements) {\r\n for (const achievement in equipmentSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (equipmentSet.achievements[achievement]) {\r\n field.checked = true;\r\n } else {\r\n field.checked = false;\r\n }\r\n player.achievements[achievement] = field.checked;\r\n }\r\n } else {\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n refreshAchievementStatics();\r\n\r\n updateState();\r\n updateUI();\r\n\r\n updateContent();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Error Handling\r\n\r\nfunction initErrorHandling() {\r\n window.addEventListener(\"error\", (event) => {\r\n showErrorModal(event.message);\r\n });\r\n\r\n let copyErrorButton = document.getElementById(\"buttonCopyError\");\r\n copyErrorButton.addEventListener(\"click\", (event) => {\r\n let errorInput = document.getElementById(\"inputError\");\r\n navigator.clipboard.writeText(errorInput.value);\r\n });\r\n}\r\n\r\nfunction initImportExportModal() {\r\n let exportSetButton = document.getElementById(\"buttonExportSet\");\r\n exportSetButton.addEventListener(\"click\", (event) => {\r\n savePreviousPlayer(currentPlayerTabId);\r\n const activeTab = document.querySelector('#importTab .nav-link.active');\r\n if (activeTab.id === 'group-combat-tab') {\r\n doGroupExport();\r\n } else if (activeTab.id === 'solo-tab') {\r\n doSoloExport();\r\n }\r\n });\r\n\r\n let importSetButton = document.getElementById(\"buttonImportSet\");\r\n importSetButton.addEventListener(\"click\", (event) => {\r\n const activeTab = document.querySelector('#importTab .nav-link.active');\r\n if (activeTab.id === 'group-combat-tab') {\r\n doGroupImport();\r\n } else if (activeTab.id === 'solo-tab') {\r\n doSoloImport();\r\n }\r\n updateState();\r\n updateUI();\r\n resetImportInputs();\r\n });\r\n}\r\n\r\nfunction resetImportInputs() {\r\n document.getElementById('inputSetGroupCombatAll').value = '';\r\n document.getElementById('inputSetGroupCombatplayer1').value = '';\r\n document.getElementById('inputSetGroupCombatplayer2').value = '';\r\n document.getElementById('inputSetGroupCombatplayer3').value = '';\r\n document.getElementById('inputSetGroupCombatplayer4').value = '';\r\n document.getElementById('inputSetGroupCombatplayer5').value = '';\r\n document.getElementById('inputSetSolo').value = '';\r\n}\r\n\r\nfunction doGroupExport() {\r\n try {\r\n navigator.clipboard.writeText(JSON.stringify(playerDataMap)).then(() => alert(\"Current Group has been copied to clipboard.\"));\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction doSoloExport() {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let equipmentArray = [];\r\n for (const item in player.equipment) {\r\n if (player.equipment[item] != null) {\r\n equipmentArray.push({\r\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\r\n \"itemHrid\": player.equipment[item].hrid,\r\n \"enhancementLevel\": player.equipment[item].enhancementLevel\r\n });\r\n }\r\n }\r\n let playerArray = {\r\n \"attackLevel\": player.attackLevel,\r\n \"magicLevel\": player.magicLevel,\r\n \"meleeLevel\": player.meleeLevel,\r\n \"rangedLevel\": player.rangedLevel,\r\n \"defenseLevel\": player.defenseLevel,\r\n \"staminaLevel\": player.staminaLevel,\r\n \"intelligenceLevel\": player.intelligenceLevel,\r\n \"equipment\": equipmentArray\r\n };\r\n let abilitiesArray = [];\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let abilityName = document.getElementById(\"selectAbility_\" + i);\r\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\r\n }\r\n let drinksArray = [];\r\n for (let i = 0; i < drinks?.length; i++) {\r\n drinksArray.push({ \"itemHrid\": drinks[i] });\r\n }\r\n let foodArray = [];\r\n for (let i = 0; i < food?.length; i++) {\r\n foodArray.push({ \"itemHrid\": food[i] });\r\n }\r\n let state = {\r\n player: playerArray,\r\n food: { \"/action_types/combat\": foodArray },\r\n drinks: { \"/action_types/combat\": drinksArray },\r\n abilities: abilitiesArray,\r\n triggerMap: triggerMap,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n houseRooms: player.houseRooms,\r\n achievements: player.achievements\r\n };\r\n try {\r\n navigator.clipboard.writeText(JSON.stringify(state)).then(() => alert(\"Current set has been copied to clipboard.\"));\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction setPlayerData(playerId, inputElementId) {\r\n const inputElement = document.getElementById(inputElementId);\r\n const value = inputElement ? inputElement.value.trim() : \"\";\r\n\r\n // Only set the value in the map if it's not null, undefined, or empty\r\n if (value) {\r\n playerDataMap[playerId] = value;\r\n return true;\r\n }\r\n return false;\r\n}\r\n\r\nfunction doGroupImport() {\r\n let needUpdateCurrentTab = false;\r\n const value = document.getElementById(\"inputSetGroupCombatAll\")?.value || \"\";\r\n if (!value.trim()) {\r\n for (let i of ['1', '2', '3', '4', '5']) {\r\n if (setPlayerData(i, \"inputSetGroupCombatplayer\" + i) && currentPlayerTabId == i) {\r\n needUpdateCurrentTab = true;\r\n }\r\n }\r\n } else {\r\n playerDataMap = JSON.parse(value);\r\n needUpdateCurrentTab = true;\r\n }\r\n\r\n if (needUpdateCurrentTab) {\r\n updateNextPlayer(currentPlayerTabId);\r\n }\r\n}\r\n\r\nfunction doSoloImport() {\r\n let importSet = document.getElementById(\"inputSetSolo\").value;\r\n importSet = JSON.parse(importSet);\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\r\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\r\n }\r\n levelInput.value = importSet.player[skill + \"Level\"];\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.itemHrid;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\r\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\r\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\r\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\r\n if (mainhandWeapon !== undefined) {\r\n weaponSelect.value = mainhandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\r\n } else if (twohandWeapon !== undefined) {\r\n weaponSelect.value = twohandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\r\n } else {\r\n weaponSelect.value = \"\";\r\n weaponEnhancementLevelInput.value = 0;\r\n }\r\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\r\n importSet.food = importSet.food[\"/action_types/combat\"];\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n if (importSet.drinks[i] != null) {\r\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\r\n } else {\r\n drinkSelect.value = \"\";\r\n }\r\n if (importSet.food[i] != null) {\r\n foodSelect.value = importSet.food[i].itemHrid;\r\n } else {\r\n foodSelect.value = \"\";\r\n }\r\n }\r\n\r\n let hasSpecial = false;\r\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\r\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\r\n }\r\n\r\n if (importSet.abilities[i] != null) {\r\n abilitySelect.value = importSet.abilities[i].abilityHrid;\r\n abilityLevelInput.value = String(importSet.abilities[i].level);\r\n } else {\r\n abilitySelect.value = \"\";\r\n abilityLevelInput.value = \"1\";\r\n }\r\n }\r\n\r\n if (importSet.triggerMap) {\r\n triggerMap = importSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n }\r\n\r\n if (importSet.houseRooms) {\r\n for (const room in importSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (importSet.houseRooms[room]) {\r\n field.value = importSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = importSet.houseRooms;\r\n } else {\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n\r\n if (importSet.achievements) {\r\n for (const achievement in importSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (importSet.achievements[achievement]) {\r\n field.checked = true;\r\n } else {\r\n field.checked = false;\r\n }\r\n player.achievements[achievement] = field.checked;\r\n }\r\n } else {\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n refreshAchievementStatics();\r\n\r\n if (\"zone\" in importSet) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n zoneSelect.value = importSet[\"zone\"];\r\n }\r\n\r\n if (\"simulationTime\" in importSet) {\r\n let simulationDuration = document.getElementById(\"inputSimulationTime\");\r\n simulationDuration.value = importSet[\"simulationTime\"];\r\n }\r\n}\r\n\r\nfunction savePreviousPlayer(playerId) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let equipmentArray = [];\r\n for (const item in player.equipment) {\r\n if (player.equipment[item] != null) {\r\n equipmentArray.push({\r\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\r\n \"itemHrid\": player.equipment[item].hrid,\r\n \"enhancementLevel\": player.equipment[item].enhancementLevel\r\n });\r\n }\r\n }\r\n let playerArray = {\r\n \"attackLevel\": player.attackLevel,\r\n \"magicLevel\": player.magicLevel,\r\n \"meleeLevel\": player.meleeLevel,\r\n \"rangedLevel\": player.rangedLevel,\r\n \"defenseLevel\": player.defenseLevel,\r\n \"staminaLevel\": player.staminaLevel,\r\n \"intelligenceLevel\": player.intelligenceLevel,\r\n \"equipment\": equipmentArray\r\n };\r\n let abilitiesArray = [];\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let abilityName = document.getElementById(\"selectAbility_\" + i);\r\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\r\n }\r\n let drinksArray = [];\r\n for (let i = 0; i < drinks?.length; i++) {\r\n drinksArray.push({ \"itemHrid\": drinks[i] });\r\n }\r\n let foodArray = [];\r\n for (let i = 0; i < food?.length; i++) {\r\n foodArray.push({ \"itemHrid\": food[i] });\r\n }\r\n let state = {\r\n player: playerArray,\r\n food: { \"/action_types/combat\": foodArray },\r\n drinks: { \"/action_types/combat\": drinksArray },\r\n abilities: abilitiesArray,\r\n triggerMap: triggerMap,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n houseRooms: player.houseRooms,\r\n achievements: player.achievements\r\n };\r\n try {\r\n playerDataMap[playerId] = JSON.stringify(state);\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction updateNextPlayer(currentPlayerNumber) {\r\n let playerImportData = playerDataMap[currentPlayerNumber];\r\n let importSet = JSON.parse(playerImportData);\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\r\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\r\n }\r\n levelInput.value = importSet.player[skill + \"Level\"];\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.itemHrid;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\r\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\r\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\r\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\r\n if (mainhandWeapon !== undefined) {\r\n weaponSelect.value = mainhandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\r\n } else if (twohandWeapon !== undefined) {\r\n weaponSelect.value = twohandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\r\n } else {\r\n weaponSelect.value = \"\";\r\n weaponEnhancementLevelInput.value = 0;\r\n }\r\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\r\n importSet.food = importSet.food[\"/action_types/combat\"];\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n if (importSet.drinks[i] != null) {\r\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\r\n } else {\r\n drinkSelect.value = \"\";\r\n }\r\n if (importSet.food[i] != null) {\r\n foodSelect.value = importSet.food[i].itemHrid;\r\n } else {\r\n foodSelect.value = \"\";\r\n }\r\n }\r\n\r\n let hasSpecial = false;\r\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\r\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\r\n }\r\n\r\n if (importSet.abilities[i] != null) {\r\n abilitySelect.value = importSet.abilities[i].abilityHrid;\r\n abilityLevelInput.value = String(importSet.abilities[i].level);\r\n } else {\r\n abilitySelect.value = \"\";\r\n abilityLevelInput.value = \"1\";\r\n }\r\n }\r\n\r\n if (importSet.triggerMap) {\r\n triggerMap = importSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n }\r\n\r\n { // reset all houseRooms\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n if (importSet.houseRooms) {\r\n for (const room in importSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (importSet.houseRooms[room]) {\r\n field.value = importSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = importSet.houseRooms;\r\n }\r\n\r\n { // reset all achievements\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n if (importSet.achievements) {\r\n for (const achievement in importSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (importSet.achievements[achievement]) {\r\n field.checked = true;\r\n player.achievements[achievement] = true;\r\n } else {\r\n field.checked = false;\r\n player.achievements[achievement] = false;\r\n }\r\n }\r\n }\r\n refreshAchievementStatics();\r\n}\r\n\r\nfunction showErrorModal(error) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n\r\n let state = {\r\n error: error,\r\n player: player,\r\n food: food,\r\n drinks: drinks,\r\n abilities: abilities,\r\n triggerMap: triggerMap,\r\n modalTriggers: modalTriggers,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n };\r\n\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n state[\"abilityLevel\" + i] = abilityLevelInput.value;\r\n }\r\n\r\n let errorInput = document.getElementById(\"inputError\");\r\n errorInput.value = JSON.stringify(state);\r\n\r\n let errorModal = new bootstrap.Modal(document.getElementById(\"errorModal\"));\r\n errorModal.show();\r\n}\r\n\r\nwindow.prices;\r\n\r\nasync function fetchPrices() {\r\n let response = null;\r\n try {\r\n response = await fetch('https://www.milkywayidle.com/game_data/marketplace.json'\r\n , {\r\n mode: 'cors'\r\n }\r\n );\r\n if (!response.ok) {\r\n console.log('Error fetching prices');\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n\r\n if (response == null) {\r\n try {\r\n response = await fetch('https://www.milkywayidlecn.com/game_data/marketplace.json'\r\n , {\r\n mode: 'cors'\r\n }\r\n );\r\n if (!response.ok) {\r\n console.log('Error fetching prices');\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n }\r\n\r\n if (!response || !response.ok) {\r\n return;\r\n }\r\n\r\n try {\r\n\r\n let btn = document.querySelector('#buttonGetPrices');\r\n btn.style.backgroundColor = 'green';\r\n\r\n const pricesJson = await response.json();\r\n\r\n const priceTmp = pricesJson['marketData'];\r\n window.prices = {};\r\n for (const item in itemDetailMap) {\r\n const hrid = itemDetailMap[item].hrid;\r\n if (hrid in priceTmp) {\r\n window.prices[hrid] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (priceTmp[hrid]['0']) {\r\n window.prices[hrid].ask = priceTmp[hrid]['0'].a;\r\n window.prices[hrid].bid = priceTmp[hrid]['0'].b;\r\n }\r\n }\r\n } \r\n\r\n window.prices[\"/items/coin\"] = { \"ask\": 1, \"bid\": 1, \"vendor\": 1 };\r\n\r\n window.prices[\"/items/small_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n window.prices[\"/items/medium_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n window.prices[\"/items/large_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n}\r\n\r\ndocument.getElementById(\"buttonGetPrices\").onclick = async () => {\r\n await fetchPrices();\r\n};\r\n\r\ndocument.addEventListener(\"input\", (e) => {\r\n let element = e.target;\r\n if (element.tagName == \"TD\" && element.parentNode.parentNode.parentNode.classList.value.includes('profit-table')) {\r\n let tableId = element.parentNode.parentNode.parentNode.id;\r\n let row = element.parentNode.querySelectorAll('td');\r\n let item = row[0].getAttribute('data-i18n').split('.')[1];\r\n let newPrice = element.innerText;\r\n\r\n let revenueSetting = document.getElementById('selectPrices_drops').value;\r\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\r\n\r\n let expensesDifference = 0;\r\n let revenueDifference = 0;\r\n let noRngRevenueDifference = 0;\r\n\r\n if (tableId == 'expensesTable') {\r\n expensesDifference = updateTable('expensesTable', item, newPrice);\r\n if (revenueSetting == expensesSetting) {\r\n revenueDifference = updateTable('revenueTable', item, newPrice);\r\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\r\n }\r\n if (window.prices) {\r\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (expensesSetting == 'bid') {\r\n window.prices[item]['bid'] = newPrice;\r\n } else {\r\n window.prices[item]['ask'] = newPrice;\r\n }\r\n }\r\n } else {\r\n revenueDifference = updateTable('revenueTable', item, newPrice);\r\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\r\n if (revenueSetting == expensesSetting) {\r\n expensesDifference = updateTable('expensesTable', item, newPrice);\r\n }\r\n if (window.prices) {\r\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (revenueSetting == 'bid') {\r\n window.prices[item]['bid'] = newPrice;\r\n } else {\r\n window.prices[item]['ask'] = newPrice;\r\n }\r\n }\r\n }\r\n\r\n window.expenses += expensesDifference;\r\n document.getElementById('expensesSpan').innerText = window.expenses.toLocaleString();\r\n window.revenue += revenueDifference;\r\n document.getElementById('revenueSpan').innerText = window.revenue.toLocaleString();\r\n window.noRngRevenue += noRngRevenueDifference;\r\n document.getElementById('noRngRevenueSpan').innerText = window.noRngRevenue.toLocaleString();\r\n\r\n window.profit = window.revenue - window.expenses;\r\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\r\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\r\n window.noRngProfit = window.noRngRevenue - window.expenses;\r\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\r\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\r\n }\r\n});\r\n\r\nfunction updateTable(tableId, item, price) {\r\n let row = document.querySelector('#' + tableId + ' .' + CSS.escape(item));\r\n if (row == null) {\r\n return 0;\r\n }\r\n\r\n row = row.querySelectorAll('td');\r\n let priceTd = row[1];\r\n let amountTd = row[2];\r\n let totalTd = row[3];\r\n let oldTotal = totalTd.innerText;\r\n let newTotal = price * amountTd.innerText;\r\n\r\n if (priceTd.innerText != price) {\r\n priceTd.innerText = price;\r\n }\r\n totalTd.innerText = newTotal;\r\n\r\n return newTotal - oldTotal;\r\n}\r\n\r\n// #endregion\r\n\r\nfunction initPatchNotes() {\r\n const patchNotesRows = document.getElementById(\"patchNotes\");\r\n for (const pn in patchNote) {\r\n const patchNoteContainer = document.createElement(\"div\");\r\n patchNotesRows.setAttribute('class', 'col-12 mb-4');\r\n\r\n const patchNoteElement = document.createElement(\"h6\");\r\n patchNoteElement.innerHTML = pn;\r\n const patchNoteList = document.createElement(\"ul\");\r\n for (const note of patchNote[pn]) {\r\n const noteElement = document.createElement(\"li\");\r\n noteElement.innerHTML = note;\r\n patchNoteList.appendChild(noteElement);\r\n }\r\n patchNoteContainer.appendChild(patchNoteElement);\r\n patchNoteContainer.appendChild(patchNoteList);\r\n\r\n patchNotesRows.appendChild(patchNoteContainer);\r\n }\r\n}\r\n\r\nfunction initExtraBuffSection() {\r\n // mooPass\r\n let mooPassToggle = document.getElementById(\"mooPassToggle\");\r\n let mooPass = localStorage.getItem('mooPass');\r\n if (mooPass) {\r\n mooPassToggle.checked = Boolean(mooPass);\r\n }\r\n mooPassToggle.onchange = () => {\r\n localStorage.setItem('mooPass', mooPassToggle.checked);\r\n }\r\n \r\n // comExp\r\n let comExpToggle = document.getElementById(\"comExpToggle\");\r\n let comExpInput = document.getElementById(\"comExpInput\");\r\n let comExp = localStorage.getItem('comExp');\r\n if (comExp) {\r\n let comExpNumber = Number(comExp);\r\n if (comExpNumber > 0) {\r\n comExpToggle.checked = true;\r\n comExpInput.value = comExpNumber;\r\n } else {\r\n comExpToggle.checked = false;\r\n comExpInput.disabled = true;\r\n }\r\n }\r\n const updateComExp = () => {\r\n if (comExpToggle.checked) {\r\n let comExp = Number(comExpInput.value);\r\n localStorage.setItem('comExp', comExp); \r\n comExpInput.disabled = false;\r\n } else {\r\n localStorage.setItem('comExp', 0);\r\n comExpInput.disabled = true;\r\n }\r\n }\r\n comExpToggle.onchange = updateComExp;\r\n comExpInput.onchange = updateComExp;\r\n\r\n // comDrop\r\n let comDropToggle = document.getElementById(\"comDropToggle\");\r\n let comDropInput = document.getElementById(\"comDropInput\");\r\n let comDrop = localStorage.getItem('comDrop');\r\n if (comDrop) {\r\n let comDropNumber = Number(comDrop);\r\n if (comDropNumber > 0) {\r\n comDropToggle.checked = true;\r\n comDropInput.value = comDropNumber;\r\n } else {\r\n comDropToggle.checked = false;\r\n comDropInput.disabled = true;\r\n }\r\n }\r\n const updateComDrop = () => {\r\n if (comDropToggle.checked) {\r\n let comDrop = Number(comDropInput.value);\r\n localStorage.setItem('comDrop', comDrop); \r\n comDropInput.disabled = false;\r\n } else {\r\n localStorage.setItem('comDrop', 0);\r\n comDropInput.disabled = true;\r\n }\r\n }\r\n comDropToggle.onchange = updateComDrop;\r\n comDropInput.onchange = updateComDrop;\r\n}\r\n\r\n\r\nfunction updateState() {\r\n updateEquipmentState();\r\n updateLevels();\r\n updateFoodState();\r\n updateDrinksState();\r\n updateAbilityState();\r\n}\r\n\r\nfunction updateUI() {\r\n updateCombatStatsUI();\r\n updateFoodUI();\r\n updateDrinksUI();\r\n updateAbilityUI();\r\n\r\n updateContent();\r\n}\r\n\r\nconst darkModeToggle = document.getElementById('darkModeToggle');\r\nconst body = document.body;\r\n\r\nif (localStorage.getItem('darkModeEnabled') === 'true') {\r\n body.classList.add('dark-mode');\r\n const tables = document.getElementsByClassName('profit-table');\r\n for (const table of tables) {\r\n table.classList.toggle('table-striped');\r\n }\r\n darkModeToggle.checked = true;\r\n}\r\n\r\ndarkModeToggle.addEventListener('change', () => {\r\n body.classList.toggle('dark-mode');\r\n const tables = document.getElementsByClassName('profit-table');\r\n for (const table of tables) {\r\n table.classList.toggle('table-striped');\r\n }\r\n localStorage.setItem('darkModeEnabled', darkModeToggle.checked);\r\n});\r\n\r\nfunction updateContent() {\r\n document.querySelectorAll('[data-i18n]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n');\r\n if (key) {\r\n element.textContent = i18next.t(key);\r\n }\r\n });\r\n\r\n document.querySelectorAll('[data-i18n-placeholder]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n-placeholder');\r\n if (key) {\r\n element.placeholder = i18next.t(key);\r\n }\r\n });\r\n\r\n document.querySelectorAll('option[data-i18n]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n');\r\n if (key) {\r\n element.textContent = i18next.t(key);\r\n }\r\n });\r\n}\r\n\r\ninitEquipmentSection();\r\ninitHouseRoomsModal();\r\ninitAchievementsModal();\r\ninitLevelSection();\r\ninitFoodSection();\r\ninitDrinksSection();\r\ninitAbilitiesSection();\r\ninitZones();\r\ninitDungeons();\r\ninitTriggerModal();\r\ninitSimulationControls();\r\ninitEquipmentSetsModal();\r\ninitErrorHandling();\r\ninitImportExportModal();\r\ninitDamageDoneTaken();\r\ninitPatchNotes();\r\ninitExtraBuffSection();\r\n\r\nupdateState();\r\nupdateUI();\r\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/src/main.js b/src/main.js index 60f09450..07f6ed27 100644 --- a/src/main.js +++ b/src/main.js @@ -44,11 +44,11 @@ let currentSimResults = {}; let currentPlayerTabId = '1'; let playerDataMap = { - "1": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},achievements:{}}", - "2": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},achievements:{}}", - "3": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},achievements:{}}", - "4": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},achievements:{}}", - "5": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},achievements:{}}" + "1": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},\"achievements\":{}}", + "2": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},\"achievements\":{}}", + "3": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},\"achievements\":{}}", + "4": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},\"achievements\":{}}", + "5": "{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"meleeLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0,\"/house_rooms/observatory\":0},\"achievements\":{}}" }; window.revenue = 0; window.noRngRevenue = 0; From 86aa24a500a1f2835b21c95d3294a637b2b88df7 Mon Sep 17 00:00:00 2001 From: shykai Date: Mon, 22 Dec 2025 09:19:12 +0800 Subject: [PATCH 04/46] fix --- dist/bundle.js | 2 +- dist/bundle.js.map | 2 +- src/main.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/bundle.js b/dist/bundle.js index de5c174f..5fde8c23 100644 --- a/dist/bundle.js +++ b/dist/bundle.js @@ -1806,7 +1806,7 @@ let simStartTime = 0; let worker = new Worker(new URL(/* worker import */ __webpack_require__.p + __webpack_require__.u(1), __webpack_require__.b)); let multiWorker = new Worker(new URL(/* worker import */ __webpack_require__.p + __webpack_require__.u("src_multiWorker_js-_004d1"), __webpack_require__.b)); - +let workerPool = []; let player = new _combatsimulator_player_js__WEBPACK_IMPORTED_MODULE_1__["default"](); diff --git a/dist/bundle.js.map b/dist/bundle.js.map index 725e832a..af7e7479 100644 --- a/dist/bundle.js.map +++ b/dist/bundle.js.map @@ -1 +1 @@ -{"version":3,"file":"bundle.js","mappings":";;;;;;;;;;;;;;;;;AAA0B;AACkC;AAC5B;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,wDAAgB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,6CAAI;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;ACxNG;AACkD;AACR;AACpE;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,gEAAwB;AAChE;AACA,0CAA0C,4DAAoB;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,WAAW;;;;;;;;;;;;;;AC1B1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,IAAI,EAAC;;;;;;;;;;;;;;;ACdpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wDAAwD;AACtF,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,mBAAmB;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;;AC/gBA;AAC4B;AACtB;AAChC;AACA;AACA;AACA;AACA;AACA,6BAA6B,qDAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;ACtF4B;AACmD;AACzG;AACA;AACA;AACA;AACA,uBAAuB,qDAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,iFAAoC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS,EAAC;;;;;;;;;;;;;;;;;AC/CC;AACsC;AAChE;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,0DAAkB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS;;;;;;;;;;;;;;;;;;;;AC7BQ;AACM;AACA;AACF;AACA;AACI;AACxC;AACA,qBAAqB,mDAAU;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,kDAAS;AACrD;AACA;AACA,qDAAqD,mDAAU;AAC/D,2DAA2D,mDAAU;AACrE,qEAAqE,gDAAO;AAC5E;AACA;AACA,2CAA2C,kDAAS;AACpD;AACA,SAAS;AACT;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,MAAM,EAAC;;;;;;;;;;;;;;;;ACvLsE;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,wEAAgC;AAC5C;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UCjLvB;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;UAEA;UACA;;;;;WCzBA;WACA;WACA;WACA;WACA,yCAAyC,wCAAwC;WACjF;WACA;WACA;;;;;WCPA;WACA;WACA;WACA;WACA;;;;;WCJA;WACA;WACA;WACA;WACA,GAAG;WACH;WACA;WACA,CAAC;;;;;WCPD;;;;;WCAA;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D;;;;;WCNA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;;;;WClBA;;WAEA;WACA;WACA;WACA;WACA;WACA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrBuD;AACN;AAC2B;AACN;AACU;AAC7B;AACM;AACN;AACyD;AACF;AACE;AACA;AAClC;AACc;AACN;AACE;AACF;AACG;AACF;AACnF;AAC0C;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,2FAA4B;AAC5D,qCAAqC,qHAAiC;AACtE;AACA;AACA;AACA,iBAAiB,kEAAM;AACvB;AACA,IAAI,SAAI;AACR,IAAI,WAAM;AACV,IAAI,cAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA,sCAAsC,qEAAa;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,mCAAmC,0EAAkB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,iFAAkB;AAClD;AACA,4FAA4F,eAAe;AAC3G;AACA;AACA;AACA,uDAAuD,eAAe;AACtE,6BAA6B,KAAK,GAAG,MAAM;AAC3C;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,iFAAkB;AAClD;AACA,sCAAsC,6EAAoB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,eAAe;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oEAAoE,eAAe;AACnF,mCAAmC,eAAe;AAClD;AACA;AACA,gGAAgG,eAAe;AAC/G;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,gCAAgC,eAAe;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,iBAAiB;AACpD;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,iDAAiD,iBAAiB;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,qEAAa;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,qEAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,qEAAS;AACvD,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,6EAAoB;AACvD;AACA;AACA,qBAAqB,4EAAmB;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,sCAAsC,qEAAa;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,SAAI;AACZ,YAAY,SAAI,mBAAmB,SAAI;AACvC,2BAA2B,qEAAa,CAAC,SAAI;AAC7C,uBAAuB,SAAI;AAC3B;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,qFAAqF,SAAI;AACzF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,uCAAuC,qEAAa;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,WAAM;AACd,YAAY,WAAM,mBAAmB,WAAM;AAC3C,2BAA2B,qEAAa,CAAC,WAAM;AAC/C,uBAAuB,WAAM;AAC7B;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,sFAAsF,WAAM;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D,UAAU;AACV,0CAA0C,wEAAgB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,cAAS;AACjB,YAAY,cAAS,mBAAmB,cAAS;AACjD,8BAA8B,wEAAgB,CAAC,cAAS;AACxD,uBAAuB,cAAS;AAChC;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,4DAA4D,yFAAgC;AAC5F,2DAA2D,yFAAgC;AAC3F,4DAA4D,yFAAgC,YAAY,cAAS;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAS;AACjC,IAAI,cAAS,iBAAiB,cAAS;AACvC,IAAI,cAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,cAAS;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,SAAI;AAChC;AACA;AACA,4BAA4B,WAAM;AAClC;AACA;AACA,4BAA4B,cAAS;AACrC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,qEAAa;AACrD,MAAM;AACN,wCAAwC,wEAAgB;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,yFAAgC;AAC5C;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,wFAAgC;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,wFAAgC;AACrD;AACA;AACA;AACA,mCAAmC,uFAA+B;AAClE,MAAM;AACN,mCAAmC,uFAA+B;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uFAA+B;AACnD;AACA,qEAAqE,yFAAgC;AACrG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,wEAAe;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2GAA2G,WAAW;AACtH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,WAAW;AAC5C,yDAAyD,WAAW;AACpE;AACA;AACA;AACA,gEAAgE,WAAW;AAC3E;AACA;AACA,qGAAqG,WAAW;AAChH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qFAAqF,WAAW;AAChG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4GAA4G,WAAW;AACvH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,WAAW;AAC7C,0DAA0D,WAAW;AACrE;AACA;AACA;AACA,iEAAiE,WAAW;AAC5E;AACA;AACA,sGAAsG,WAAW;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sFAAsF,WAAW;AACjG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,OAAO;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uBAAuB;AAC3C,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,+EAAsB;AAClC,+BAA+B,+EAAsB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,iJAAiJ;AAC9L;AACA,gBAAgB,+EAAsB;AACtC,mCAAmC,+EAAsB;AACzD;AACA;AACA;AACA,qDAAqD,uIAAuI;AAC5L;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,+BAA+B;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,UAAU,kCAAkC;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uEAAuE,oDAAoD;AAC3H,iEAAiE,oDAAoD;AACrH,iFAAiF,oDAAoD;AACrI;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,eAAe;AACrC;AACA;AACA;AACA;AACA,0BAA0B,mBAAmB;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,SAAS;AAC3D;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,KAAK;AACL,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,iBAAiB;AACrC;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,+CAA+C,kBAAkB;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,+EAAsB;AACvC;AACA;AACA;AACA,SAAS;AACT;AACA,UAAU,kCAAkC,qEAAqE;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2EAA2E,wEAAgB;AAC3F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa,qEAAa;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,qEAAa;AACjC,iCAAiC,qEAAa;AAC9C;AACA,kBAAkB,SAAS,wEAAgB;AAC3C,iCAAiC,wEAAgB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,qEAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kFAAkF,IAAI;AACtF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,QAAQ;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,QAAQ;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wEAAgB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,6BAA6B;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qEAAqE,UAAU;AAC/E;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,sCAAsC,4BAA4B;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,qCAAqC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC,2FAA4B;AAChE;AACA;AACA;AACA;AACA,yCAAyC,qHAAiC;AAC1E;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,SAAI;AACxB,yCAAyC,sEAAU,CAAC,SAAI,gBAAgB,SAAI;AAC5E;AACA,kBAAkB;AAClB;AACA;AACA;AACA,oBAAoB,WAAM;AAC1B,yCAAyC,sEAAU,CAAC,WAAM,gBAAgB,WAAM;AAChF;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,cAAS,mCAAmC,yFAAgC;AAChG;AACA,sCAAsC,mEAAO,CAAC,cAAS,iDAAiD,cAAS;AACjH;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,oDAAoD;AACxE;AACA;AACA;AACA;AACA;AACA,wCAAwC,qHAAiC;AACzE;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;AACA;AACA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,wCAAwC;AACrF,kCAAkC,uDAAuD;AACzF;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,qHAAiC;AAC9E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,KAAK,SAAS,qEAAS;AAC5E;AACA,KAAK;AACL;AACA;AACA;AACA,yBAAyB,sEAAU;AACnC;AACA;AACA;AACA;AACA,0BAA0B,sEAAU;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,mEAAO;AAC1C;AACA;AACA;AACA,mBAAmB,kEAAM;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wEAAwE,UAAU;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,IAAI;AAC7D;AACA;AACA;AACA;AACA;AACA,sCAAsC,wEAAe;AACrD;AACA;AACA;AACA;AACA;AACA,4CAA4C,0BAA0B;AACtE;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,wCAAwC;AACjG,8CAA8C,uDAAuD;AACrG;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,oDAAoD;AACpF;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,WAAW,QAAQ,KAAK;AACzD;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,KAAK;AACxC,oCAAoC,KAAK,YAAY,UAAU;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,wBAAwB,WAAW,WAAW;AACnF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd,qEAAqE,WAAW;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd,sEAAsE,YAAY;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sEAAsE,cAAc,IAAI,YAAY;AACpG;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,YAAY,IAAI,eAAe,GAAG,WAAW;AACxF;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,kCAAkC;AAClC,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB,qBAAqB;AACrB,gBAAgB;AAChB,kBAAkB;AAClB,qBAAqB;AACrB,sBAAsB;AACtB,sBAAsB;AACtB,wBAAwB;AACxB;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,uFAA+B;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,SAAI;AAClB,gBAAgB,WAAM;AACtB,mBAAmB,cAAS;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,qEAAa;AACxC,yBAAyB,qEAAa;AACtC;AACA,wCAAwC,gCAAgC,qEAAa;AACrF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yCAAyC;AACzC;AACA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;AACA;AACA;AACA,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,sBAAsB,4GAAmD;AACzE;AACA,aAAa;AACb;AACA;AACA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,6CAAS;AAC9B;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,6CAAS;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sources":["webpack://mwicombatsimulator/./src/combatsimulator/ability.js","webpack://mwicombatsimulator/./src/combatsimulator/achievement.js","webpack://mwicombatsimulator/./src/combatsimulator/buff.js","webpack://mwicombatsimulator/./src/combatsimulator/combatUnit.js","webpack://mwicombatsimulator/./src/combatsimulator/consumable.js","webpack://mwicombatsimulator/./src/combatsimulator/equipment.js","webpack://mwicombatsimulator/./src/combatsimulator/houseRoom.js","webpack://mwicombatsimulator/./src/combatsimulator/player.js","webpack://mwicombatsimulator/./src/combatsimulator/trigger.js","webpack://mwicombatsimulator/webpack/bootstrap","webpack://mwicombatsimulator/webpack/runtime/define property getters","webpack://mwicombatsimulator/webpack/runtime/get javascript chunk filename","webpack://mwicombatsimulator/webpack/runtime/global","webpack://mwicombatsimulator/webpack/runtime/hasOwnProperty shorthand","webpack://mwicombatsimulator/webpack/runtime/make namespace object","webpack://mwicombatsimulator/webpack/runtime/publicPath","webpack://mwicombatsimulator/webpack/runtime/jsonp chunk loading","webpack://mwicombatsimulator/./src/main.js"],"sourcesContent":["import Buff from \"./buff\";\r\nimport abilityDetailMap from \"./data/abilityDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nconst abilityFromCombatStat = {\r\n \"blaze\":\r\n {\r\n \"hrid\": \"/abilities/blaze\",\r\n \"name\": \"Blaze\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"allEnemies\",\r\n \"effectType\": \"/ability_effect_types/damage\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"/damage_types/fire\",\r\n \"baseDamageFlat\": 0,\r\n \"baseDamageFlatLevelBonus\": 0.0,\r\n \"baseDamageRatio\": 0.3,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/number_of_active_units\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n },\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/current_hp\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n }\r\n ],\r\n },\r\n \"bloom\":\r\n {\r\n \"hrid\": \"/abilities/bloom\",\r\n \"name\": \"Bloom\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"lowestHpAlly\",\r\n \"effectType\": \"/ability_effect_types/heal\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"\",\r\n \"baseDamageFlat\": 10,\r\n \"baseDamageFlatLevelBonus\": 0,\r\n \"baseDamageRatio\": 0.15,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_allies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/lowest_hp_percentage\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/less_than_equal\",\r\n \"value\": 100\r\n }\r\n ],\r\n }\r\n}\r\n\r\nclass Ability {\r\n constructor(hrid, level = 1, triggers = null) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameAbility = abilityDetailMap[hrid];\r\n if (!gameAbility) {\r\n gameAbility = abilityFromCombatStat[hrid];\r\n }\r\n if (!gameAbility) {\r\n throw new Error(\"No ability found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.manaCost = gameAbility.manaCost;\r\n this.cooldownDuration = gameAbility.cooldownDuration;\r\n this.castDuration = gameAbility.castDuration;\r\n this.isSpecialAbility = gameAbility.isSpecialAbility;\r\n\r\n this.abilityEffects = [];\r\n\r\n for (const effect of gameAbility.abilityEffects) {\r\n let abilityEffect = {\r\n targetType: effect.targetType,\r\n effectType: effect.effectType,\r\n combatStyleHrid: effect.combatStyleHrid,\r\n damageType: effect.damageType,\r\n damageFlat: effect.baseDamageFlat + (this.level - 1) * effect.baseDamageFlatLevelBonus,\r\n damageRatio: effect.baseDamageRatio + (this.level - 1) * effect.baseDamageRatioLevelBonus,\r\n bonusAccuracyRatio: effect.bonusAccuracyRatio + (this.level - 1) * effect.bonusAccuracyRatioLevelBonus,\r\n damageOverTimeRatio: effect.damageOverTimeRatio,\r\n damageOverTimeDuration: effect.damageOverTimeDuration,\r\n armorDamageRatio: effect.armorDamageRatio + (this.level - 1) * effect.armorDamageRatioLevelBonus,\r\n hpDrainRatio: effect.hpDrainRatio,\r\n pierceChance: effect.pierceChance,\r\n blindChance: effect.blindChance,\r\n blindDuration: effect.blindDuration,\r\n silenceChance: effect.silenceChance,\r\n silenceDuration: effect.silenceDuration,\r\n stunChance: effect.stunChance,\r\n stunDuration: effect.stunDuration,\r\n spendHpRatio: effect.spendHpRatio,\r\n buffs: null,\r\n };\r\n if (effect.buffs) {\r\n abilityEffect.buffs = [];\r\n for (const buff of effect.buffs) {\r\n abilityEffect.buffs.push(new Buff(buff, this.level));\r\n }\r\n }\r\n this.abilityEffects.push(abilityEffect);\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameAbility.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let ability = new Ability(dto.hrid, dto.level, triggers);\r\n\r\n return ability;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n\r\n if (source.isSilenced) {\r\n return false;\r\n }\r\n\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = this.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Ability;\r\n","import Buff from \"./buff\";\r\nimport achievementTierDetailMap from \"./data/achievementTierDetailMap.json\";\r\nimport achievementDetailMap from \"./data/achievementDetailMap.json\";\r\n\r\nclass Achievement {\r\n constructor(achievements) {\r\n this.achievements = achievements;\r\n this.buffs = [];\r\n\r\n for(const tier of Object.values(achievementTierDetailMap)) {\r\n let isGetAll = true;\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid)\r\n for(const achievement of Object.values(detailMap)) {\r\n if(!this.achievements[achievement.hrid] || this.achievements[achievement.hrid] == false) {\r\n isGetAll = false;\r\n break;\r\n }\r\n }\r\n if(isGetAll) {\r\n let buff = new Buff(tier.buff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default Achievement;","class Buff {\r\n startTime;\r\n\r\n constructor(buff, level = 1) {\r\n this.uniqueHrid = buff.uniqueHrid;\r\n this.typeHrid = buff.typeHrid;\r\n this.ratioBoost = buff.ratioBoost + (level - 1) * buff.ratioBoostLevelBonus;\r\n this.flatBoost = buff.flatBoost + (level - 1) * buff.flatBoostLevelBonus;\r\n this.duration = buff.duration;\r\n this.multiplierForSkillHrid = buff.multiplierForSkillHrid ?? \"\";\r\n this.multiplierPerSkillLevel = buff.multiplierPerSkillLevel ?? 0;\r\n }\r\n}\r\n\r\nexport default Buff;\r\n","class CombatUnit {\r\n isPlayer;\r\n isStunned = false;\r\n stunExpireTime = null;\r\n isBlinded = false;\r\n blindExpireTime = null;\r\n isSilenced = false;\r\n silenceExpireTime = null;\r\n\r\n isOutOfMana = false;\r\n\r\n // Base levels which don't change after initialization\r\n staminaLevel = 1;\r\n intelligenceLevel = 1;\r\n attackLevel = 1;\r\n meleeLevel = 1;\r\n defenseLevel = 1;\r\n rangedLevel = 1;\r\n magicLevel = 1;\r\n\r\n experience = 0;\r\n experienceRate = 0;\r\n enrageTime = 0;\r\n\r\n abilities = [null, null, null, null];\r\n food = [null, null, null];\r\n drinks = [null, null, null];\r\n houseRooms = [];\r\n achievements = null;\r\n dropTable = [];\r\n rareDropTable = [];\r\n abilityManaCosts = new Map();\r\n\r\n // Calculated combat stats including temporary buffs\r\n combatDetails = {\r\n staminaLevel: 1,\r\n intelligenceLevel: 1,\r\n attackLevel: 1,\r\n meleeLevel: 1,\r\n defenseLevel: 1,\r\n rangedLevel: 1,\r\n magicLevel: 1,\r\n maxHitpoints: 110,\r\n currentHitpoints: 110,\r\n maxManapoints: 110,\r\n currentManapoints: 110,\r\n stabAccuracyRating: 11,\r\n slashAccuracyRating: 11,\r\n smashAccuracyRating: 11,\r\n rangedAccuracyRating: 11,\r\n magicAccuracyRating: 11,\r\n stabMaxDamage: 11,\r\n slashMaxDamage: 11,\r\n smashMaxDamage: 11,\r\n rangedMaxDamage: 11,\r\n magicMaxDamage: 11,\r\n stabEvasionRating: 11,\r\n slashEvasionRating: 11,\r\n smashEvasionRating: 11,\r\n rangedEvasionRating: 11,\r\n magicEvasionRating: 11,\r\n defensiveMaxDamage: 0,\r\n totalArmor: 0.2,\r\n totalWaterResistance: 0.4,\r\n totalNatureResistance: 0.4,\r\n totalFireResistance: 0.4,\r\n abilityHaste: 0,\r\n tenacity: 0,\r\n totalThreat: 100,\r\n combatStats: {\r\n combatStyleHrid: \"/combat_styles/smash\",\r\n damageType: \"/damage_types/physical\",\r\n attackInterval: 3000000000,\r\n autoAttackDamage: 0,\r\n abilityDamage: 0,\r\n criticalRate: 0,\r\n criticalDamage: 0,\r\n stabAccuracy: 0,\r\n slashAccuracy: 0,\r\n smashAccuracy: 0,\r\n rangedAccuracy: 0,\r\n magicAccuracy: 0,\r\n stabDamage: 0,\r\n slashDamage: 0,\r\n smashDamage: 0,\r\n rangedDamage: 0,\r\n magicDamage: 0,\r\n defensiveDamage: 0,\r\n taskDamage: 0,\r\n physicalAmplify: 0,\r\n waterAmplify: 0,\r\n natureAmplify: 0,\r\n fireAmplify: 0,\r\n healingAmplify: 0,\r\n physicalThorns: 0,\r\n elementalThorns: 0,\r\n maxHitpoints: 0,\r\n maxManapoints: 0,\r\n stabEvasion: 0,\r\n slashEvasion: 0,\r\n smashEvasion: 0,\r\n rangedEvasion: 0,\r\n magicEvasion: 0,\r\n armor: 0,\r\n waterResistance: 0,\r\n natureResistance: 0,\r\n fireResistance: 0,\r\n lifeSteal: 0,\r\n hpRegenPer10: 0.01,\r\n mpRegenPer10: 0.01,\r\n combatDropRate: 0,\r\n combatDropQuantity: 0,\r\n combatRareFind: 0,\r\n combatExperience: 0,\r\n foodSlots: 1,\r\n drinkSlots: 1,\r\n armorPenetration: 0,\r\n waterPenetration: 0,\r\n naturePenetration: 0,\r\n firePenetration: 0,\r\n manaLeech: 0,\r\n castSpeed: 0,\r\n threat: 100,\r\n parry: 0,\r\n mayhem: 0,\r\n pierce: 0,\r\n curse: 0,\r\n ripple: 0,\r\n bloom: 0,\r\n blaze: 0,\r\n weaken: 0,\r\n fury: 0,\r\n foodHaste: 0,\r\n drinkConcentration: 0,\r\n damageTaken: 0,\r\n attackSpeed: 0,\r\n armorDamageRatio: 0,\r\n hpDrainRatio: 0,\r\n primaryTraining: \"\",\r\n focusTraining: \"\",\r\n staminaExperience: 0,\r\n intelligenceExperience: 0,\r\n attackExperience: 0,\r\n defenseExperience: 0,\r\n meleeExperience: 0,\r\n rangedExperience: 0,\r\n magicExperience: 0,\r\n retaliation: 0,\r\n },\r\n };\r\n combatBuffs = {};\r\n permanentBuffs = {};\r\n zoneBuffs = {};\r\n extraBuffs = {};\r\n\r\n constructor() { }\r\n\r\n updateCombatDetails() {\r\n if (this.isPlayer) {\r\n if (this.combatDetails.combatStats.hpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01 + this.combatDetails.combatStats.hpRegenPer10;\r\n }\r\n if (this.combatDetails.combatStats.mpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01 + this.combatDetails.combatStats.mpRegenPer10;\r\n }\r\n }\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((stat) => {\r\n this.combatDetails[stat + \"Level\"] = this[stat + \"Level\"];\r\n let boosts = this.getBuffBoosts(\"/buff_types/\" + stat + \"_level\");\r\n boosts.forEach((buff) => {\r\n this.combatDetails[stat + \"Level\"] += (this[stat + \"Level\"] * buff.ratioBoost);\r\n this.combatDetails[stat + \"Level\"] += buff.flatBoost;\r\n });\r\n });\r\n\r\n this.combatDetails.maxHitpoints = Math.floor\r\n (10 * (10 + this.combatDetails.staminaLevel) + this.combatDetails.combatStats.maxHitpoints);\r\n this.combatDetails.maxManapoints = Math.floor\r\n (10 * (10 + this.combatDetails.intelligenceLevel) + this.combatDetails.combatStats.maxManapoints);\r\n\r\n let accuracyRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_accuracy\").ratioBoost;\r\n let damageRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_damage\").ratioBoost;\r\n // if (accuracyRatioBoostFromFury > 0) {\r\n // console.log(\"Fury Boost: \" + accuracyRatioBoostFromFury);\r\n // }\r\n\r\n let accuracyRatioBoost = this.getBuffBoost(\"/buff_types/accuracy\").ratioBoost;\r\n let damageRatioBoost = this.getBuffBoost(\"/buff_types/damage\").ratioBoost;\r\n\r\n [\"stab\", \"slash\", \"smash\"].forEach((style) => {\r\n this.combatDetails[style + \"AccuracyRating\"] =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Accuracy\"]) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails[style + \"MaxDamage\"] =\r\n (10 + this.combatDetails.meleeLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Damage\"]) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n let baseEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats[style + \"Evasion\"]);\r\n this.combatDetails[style + \"EvasionRating\"] = baseEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails[style + \"EvasionRating\"] += boost.flatBoost;\r\n this.combatDetails[style + \"EvasionRating\"] += baseEvasion * boost.ratioBoost;\r\n }\r\n });\r\n\r\n this.combatDetails.defensiveMaxDamage = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.defensiveDamage);\r\n\r\n // when equiped bulwark\r\n if (this.equipment?.['/equipment_types/two_hand']?.hrid.includes(\"bulwark\")) {\r\n this.combatDetails.smashMaxDamage += this.combatDetails.defensiveMaxDamage;\r\n }\r\n\r\n this.combatDetails.rangedAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.rangedAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.rangedMaxDamage =\r\n (10 + this.combatDetails.rangedLevel) *\r\n (1 + this.combatDetails.combatStats.rangedDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseRangedEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.rangedEvasion);\r\n this.combatDetails.rangedEvasionRating = baseRangedEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.rangedEvasionRating += boost.flatBoost;\r\n this.combatDetails.rangedEvasionRating += baseRangedEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.damageTaken = this.getBuffBoost(\"/buff_types/damage_taken\").flatBoost;\r\n // if (this.combatDetails.combatStats.damageTaken > 0) {\r\n // console.log(\"Damage taken: \" + this.combatDetails.combatStats.damageTaken);\r\n // }\r\n\r\n this.combatDetails.magicAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.magicAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.magicMaxDamage =\r\n (10 + this.combatDetails.magicLevel) *\r\n (1 + this.combatDetails.combatStats.magicDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseMagicEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.magicEvasion);\r\n this.combatDetails.magicEvasionRating = baseMagicEvasion;\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.magicEvasionRating += boost.flatBoost;\r\n this.combatDetails.magicEvasionRating += baseMagicEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.physicalAmplify += this.getBuffBoost(\"/buff_types/physical_amplify\").flatBoost;\r\n this.combatDetails.combatStats.waterAmplify += this.getBuffBoost(\"/buff_types/water_amplify\").flatBoost;\r\n this.combatDetails.combatStats.natureAmplify += this.getBuffBoost(\"/buff_types/nature_amplify\").flatBoost;\r\n this.combatDetails.combatStats.fireAmplify += this.getBuffBoost(\"/buff_types/fire_amplify\").flatBoost;\r\n\r\n this.combatDetails.combatStats.attackInterval /= (1 + (this.combatDetails.attackLevel / 2000));\r\n\r\n let baseAttackSpeed = this.combatDetails.combatStats.attackSpeed;\r\n this.combatDetails.combatStats.attackInterval /= (1 + baseAttackSpeed);\r\n let attackIntervalBoosts = this.getBuffBoosts(\"/buff_types/attack_speed\");\r\n let attackIntervalRatioBoost = attackIntervalBoosts\r\n .map((boost) => boost.ratioBoost)\r\n .reduce((prev, cur) => prev + cur, 0);\r\n this.combatDetails.combatStats.attackInterval /= (1 + attackIntervalRatioBoost);\r\n\r\n let baseArmor = 0.2 * this.combatDetails.defenseLevel + this.combatDetails.combatStats.armor;\r\n this.combatDetails.totalArmor = baseArmor;\r\n let armorBoosts = this.getBuffBoosts(\"/buff_types/armor\");\r\n for (const boost of armorBoosts) {\r\n this.combatDetails.totalArmor += boost.flatBoost;\r\n this.combatDetails.totalArmor += baseArmor * boost.ratioBoost;\r\n }\r\n\r\n let baseWaterResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.waterResistance;\r\n this.combatDetails.totalWaterResistance = baseWaterResistance;\r\n let waterResistanceBoosts = this.getBuffBoosts(\"/buff_types/water_resistance\");\r\n for (const boost of waterResistanceBoosts) {\r\n this.combatDetails.totalWaterResistance += boost.flatBoost;\r\n this.combatDetails.totalWaterResistance += baseWaterResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseNatureResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.natureResistance;\r\n this.combatDetails.totalNatureResistance = baseNatureResistance;\r\n let natureResistanceBoosts = this.getBuffBoosts(\"/buff_types/nature_resistance\");\r\n for (const boost of natureResistanceBoosts) {\r\n this.combatDetails.totalNatureResistance += boost.flatBoost;\r\n this.combatDetails.totalNatureResistance += baseNatureResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseFireResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.fireResistance;\r\n this.combatDetails.totalFireResistance = baseFireResistance;\r\n let fireResistanceBoosts = this.getBuffBoosts(\"/buff_types/fire_resistance\");\r\n for (const boost of fireResistanceBoosts) {\r\n this.combatDetails.totalFireResistance += boost.flatBoost;\r\n this.combatDetails.totalFireResistance += baseFireResistance * boost.ratioBoost;\r\n }\r\n\r\n let hpRegenBoosts = this.getBuffBoost(\"/buff_types/hp_regen\");\r\n this.combatDetails.combatStats.hpRegenPer10 += this.combatDetails.combatStats.hpRegenPer10 * hpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.hpRegenPer10 += hpRegenBoosts.flatBoost;\r\n\r\n let mpRegenBoosts = this.getBuffBoost(\"/buff_types/mp_regen\");\r\n this.combatDetails.combatStats.mpRegenPer10 += this.combatDetails.combatStats.mpRegenPer10 * mpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.mpRegenPer10 += mpRegenBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.lifeSteal += this.getBuffBoost(\"/buff_types/life_steal\").flatBoost;\r\n this.combatDetails.combatStats.physicalThorns += this.getBuffBoost(\r\n \"/buff_types/physical_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.elementalThorns += this.getBuffBoost(\r\n \"/buff_types/elemental_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.combatExperience += this.getBuffBoost(\"/buff_types/wisdom\").flatBoost;\r\n this.combatDetails.combatStats.criticalRate += this.getBuffBoost(\"/buff_types/critical_rate\").flatBoost;\r\n this.combatDetails.combatStats.criticalDamage += this.getBuffBoost(\"/buff_types/critical_damage\").flatBoost;\r\n\r\n this.combatDetails.combatStats.castSpeed += this.getBuffBoost(\"/buff_types/cast_speed\").flatBoost;\r\n this.combatDetails.combatStats.castSpeed += this.combatDetails[\"attackLevel\"] / 2000;\r\n\r\n let combatDropRateBoosts = this.getBuffBoost(\"/buff_types/combat_drop_rate\");\r\n this.combatDetails.combatStats.combatDropRate += (1 + this.combatDetails.combatStats.combatDropRate) * combatDropRateBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropRate += combatDropRateBoosts.flatBoost;\r\n let combatRareFindBoosts = this.getBuffBoost(\"/buff_types/rare_find\");\r\n this.combatDetails.combatStats.combatRareFind += (1 + this.combatDetails.combatStats.combatRareFind) * combatRareFindBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatRareFind += combatRareFindBoosts.flatBoost;\r\n let combatDropQuantityBoosts = this.getBuffBoost(\"/buff_types/combat_drop_quantity\");\r\n this.combatDetails.combatStats.combatDropQuantity += (1 + this.combatDetails.combatStats.combatDropQuantity) * combatDropQuantityBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropQuantity += combatDropQuantityBoosts.flatBoost;\r\n\r\n let baseThreat = 100 + this.combatDetails.combatStats.threat;\r\n this.combatDetails.totalThreat = baseThreat;\r\n let threatBoosts = this.getBuffBoost(\"/buff_types/threat\");\r\n if (threatBoosts.ratioBoost !== 0) {\r\n this.combatDetails.combatStats.threat += baseThreat * threatBoosts.ratioBoost;\r\n } else {\r\n this.combatDetails.combatStats.threat = baseThreat;\r\n }\r\n this.combatDetails.combatStats.threat += threatBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.retaliation += this.getBuffBoost(\"/buff_types/retaliation\").flatBoost;\r\n }\r\n\r\n addBuff(buff, currentTime) {\r\n buff.startTime = currentTime;\r\n this.combatBuffs[buff.uniqueHrid] = buff;\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n removeBuff(buff) {\r\n if (!this.combatBuffs[buff.uniqueHrid]) {\r\n return;\r\n }\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n addPermanentBuff(buff) {\r\n if (this.permanentBuffs[buff.typeHrid]) {\r\n this.permanentBuffs[buff.typeHrid].flatBoost += buff.flatBoost;\r\n this.permanentBuffs[buff.typeHrid].ratioBoost += buff.ratioBoost;\r\n } else {\r\n this.permanentBuffs[buff.typeHrid] = buff;\r\n }\r\n }\r\n\r\n generatePermanentBuffs() {\r\n for (let i = 0; i < this.houseRooms.length; i++) {\r\n const houseRoom = this.houseRooms[i];\r\n houseRoom.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n\r\n if (this.achievements) {\r\n this.achievements.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.zoneBuffs) {\r\n this.zoneBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.extraBuffs) {\r\n this.extraBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n }\r\n\r\n removeExpiredBuffs(currentTime) {\r\n let expiredBuffs = Object.values(this.combatBuffs).filter(\r\n (buff) => buff.startTime + buff.duration <= currentTime\r\n );\r\n expiredBuffs.forEach((buff) => {\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n });\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearBuffs() {\r\n this.combatBuffs = structuredClone(this.permanentBuffs);\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearCCs() {\r\n this.isStunned = false;\r\n this.stunExpireTime = null;\r\n this.isSilenced = false;\r\n this.silenceExpireTime = null;\r\n this.isBlinded = false;\r\n this.blindExpireTime = null;\r\n this.combatDetails.combatStats.damageTaken = 0;\r\n }\r\n\r\n getBuffBoosts(type) {\r\n let boosts = [];\r\n Object.values(this.combatBuffs)\r\n .filter((buff) => buff.typeHrid == type)\r\n .forEach((buff) => {\r\n boosts.push({ ratioBoost: buff.ratioBoost, flatBoost: buff.flatBoost });\r\n });\r\n\r\n return boosts;\r\n }\r\n\r\n getBuffBoost(type) {\r\n let boosts = this.getBuffBoosts(type);\r\n\r\n let boost = {\r\n ratioBoost: 0,\r\n flatBoost: 0,\r\n };\r\n\r\n for (let i = 0; i < boosts.length; i++) {\r\n boost.ratioBoost += boosts[i]?.ratioBoost ?? 0;\r\n boost.flatBoost += boosts[i]?.flatBoost ?? 0;\r\n }\r\n\r\n return boost;\r\n }\r\n\r\n reset(currentTime = 0) {\r\n this.clearCCs();\r\n this.clearBuffs();\r\n this.updateCombatDetails();\r\n this.resetCooldowns(currentTime);\r\n\r\n this.combatDetails.currentHitpoints = this.combatDetails.maxHitpoints;\r\n this.combatDetails.currentManapoints = this.combatDetails.maxManapoints;\r\n }\r\n\r\n resetCooldowns(currentTime = 0) {\r\n this.food.filter((food) => food != null).forEach((food) => (food.lastUsed = Number.MIN_SAFE_INTEGER));\r\n this.drinks.filter((drink) => drink != null).forEach((drink) => (drink.lastUsed = Number.MIN_SAFE_INTEGER));\r\n\r\n let haste = this.combatDetails.combatStats.abilityHaste;\r\n\r\n this.abilities\r\n .filter((ability) => ability != null)\r\n .forEach((ability) => {\r\n if (this.isPlayer) {\r\n ability.lastUsed = Number.MIN_SAFE_INTEGER;\r\n } else {\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n ability.lastUsed = currentTime - Math.floor(cooldownDuration * 0.5) + Math.floor(Math.random() * cooldownDuration * 0.5);\r\n }\r\n });\r\n }\r\n\r\n addHitpoints(hitpoints) {\r\n let hitpointsAdded = 0;\r\n\r\n if (this.combatDetails.currentHitpoints >= this.combatDetails.maxHitpoints) {\r\n return hitpointsAdded;\r\n }\r\n\r\n let newHitpoints = Math.min(this.combatDetails.currentHitpoints + hitpoints, this.combatDetails.maxHitpoints);\r\n hitpointsAdded = newHitpoints - this.combatDetails.currentHitpoints;\r\n this.combatDetails.currentHitpoints = newHitpoints;\r\n\r\n return hitpointsAdded;\r\n }\r\n\r\n addManapoints(manapoints) {\r\n let manapointsAdded = 0;\r\n\r\n if (this.combatDetails.currentManapoints >= this.combatDetails.maxManapoints) {\r\n return manapointsAdded;\r\n }\r\n\r\n let newManapoints = Math.min(\r\n this.combatDetails.currentManapoints + manapoints,\r\n this.combatDetails.maxManapoints\r\n );\r\n manapointsAdded = newManapoints - this.combatDetails.currentManapoints;\r\n this.combatDetails.currentManapoints = newManapoints;\r\n\r\n return manapointsAdded;\r\n }\r\n}\r\n\r\nexport default CombatUnit;\r\n","import Buff from \"./buff\";\r\nimport itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nclass Consumable {\r\n constructor(hrid, triggers = null) {\r\n this.hrid = hrid;\r\n\r\n let gameConsumable = itemDetailMap[this.hrid];\r\n if (!gameConsumable) {\r\n throw new Error(\"No consumable found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.cooldownDuration = gameConsumable.consumableDetail.cooldownDuration;\r\n this.hitpointRestore = gameConsumable.consumableDetail.hitpointRestore;\r\n this.manapointRestore = gameConsumable.consumableDetail.manapointRestore;\r\n this.recoveryDuration = gameConsumable.consumableDetail.recoveryDuration;\r\n this.catagoryHrid = gameConsumable.categoryHrid;\r\n\r\n this.buffs = [];\r\n if (gameConsumable.consumableDetail.buffs) {\r\n for (const consumableBuff of gameConsumable.consumableDetail.buffs) {\r\n let buff = new Buff(consumableBuff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameConsumable.consumableDetail.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let consumable = new Consumable(dto.hrid, triggers);\r\n\r\n return consumable;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n let consumableHaste;\r\n if (this.catagoryHrid.includes(\"food\")) {\r\n consumableHaste = source.combatDetails.combatStats.foodHaste\r\n } else {\r\n consumableHaste = source.combatDetails.combatStats.drinkConcentration;\r\n }\r\n let cooldownDuration = this.cooldownDuration;\r\n if (consumableHaste > 0) {\r\n cooldownDuration = cooldownDuration / (1 + consumableHaste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Consumable;\r\n","import itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport enhancementLevelTotalMultiplierTable from \"./data/enhancementLevelTotalBonusMultiplierTable.json\";\r\n\r\nclass Equipment {\r\n constructor(hrid, enhancementLevel) {\r\n this.hrid = hrid;\r\n let gameItem = itemDetailMap[this.hrid];\r\n if (!gameItem) {\r\n throw new Error(\"No equipment found for hrid: \" + this.hrid);\r\n }\r\n this.gameItem = gameItem;\r\n this.enhancementLevel = enhancementLevel;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let equipment = new Equipment(dto.hrid, dto.enhancementLevel);\r\n\r\n return equipment;\r\n }\r\n\r\n getCombatStat(combatStat) {\r\n let multiplier = enhancementLevelTotalMultiplierTable[this.enhancementLevel];\r\n if(this.gameItem.equipmentDetail.combatStats[combatStat]) {\r\n let enhancementBonus = this.gameItem.equipmentDetail.combatEnhancementBonuses[combatStat] || 0;\r\n let stat = this.gameItem.equipmentDetail.combatStats[combatStat] + multiplier * enhancementBonus;\r\n return stat;\r\n }\r\n return 0;\r\n }\r\n\r\n getCombatStyle() {\r\n return this.gameItem.equipmentDetail.combatStats.combatStyleHrids[0];\r\n }\r\n\r\n getDamageType() {\r\n return this.gameItem.equipmentDetail.combatStats.damageType;\r\n }\r\n\r\n getPrimaryTraining() {\r\n return this.gameItem.equipmentDetail.combatStats.primaryTraining;\r\n }\r\n\r\n getFocusTraining(){\r\n return this.gameItem.equipmentDetail.combatStats.focusTraining;\r\n }\r\n}\r\n\r\nexport default Equipment;\r\n","import Buff from \"./buff\";\r\nimport houseRoomDetailMap from \"./data/houseRoomDetailMap.json\";\r\n\r\nclass HouseRoom {\r\n constructor(hrid, level) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameHouseRoom = houseRoomDetailMap[this.hrid];\r\n if (!gameHouseRoom) {\r\n throw new Error(\"No house room found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.buffs = [];\r\n if (gameHouseRoom.actionBuffs) {\r\n for (const actionBuff of gameHouseRoom.actionBuffs) {\r\n let buff = new Buff(actionBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n if (gameHouseRoom.globalBuffs) {\r\n for (const globalBuff of gameHouseRoom.globalBuffs) {\r\n let buff = new Buff(globalBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default HouseRoom;","import Ability from \"./ability\";\r\nimport CombatUnit from \"./combatUnit\";\r\nimport Consumable from \"./consumable\";\r\nimport Equipment from \"./equipment\";\r\nimport HouseRoom from \"./houseRoom\";\r\nimport Achievement from \"./achievement\";\r\n\r\nclass Player extends CombatUnit {\r\n equipment = {\r\n \"/equipment_types/head\": null,\r\n \"/equipment_types/body\": null,\r\n \"/equipment_types/legs\": null,\r\n \"/equipment_types/feet\": null,\r\n \"/equipment_types/hands\": null,\r\n \"/equipment_types/main_hand\": null,\r\n \"/equipment_types/two_hand\": null,\r\n \"/equipment_types/off_hand\": null,\r\n \"/equipment_types/pouch\": null,\r\n \"/equipment_types/back\": null,\r\n };\r\n\r\n constructor() {\r\n super();\r\n\r\n this.isPlayer = true;\r\n this.hrid = \"player\";\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let player = new Player();\r\n\r\n player.staminaLevel = dto.staminaLevel;\r\n player.intelligenceLevel = dto.intelligenceLevel;\r\n player.attackLevel = dto.attackLevel;\r\n player.meleeLevel = dto.meleeLevel;\r\n player.defenseLevel = dto.defenseLevel;\r\n player.rangedLevel = dto.rangedLevel;\r\n player.magicLevel = dto.magicLevel;\r\n\r\n player.hrid = dto.hrid;\r\n\r\n for (const [key, value] of Object.entries(dto.equipment)) {\r\n player.equipment[key] = value ? Equipment.createFromDTO(value) : null;\r\n }\r\n\r\n player.food = dto.food.map((food) => (food ? Consumable.createFromDTO(food) : null));\r\n player.drinks = dto.drinks.map((drink) => (drink ? Consumable.createFromDTO(drink) : null));\r\n player.abilities = dto.abilities.map((ability) => (ability ? Ability.createFromDTO(ability) : null));\r\n Object.entries(dto.houseRooms).forEach(houseRoom => {\r\n if (houseRoom[1] > 0) {\r\n player.houseRooms.push(new HouseRoom(houseRoom[0], houseRoom[1]))\r\n }\r\n });\r\n\r\n player.achievements = new Achievement(dto.achievements);\r\n\r\n player.debuffOnLevelGap = dto.debuffOnLevelGap;\r\n\r\n return player;\r\n }\r\n\r\n updateCombatDetails() {\r\n if (this.equipment[\"/equipment_types/main_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/main_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/main_hand\"].getPrimaryTraining();\r\n } else if (this.equipment[\"/equipment_types/two_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/two_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/two_hand\"].getPrimaryTraining();\r\n } else {\r\n this.combatDetails.combatStats.combatStyleHrid = \"/combat_styles/smash\";\r\n this.combatDetails.combatStats.damageType = \"/damage_types/physical\";\r\n this.combatDetails.combatStats.attackInterval = 3000000000;\r\n this.combatDetails.combatStats.primaryTraining = \"/skills/melee\";\r\n }\r\n\r\n if (this.equipment[\"/equipment_types/charm\"]) {\r\n this.combatDetails.combatStats.focusTraining = this.equipment[\"/equipment_types/charm\"].getFocusTraining();\r\n } else {\r\n this.combatDetails.combatStats.focusTraining = \"\";\r\n }\r\n\r\n [\r\n \"stabAccuracy\",\r\n \"slashAccuracy\",\r\n \"smashAccuracy\",\r\n \"rangedAccuracy\",\r\n \"magicAccuracy\",\r\n \"stabDamage\",\r\n \"slashDamage\",\r\n \"smashDamage\",\r\n \"rangedDamage\",\r\n \"magicDamage\",\r\n \"defensiveDamage\",\r\n \"taskDamage\",\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"stabEvasion\",\r\n \"slashEvasion\",\r\n \"smashEvasion\",\r\n \"rangedEvasion\",\r\n \"magicEvasion\",\r\n \"armor\",\r\n \"waterResistance\",\r\n \"natureResistance\",\r\n \"fireResistance\",\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"combatDropRate\",\r\n \"combatRareFind\",\r\n \"combatDropQuantity\",\r\n \"combatExperience\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"abilityHaste\",\r\n \"tenacity\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"threat\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"foodHaste\",\r\n \"drinkConcentration\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\",\r\n \"retaliation\"\r\n ].forEach((stat) => {\r\n this.combatDetails.combatStats[stat] = Object.values(this.equipment)\r\n .filter((equipment) => equipment != null)\r\n .map((equipment) => equipment.getCombatStat(stat))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n });\r\n\r\n if (this.equipment[\"/equipment_types/pouch\"]) {\r\n this.combatDetails.combatStats.foodSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"foodSlots\");\r\n this.combatDetails.combatStats.drinkSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"drinkSlots\");\r\n } else {\r\n this.combatDetails.combatStats.foodSlots = 1;\r\n this.combatDetails.combatStats.drinkSlots = 1;\r\n }\r\n\r\n super.updateCombatDetails();\r\n }\r\n}\r\n\r\nexport default Player;\r\n","import combatTriggerDependencyDetailMap from \"./data/combatTriggerDependencyDetailMap.json\";\r\n\r\nclass Trigger {\r\n constructor(dependencyHrid, conditionHrid, comparatorHrid, value = 0) {\r\n this.dependencyHrid = dependencyHrid;\r\n this.conditionHrid = conditionHrid;\r\n this.comparatorHrid = comparatorHrid;\r\n this.value = value;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let trigger = new Trigger(dto.dependencyHrid, dto.conditionHrid, dto.comparatorHrid, dto.value);\r\n\r\n return trigger;\r\n }\r\n\r\n isActive(source, target, friendlies, enemies, currentTime) {\r\n if (combatTriggerDependencyDetailMap[this.dependencyHrid].isSingleTarget) {\r\n return this.isActiveSingleTarget(source, target, currentTime);\r\n } else {\r\n return this.isActiveMultiTarget(friendlies, enemies, currentTime);\r\n }\r\n }\r\n\r\n isActiveSingleTarget(source, target, currentTime) {\r\n let dependencyValue;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/self\":\r\n dependencyValue = this.getDependencyValue(source, currentTime);\r\n break;\r\n case \"/combat_trigger_dependencies/targeted_enemy\":\r\n if (!target) {\r\n return false;\r\n }\r\n dependencyValue = this.getDependencyValue(target, currentTime);\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n isActiveMultiTarget(friendlies, enemies, currentTime) {\r\n let dependency;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/all_allies\":\r\n dependency = friendlies;\r\n break;\r\n case \"/combat_trigger_dependencies/all_enemies\":\r\n if (!enemies) {\r\n return false;\r\n }\r\n dependency = enemies;\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n let dependencyValue;\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/number_of_active_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints > 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/number_of_dead_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints <= 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/lowest_hp_percentage\":\r\n dependencyValue = dependency.reduce((prev, curr) => {\r\n let currentHpPercentage = curr.combatDetails.currentHitpoints / curr.combatDetails.maxHitpoints;\r\n return currentHpPercentage < prev ? currentHpPercentage : prev;\r\n }, 2) * 100;\r\n break;\r\n default:\r\n dependencyValue = dependency\r\n .map((unit) => this.getDependencyValue(unit, currentTime))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n break;\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n getDependencyValue(source, currentTime) {\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/berserk\":\r\n case \"/combat_trigger_conditions/frenzy\":\r\n case \"/combat_trigger_conditions/precision\":\r\n case \"/combat_trigger_conditions/vampirism\":\r\n case \"/combat_trigger_conditions/attack_coffee\":\r\n case \"/combat_trigger_conditions/defense_coffee\":\r\n case \"/combat_trigger_conditions/lucky_coffee\":\r\n case \"/combat_trigger_conditions/magic_coffee\":\r\n case \"/combat_trigger_conditions/melee_coffee\":\r\n case \"/combat_trigger_conditions/ranged_coffee\":\r\n case \"/combat_trigger_conditions/swiftness_coffee\":\r\n case \"/combat_trigger_conditions/wisdom_coffee\":\r\n case \"/combat_trigger_conditions/ice_spear\":\r\n case \"/combat_trigger_conditions/puncture\":\r\n case \"/combat_trigger_conditions/frost_surge\":\r\n case \"/combat_trigger_conditions/elusiveness\":\r\n case \"/combat_trigger_conditions/channeling_coffee\":\r\n case \"/combat_trigger_conditions/fierce_aura\":\r\n case \"/combat_trigger_conditions/invincible_armor\":\r\n case \"/combat_trigger_conditions/invincible_fire_resistance\":\r\n case \"/combat_trigger_conditions/invincible_nature_resistance\":\r\n case \"/combat_trigger_conditions/invincible_water_resistance\":\r\n case \"/combat_trigger_conditions/provoke\":\r\n case \"/combat_trigger_conditions/taunt\":\r\n case \"/combat_trigger_conditions/crippling_slash\":\r\n case \"/combat_trigger_conditions/mana_spring\":\r\n case \"/combat_trigger_conditions/retribution\":\r\n case \"/combat_trigger_conditions/fracturing_impact\":\r\n case \"/combat_trigger_conditions/maim\":\r\n case \"/combat_trigger_conditions/curse\":\r\n case \"/combat_trigger_conditions/weaken\":\r\n let buffHrid = \"/buff_uniques\";\r\n buffHrid += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n return source.combatBuffs[buffHrid];\r\n case \"/combat_trigger_conditions/critical_aura\":\r\n case \"/combat_trigger_conditions/critical_coffee\":\r\n case \"/combat_trigger_conditions/intelligence_coffee\":\r\n case \"/combat_trigger_conditions/stamina_coffee\":\r\n case \"/combat_trigger_conditions/elemental_affinity\":\r\n case \"/combat_trigger_conditions/fury\":\r\n case \"/combat_trigger_conditions/guardian_aura\":\r\n case \"/combat_trigger_conditions/insanity\":\r\n case \"/combat_trigger_conditions/spike_shell\":\r\n case \"/combat_trigger_conditions/toxic_pollen\":\r\n case \"/combat_trigger_conditions/invincible\":\r\n case \"/combat_trigger_conditions/mystic_aura\":\r\n case \"/combat_trigger_conditions/pestilent_shot\":\r\n case \"/combat_trigger_conditions/smoke_burst\":\r\n case \"/combat_trigger_conditions/speed_aura\":\r\n case \"/combat_trigger_conditions/toughness\":\r\n case \"/combat_trigger_conditions/enrage\":\r\n let buffPrefix = \"/buff_uniques\";\r\n buffPrefix += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n let buffs = Object.keys(source.combatBuffs).filter(buff => buff.startsWith(buffPrefix));\r\n return source.combatBuffs[buffs?.[0]];\r\n case \"/combat_trigger_conditions/current_hp\":\r\n return source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/current_mp\":\r\n return source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/missing_hp\":\r\n return source.combatDetails.maxHitpoints - source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/missing_mp\":\r\n return source.combatDetails.maxManapoints - source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/stun_status\":\r\n // Replicate the game's behaviour of \"stun status active\" triggers activating\r\n // immediately after the stun has worn off\r\n return source.isStunned || source.stunExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/blind_status\":\r\n return source.isBlinded || source.blindExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/silence_status\":\r\n return source.isSilenced || source.silenceExpireTime == currentTime;\r\n default:\r\n throw new Error(\"Unknown conditionHrid in trigger: \" + this.conditionHrid);\r\n }\r\n }\r\n\r\n compareValue(dependencyValue) {\r\n switch (this.comparatorHrid) {\r\n case \"/combat_trigger_comparators/greater_than_equal\":\r\n return dependencyValue >= this.value;\r\n case \"/combat_trigger_comparators/less_than_equal\":\r\n return dependencyValue <= this.value;\r\n case \"/combat_trigger_comparators/is_active\":\r\n return !!dependencyValue;\r\n case \"/combat_trigger_comparators/is_inactive\":\r\n return !dependencyValue;\r\n default:\r\n throw new Error(\"Unknown comparatorHrid in trigger: \" + this.comparatorHrid);\r\n }\r\n }\r\n}\r\n\r\nexport default Trigger;\r\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","// This function allow to reference async chunks\n__webpack_require__.u = (chunkId) => {\n\t// return url for filenames based on template\n\treturn \"\" + chunkId + \".bundle.js\";\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","var scriptUrl;\nif (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + \"\";\nvar document = __webpack_require__.g.document;\nif (!scriptUrl && document) {\n\tif (document.currentScript)\n\t\tscriptUrl = document.currentScript.src;\n\tif (!scriptUrl) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tif(scripts.length) {\n\t\t\tvar i = scripts.length - 1;\n\t\t\twhile (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;\n\t\t}\n\t}\n}\n// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration\n// or pass an empty string (\"\") and set the __webpack_public_path__ variable from your code to use your own logic.\nif (!scriptUrl) throw new Error(\"Automatic publicPath is not supported in this browser\");\nscriptUrl = scriptUrl.replace(/#.*$/, \"\").replace(/\\?.*$/, \"\").replace(/\\/[^\\/]+$/, \"/\");\n__webpack_require__.p = scriptUrl;","__webpack_require__.b = document.baseURI || self.location.href;\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t\"main\": 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n// no on chunks loaded\n\n// no jsonp function","import Equipment from \"./combatsimulator/equipment.js\";\r\nimport Player from \"./combatsimulator/player.js\";\r\nimport abilityDetailMap from \"./combatsimulator/data/abilityDetailMap.json\";\r\nimport itemDetailMap from \"./combatsimulator/data/itemDetailMap.json\";\r\nimport houseRoomDetailMap from \"./combatsimulator/data/houseRoomDetailMap.json\";\r\nimport Ability from \"./combatsimulator/ability.js\";\r\nimport Consumable from \"./combatsimulator/consumable.js\";\r\nimport HouseRoom from \"./combatsimulator/houseRoom\"\r\nimport combatTriggerDependencyDetailMap from \"./combatsimulator/data/combatTriggerDependencyDetailMap.json\";\r\nimport combatTriggerConditionDetailMap from \"./combatsimulator/data/combatTriggerConditionDetailMap.json\";\r\nimport combatTriggerComparatorDetailMap from \"./combatsimulator/data/combatTriggerComparatorDetailMap.json\";\r\nimport abilitySlotsLevelRequirementList from \"./combatsimulator/data/abilitySlotsLevelRequirementList.json\";\r\nimport actionDetailMap from \"./combatsimulator/data/actionDetailMap.json\";\r\nimport combatMonsterDetailMap from \"./combatsimulator/data/combatMonsterDetailMap.json\";\r\nimport damageTypeDetailMap from \"./combatsimulator/data/damageTypeDetailMap.json\";\r\nimport combatStyleDetailMap from \"./combatsimulator/data/combatStyleDetailMap.json\";\r\nimport openableLootDropMap from \"./combatsimulator/data/openableLootDropMap.json\";\r\nimport achievementTierMap from \"./combatsimulator/data/achievementTierDetailMap.json\"\r\nimport achievementDetailMap from \"./combatsimulator/data/achievementDetailMap.json\"\r\n\r\nimport patchNote from \"../patchNote.json\";\r\n\r\nconst ONE_SECOND = 1e9;\r\nconst ONE_HOUR = 60 * 60 * ONE_SECOND;\r\n\r\nlet buttonStartSimulation = document.getElementById(\"buttonStartSimulation\");\r\nlet buttonStopSimulation = document.getElementById(\"buttonStopSimulation\");\r\nlet progressbar = document.getElementById(\"simulationProgressBar\");\r\nlet simStartTime = 0;\r\n\r\nlet worker = new Worker(new URL(\"worker.js\", import.meta.url));\r\nlet multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n\r\n\r\n\r\nlet player = new Player();\r\nlet selectedPlayers = [];\r\nlet food = [null, null, null];\r\nlet drinks = [null, null, null];\r\nlet abilities = [null, null, null, null];\r\nlet triggerMap = {};\r\nlet modalTriggers = [];\r\nlet currentSimResults = {};\r\n\r\nlet currentPlayerTabId = '1';\r\nlet playerDataMap = {\r\n \"1\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"2\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"3\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"4\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"5\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\"\r\n};\r\nwindow.revenue = 0;\r\nwindow.noRngRevenue = 0;\r\nwindow.expenses = 0;\r\nwindow.profit = 0;\r\nwindow.noRngProfit = 0;\r\n\r\n// #region Worker\r\n\r\nfunction onWorkerMessage(event) {\r\n switch (event.data.type) {\r\n case \"simulation_result\":\r\n progressbar.style.width = \"100%\";\r\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n //console.log(\"SIM RESULTS: \", event.data.simResult);\r\n showSimulationResult(event.data.simResult);\r\n updateContent();\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n document.getElementById('buttonShowAllSimData').style.display = 'none';\r\n break;\r\n case \"simulation_progress\":\r\n let progress = Math.floor(100 * event.data.progress);\r\n progressbar.style.width = progress + \"%\";\r\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n break;\r\n case \"simulation_error\":\r\n showErrorModal(event.data.error.toString());\r\n break;\r\n }\r\n};\r\n\r\nfunction onMultiWorkerMessage(event) {\r\n switch (event.data.type) {\r\n case \"simulation_result_allZones\":\r\n progressbar.style.width = \"100%\";\r\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n showAllSimulationResults(event.data.simResults);\r\n updateContent();\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n document.getElementById('buttonShowAllSimData').style.display = 'block';\r\n break;\r\n case \"simulation_progress\":\r\n let progress = Math.floor(100 * event.data.progress);\r\n progressbar.style.width = progress + \"%\";\r\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n break;\r\n case \"simulation_error\":\r\n showErrorModal(event.data.error.toString());\r\n break;\r\n }\r\n};\r\n\r\n// #endregion\r\n\r\n// #region Equipment\r\n\r\nfunction initEquipmentSection() {\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n initEquipmentSelect(type);\r\n initEnhancementLevelInput(type);\r\n });\r\n}\r\n\r\nfunction initEquipmentSelect(equipmentType) {\r\n let selectId = \"selectEquipment_\";\r\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\r\n selectId += \"weapon\";\r\n } else {\r\n selectId += equipmentType;\r\n }\r\n let selectElement = document.getElementById(selectId);\r\n\r\n let gameEquipment = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/equipment\")\r\n .filter((item) => item.equipmentDetail.type == \"/equipment_types/\" + equipmentType)\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const equipment of Object.values(gameEquipment)) {\r\n let opt = new Option(equipment.name, equipment.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + equipment.hrid);\r\n selectElement.add(opt);\r\n }\r\n\r\n selectElement.addEventListener(\"change\", (event) => {\r\n equipmentSelectHandler(event, equipmentType);\r\n });\r\n}\r\n\r\nfunction initHouseRoomsModal() {\r\n let houseRoomsList = document.getElementById(\"houseRoomsList\");\r\n let newChildren = [];\r\n let houseRooms = Object.values(houseRoomDetailMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n player.houseRooms = {};\r\n\r\n for (const room of Object.values(houseRooms)) {\r\n player.houseRooms[room.hrid] = 0;\r\n\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let nameCol = createElement(\"div\", \"col-md-4 offset-md-3 align-self-center\", room.name);\r\n nameCol.setAttribute(\"data-i18n\", \"houseRoomNames.\" + room.hrid);\r\n row.appendChild(nameCol);\r\n\r\n let levelCol = createElement(\"div\", \"col-md-2\");\r\n let levelInput = createHouseInput(room.hrid);\r\n\r\n levelInput.addEventListener(\"input\", function (e) {\r\n let inputValue = e.target.value;\r\n const hrid = e.target.dataset.houseHrid;\r\n player.houseRooms[hrid] = parseInt(inputValue);\r\n });\r\n\r\n levelCol.appendChild(levelInput);\r\n row.appendChild(levelCol);\r\n\r\n newChildren.push(row);\r\n }\r\n\r\n houseRoomsList.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction createHouseInput(hrid) {\r\n let levelInput = document.createElement(\"input\");\r\n levelInput.className = \"form-control\";\r\n levelInput.type = \"number\";\r\n levelInput.placeholder = 0;\r\n levelInput.min = 0;\r\n levelInput.max = 8;\r\n levelInput.step = 1;\r\n levelInput.dataset.houseHrid = hrid;\r\n\r\n return levelInput;\r\n}\r\n\r\nfunction refreshAchievementStatics() {\r\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n for(const tier of Object.values(tierMap)) {\r\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\r\n const done = Array.from(checks).filter(cb => cb.checked).length;\r\n const total = checks.length;\r\n\r\n const stat = document.getElementById(`AchTier${tier.sortIndex}Statics`);\r\n stat.innerText = `(${done}/${total})`;\r\n if (done == total) {\r\n // set to green\r\n stat.classList.remove(\"text-secondary\");\r\n stat.classList.add(\"text-success\");\r\n } else {\r\n // set to secondary\r\n stat.classList.remove(\"text-success\");\r\n stat.classList.add(\"text-secondary\");\r\n }\r\n }\r\n}\r\n\r\nfunction initAchievementsModal(){\r\n let achievementsList = document.getElementById(\"achievementsList\");\r\n let newChildren = [];\r\n player.achievements = {};\r\n\r\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n for(const tier of Object.values(tierMap)) {\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid).sort((a, b) => a.sortIndex - b.sortIndex);\r\n let detailMapCount = detailMap.length;\r\n if (detailMapCount <= 0) continue;\r\n\r\n let card = createElement(\"div\", \"card\");\r\n let cardHeader = createElement(\"div\", \"card-header d-flex align-items-center\");\r\n\r\n let cardTitle = createElement(\"a\", \"btn\", tier.name);\r\n cardTitle.setAttribute(\"data-bs-toggle\",\"collapse\");\r\n cardTitle.setAttribute(\"href\", `#AchTier${tier.sortIndex}`);\r\n cardTitle.setAttribute(\"data-i18n\", \"achievementTierNames.\"+tier.hrid);\r\n cardHeader.appendChild(cardTitle);\r\n\r\n let bufDesc = createElement(\"div\", \"small text-secondary\");\r\n let buffName = createElement(\"i\", \"\");\r\n buffName.setAttribute(\"data-i18n\", \"buffTypeNames.\"+tier[\"buff\"].typeHrid);\r\n bufDesc.appendChild(buffName);\r\n let buffValue = createElement(\"i\", \"\");\r\n buffValue.innerText = \":+\" + parseFloat(tier[\"buff\"].ratioBoost==0?tier[\"buff\"].flatBoost:tier[\"buff\"].ratioBoost)*100 + \"%\";\r\n bufDesc.appendChild(buffValue);\r\n cardHeader.appendChild(bufDesc);\r\n\r\n let cardStatics = createElement(\"div\", \"ms-auto btn\", `(0/${detailMapCount})`);\r\n cardStatics.id = `AchTier${tier.sortIndex}Statics`;\r\n cardStatics.dataset.checked = \"true\";\r\n cardStatics.addEventListener(\"click\", function (e) {\r\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\r\n for (const check of checks) {\r\n check.checked = cardStatics.dataset.checked == \"true\";\r\n const hrid = check.dataset.achievementHrid;\r\n player.achievements[hrid] = check.checked;\r\n }\r\n cardStatics.dataset.checked = cardStatics.dataset.checked == \"true\" ? \"false\" : \"true\";\r\n refreshAchievementStatics();\r\n });\r\n cardHeader.appendChild(cardStatics);\r\n\r\n card.appendChild(cardHeader);\r\n\r\n let cardMain = createElement(\"div\", \"collapse\");\r\n cardMain.id = `AchTier${tier.sortIndex}`;\r\n let cardBody = createElement(\"div\", \"card-body\");\r\n\r\n for (const detail of Object.values(detailMap)) {\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let formCheck = createElement(\"div\", \"form-check\");\r\n let input = createElement(\"input\", \"form-check-input\");\r\n input.setAttribute(\"type\", \"checkbox\");\r\n input.setAttribute(\"data-tier\", tier.sortIndex);\r\n input.id = `AchDetail${detail.sortIndex}`;\r\n input.dataset.achievementHrid = detail.hrid;\r\n input.addEventListener(\"change\", function (e) {\r\n const hrid = e.target.dataset.achievementHrid;\r\n player.achievements[hrid] = e.target.checked;\r\n\r\n refreshAchievementStatics();\r\n });\r\n formCheck.appendChild(input);\r\n\r\n let name = createElement(\"label\", \"form-check-label\", detail.name);\r\n name.setAttribute(\"data-i18n\", \"achievementNames.\" + detail.hrid);\r\n name.setAttribute(\"for\", `AchDetail${detail.sortIndex}`);\r\n formCheck.appendChild(name);\r\n row.appendChild(formCheck);\r\n cardBody.appendChild(row);\r\n }\r\n cardMain.appendChild(cardBody);\r\n card.appendChild(cardMain);\r\n\r\n newChildren.push(card);\r\n }\r\n\r\n achievementsList.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction initEnhancementLevelInput(equipmentType) {\r\n let inputId = \"inputEquipmentEnhancementLevel_\";\r\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\r\n inputId += \"weapon\";\r\n } else {\r\n inputId += equipmentType;\r\n }\r\n\r\n let inputElement = document.getElementById(inputId);\r\n inputElement.value = 0;\r\n inputElement.addEventListener(\"change\", enhancementLevelInputHandler);\r\n}\r\n\r\nfunction equipmentSelectHandler(event, type) {\r\n let equipmentType = \"/equipment_types/\" + type;\r\n\r\n if (!event.target.value) {\r\n updateEquipmentState();\r\n updateUI();\r\n return;\r\n }\r\n\r\n let gameItem = itemDetailMap[event.target.value];\r\n\r\n // Weapon select has two handlers because of mainhand and twohand weapons. Ignore the handler with the wrong type\r\n if (gameItem.equipmentDetail.type != equipmentType) {\r\n return;\r\n }\r\n\r\n if (type == \"two_hand\") {\r\n document.getElementById(\"selectEquipment_off_hand\").value = \"\";\r\n document.getElementById(\"inputEquipmentEnhancementLevel_off_hand\").value = 0;\r\n }\r\n if (type == \"off_hand\" && player.equipment[\"/equipment_types/two_hand\"]) {\r\n document.getElementById(\"selectEquipment_weapon\").value = \"\";\r\n document.getElementById(\"inputEquipmentEnhancementLevel_weapon\").value = 0;\r\n }\r\n\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\nfunction enhancementLevelInputHandler() {\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\nfunction updateEquipmentState() {\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentType = \"/equipment_types/\" + type;\r\n let selectType = type;\r\n if (type == \"main_hand\" || type == \"two_hand\") {\r\n selectType = \"weapon\";\r\n }\r\n\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + selectType);\r\n let equipmentHrid = equipmentSelect.value;\r\n\r\n if (!equipmentHrid) {\r\n player.equipment[equipmentType] = null;\r\n return;\r\n }\r\n\r\n let gameItem = itemDetailMap[equipmentHrid];\r\n\r\n // Clear old weapon if a weapon of a different type is equipped\r\n if (gameItem.equipmentDetail.type != equipmentType) {\r\n player.equipment[equipmentType] = null;\r\n return;\r\n }\r\n\r\n let enhancementLevel = Number(document.getElementById(\"inputEquipmentEnhancementLevel_\" + selectType).value);\r\n player.equipment[equipmentType] = new Equipment(gameItem.hrid, enhancementLevel);\r\n });\r\n}\r\n\r\ndocument.getElementById(\"selectEquipment_set\").onchange = changeEquipmentSetListener;\r\n\r\nfunction changeEquipmentSetListener() {\r\n let value = this.value\r\n let optgroupType = this.options[this.selectedIndex].parentNode.label;\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\"].forEach((type) => {\r\n let selectType = type;\r\n\r\n let currentEquipment = document.getElementById(\"selectEquipment_\" + selectType);\r\n if (type === \"feet\") {\r\n type = \"_boots\";\r\n }\r\n if (type === \"hands\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_bracers\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_gloves\";\r\n } else {\r\n type = \"_gauntlets\";\r\n }\r\n }\r\n if (type === \"head\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_hood\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_hat\";\r\n } else {\r\n type = \"_helmet\";\r\n }\r\n }\r\n if (type === \"legs\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_chaps\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_robe_bottoms\";\r\n } else {\r\n type = \"_plate_legs\";\r\n }\r\n }\r\n if (type === \"body\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_tunic\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_robe_top\";\r\n } else {\r\n type = \"_plate_body\";\r\n }\r\n }\r\n currentEquipment.value = \"/items/\" + value.toLowerCase() + type;\r\n });\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Combat Stats\r\n\r\nfunction updateCombatStatsUI() {\r\n player.updateCombatDetails();\r\n\r\n let combatStyleElement = document.getElementById(\"combatStat_combatStyleHrid\");\r\n let combatStyle = player.combatDetails.combatStats.combatStyleHrid;\r\n combatStyleElement.setAttribute(\"data-i18n\", \"combatStyleNames.\" + combatStyle);\r\n combatStyleElement.innerHTML = combatStyleDetailMap[combatStyle].name;\r\n\r\n let damageTypeElement = document.getElementById(\"combatStat_damageType\");\r\n let damageType = damageTypeDetailMap[player.combatDetails.combatStats.damageType];\r\n damageTypeElement.setAttribute(\"data-i18n\", \"damageTypeNames.\" + damageType.hrid);\r\n damageTypeElement.innerHTML = damageType.name;\r\n\r\n let attackIntervalElement = document.getElementById(\"combatStat_attackInterval\");\r\n attackIntervalElement.innerHTML = (player.combatDetails.combatStats.attackInterval / 1e9).toLocaleString() + \"s\";\r\n\r\n let primaryTrainingElement = document.getElementById(\"combatStat_primaryTraining\");\r\n let primaryTraining = player.combatDetails.combatStats.primaryTraining;\r\n primaryTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + primaryTraining);\r\n primaryTrainingElement.innerHTML = primaryTraining;\r\n\r\n let focusTrainingElement = document.getElementById(\"combatStat_focusTraining\");\r\n let focusTraining = player.combatDetails.combatStats.focusTraining;\r\n if (focusTraining) {\r\n focusTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + focusTraining);\r\n } else {\r\n focusTrainingElement.setAttribute(\"data-i18n\", \"characterSelectPage.slots.empty\");\r\n }\r\n focusTrainingElement.innerHTML = focusTraining;\r\n\r\n [\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"stabAccuracyRating\",\r\n \"stabMaxDamage\",\r\n \"slashAccuracyRating\",\r\n \"slashMaxDamage\",\r\n \"smashAccuracyRating\",\r\n \"smashMaxDamage\",\r\n \"rangedAccuracyRating\",\r\n \"rangedMaxDamage\",\r\n \"magicAccuracyRating\",\r\n \"magicMaxDamage\",\r\n \"defensiveMaxDamage\",\r\n \"stabEvasionRating\",\r\n \"slashEvasionRating\",\r\n \"smashEvasionRating\",\r\n \"rangedEvasionRating\",\r\n \"magicEvasionRating\",\r\n \"totalArmor\",\r\n \"totalWaterResistance\",\r\n \"totalNatureResistance\",\r\n \"totalFireResistance\",\r\n \"totalThreat\"\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n element.innerHTML = Math.floor(player.combatDetails[stat]);\r\n });\r\n\r\n [\r\n \"abilityHaste\",\r\n \"tenacity\"\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n element.innerHTML = Math.floor(player.combatDetails.combatStats[stat]);\r\n });\r\n\r\n [\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"combatExperience\",\r\n \"taskDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"drinkConcentration\",\r\n \"foodHaste\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\"\r\n\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n let value = (100 * player.combatDetails.combatStats[stat]).toLocaleString([], {\r\n minimumFractionDigits: 0,\r\n maximumFractionDigits: 4,\r\n });\r\n element.innerHTML = value + \"%\";\r\n });\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Level\r\n\r\nfunction initLevelSection() {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n levelInput.value = 1;\r\n levelInput.addEventListener(\"change\", levelInputHandler);\r\n });\r\n}\r\n\r\nfunction levelInputHandler() {\r\n updateLevels();\r\n updateUI();\r\n}\r\n\r\nfunction updateLevels() {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n player[skill + \"Level\"] = Number(levelInput.value);\r\n });\r\n updateCombatLevel();\r\n}\r\n\r\nfunction calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel) {\r\n return Math.floor(\r\n 0.1 * (staminaLevel + intelligenceLevel + attackLevel + defenseLevel + Math.max(meleeLevel, rangedLevel, magicLevel))\r\n + 0.5 * Math.max(attackLevel, defenseLevel, meleeLevel, rangedLevel, magicLevel)\r\n );\r\n}\r\n\r\n\r\nfunction updateCombatLevel() {\r\n let staminaLevel = player[\"staminaLevel\"];\r\n let intelligenceLevel = player[\"intelligenceLevel\"];\r\n let defenseLevel = player[\"defenseLevel\"];\r\n let attackLevel = player[\"attackLevel\"];\r\n let meleeLevel = player[\"meleeLevel\"];\r\n let rangedLevel = player[\"rangedLevel\"];\r\n let magicLevel = player[\"magicLevel\"];\r\n\r\n let levelInput = document.getElementById(\"inputLevel_combat\");\r\n levelInput.value = calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel);;\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Food\r\n\r\nfunction initFoodSection() {\r\n for (let i = 0; i < 3; i++) {\r\n let element = document.getElementById(\"selectFood_\" + i);\r\n\r\n let gameFoods = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/food\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const food of Object.values(gameFoods)) {\r\n let opt = new Option(food.name, food.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + food.hrid);\r\n element.add(opt);\r\n }\r\n\r\n element.addEventListener(\"change\", foodSelectHandler);\r\n }\r\n}\r\n\r\nfunction foodSelectHandler() {\r\n updateFoodState();\r\n updateUI();\r\n}\r\n\r\nfunction updateFoodState() {\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n food[i] = foodSelect.value;\r\n if (food[i] && !triggerMap[food[i]]) {\r\n let gameItem = itemDetailMap[food[i]];\r\n triggerMap[food[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateFoodUI() {\r\n for (let i = 0; i < 3; i++) {\r\n let selectElement = document.getElementById(\"selectFood_\" + i);\r\n let triggerButton = document.getElementById(\"buttonFoodTrigger_\" + i);\r\n\r\n selectElement.disabled = i >= player.combatDetails.combatStats.foodSlots;\r\n triggerButton.disabled = i >= player.combatDetails.combatStats.foodSlots || !food[i];\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Drinks\r\n\r\nfunction initDrinksSection() {\r\n for (let i = 0; i < 3; i++) {\r\n let element = document.getElementById(\"selectDrink_\" + i);\r\n\r\n let gameDrinks = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/drink\")\r\n .filter((item) => item.consumableDetail.usableInActionTypeMap[\"/action_types/combat\"])\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const drink of Object.values(gameDrinks)) {\r\n let opt = new Option(drink.name, drink.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + drink.hrid);\r\n element.add(opt);\r\n }\r\n\r\n element.addEventListener(\"change\", drinkSelectHandler);\r\n }\r\n}\r\n\r\nfunction drinkSelectHandler() {\r\n updateDrinksState();\r\n updateDrinksUI();\r\n}\r\n\r\nfunction updateDrinksState() {\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n drinks[i] = drinkSelect.value;\r\n if (drinks[i] && !triggerMap[drinks[i]]) {\r\n let gameItem = itemDetailMap[drinks[i]];\r\n triggerMap[drinks[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateDrinksUI() {\r\n for (let i = 0; i < 3; i++) {\r\n let selectElement = document.getElementById(\"selectDrink_\" + i);\r\n let triggerButton = document.getElementById(\"buttonDrinkTrigger_\" + i);\r\n\r\n selectElement.disabled = i >= player.combatDetails.combatStats.drinkSlots;\r\n triggerButton.disabled = i >= player.combatDetails.combatStats.drinkSlots || !drinks[i];\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Abilities\r\n\r\nfunction initAbilitiesSection() {\r\n for (let i = 0; i < 5; i++) {\r\n let selectElement = document.getElementById(\"selectAbility_\" + i);\r\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\r\n\r\n inputElement.value = 1;\r\n\r\n let gameAbilities;\r\n if (i == 0) {\r\n gameAbilities = Object.values(abilityDetailMap).filter(x => x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\r\n } else {\r\n gameAbilities = Object.values(abilityDetailMap).filter(x => !x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\r\n }\r\n\r\n\r\n for (const ability of Object.values(gameAbilities)) {\r\n let opt = new Option(ability.name, ability.hrid);\r\n opt.setAttribute(\"data-i18n\", \"abilityNames.\" + ability.hrid);\r\n selectElement.add(opt);\r\n }\r\n\r\n selectElement.addEventListener(\"change\", abilitySelectHandler);\r\n }\r\n}\r\n\r\nfunction abilitySelectHandler() {\r\n updateAbilityState();\r\n updateAbilityUI();\r\n}\r\n\r\nfunction updateAbilityState() {\r\n for (let i = 0; i < 5; i++) {\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\r\n abilities[i] = abilitySelect.value;\r\n if (abilities[i] && !triggerMap[abilities[i]]) {\r\n let gameAbility = abilityDetailMap[abilities[i]];\r\n triggerMap[abilities[i]] = structuredClone(gameAbility.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateAbilityUI() {\r\n for (let i = 0; i < 5; i++) {\r\n let selectElement = document.getElementById(\"selectAbility_\" + i);\r\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let triggerButton = document.getElementById(\"buttonAbilityTrigger_\" + i);\r\n\r\n selectElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\r\n inputElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\r\n triggerButton.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1] || !abilities[i];\r\n let moveUpButton = document.getElementById(\"selectAbilityMoveUp_\" + i);\r\n moveUpButton.onclick = () => swapAbilityOrder(i, -1);\r\n }\r\n}\r\n\r\nfunction swapAbilityOrder(abilityIndex, step) {\r\n const swapIndex = abilityIndex + step;\r\n if (swapIndex < 0 || swapIndex > 4) {\r\n return;\r\n }\r\n\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilityIndex);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilityIndex);\r\n\r\n const tempAbility = abilities[abilityIndex];\r\n abilities[abilityIndex] = abilities[swapIndex];\r\n abilities[swapIndex] = tempAbility;\r\n\r\n const tempLevel = abilityLevelInput.value;\r\n abilityLevelInput.value = document.getElementById(\"inputAbilityLevel_\" + swapIndex).value;\r\n document.getElementById(\"inputAbilityLevel_\" + swapIndex).value = tempLevel;\r\n\r\n abilitySelect.value = document.getElementById(\"selectAbility_\" + (swapIndex)).value;\r\n document.getElementById(\"selectAbility_\" + swapIndex).value = abilities[swapIndex];\r\n\r\n updateAbilityState();\r\n updateAbilityUI();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Trigger\r\n\r\nfunction initTriggerModal() {\r\n let modal = document.getElementById(\"triggerModal\");\r\n modal.addEventListener(\"show.bs.modal\", (event) => triggerModalShownHandler(event));\r\n\r\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\r\n triggerSaveButton.addEventListener(\"click\", (event) => triggerModalSaveHandler(event));\r\n\r\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\r\n triggerAddButton.addEventListener(\"click\", (event) => triggerAddButtonHandler(event));\r\n\r\n let triggerDefaultButton = document.getElementById(\"buttonDefaultTrigger\");\r\n triggerDefaultButton.addEventListener(\"click\", (event) => triggerDefaultButtonHandler(event));\r\n\r\n for (let i = 0; i < 4; i++) {\r\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\r\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\r\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\r\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\r\n let triggerRemoveButton = document.getElementById(\"buttonRemoveTrigger_\" + i);\r\n\r\n triggerDependencySelect.addEventListener(\"change\", (event) => triggerDependencySelectHandler(event, i));\r\n triggerConditionSelect.addEventListener(\"change\", (event) => triggerConditionSelectHandler(event, i));\r\n triggerComparatorSelect.addEventListener(\"change\", (event) => triggerComparatorSelectHander(event, i));\r\n triggerValueInput.addEventListener(\"change\", (event) => triggerValueInputHandler(event, i));\r\n triggerRemoveButton.addEventListener(\"click\", (event) => triggerRemoveButtonHandler(event, i));\r\n }\r\n}\r\n\r\nfunction triggerModalShownHandler(event) {\r\n let triggerButton = event.relatedTarget;\r\n\r\n let triggerType = triggerButton.getAttribute(\"data-bs-triggertype\");\r\n let triggerIndex = Number(triggerButton.getAttribute(\"data-bs-triggerindex\"));\r\n\r\n let triggerTarget;\r\n switch (triggerType) {\r\n case \"food\":\r\n triggerTarget = food[triggerIndex];\r\n break;\r\n case \"drink\":\r\n triggerTarget = drinks[triggerIndex];\r\n break;\r\n case \"ability\":\r\n triggerTarget = abilities[triggerIndex];\r\n break;\r\n }\r\n\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n triggerTargetnput.value = triggerTarget;\r\n modalTriggers = triggerMap[triggerTarget];\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerModalSaveHandler(event) {\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n let triggerTarget = triggerTargetnput.value;\r\n\r\n triggerMap[triggerTarget] = modalTriggers;\r\n}\r\n\r\nfunction triggerDependencySelectHandler(event, index) {\r\n modalTriggers[index].dependencyHrid = event.target.value;\r\n modalTriggers[index].conditionHrid = \"\";\r\n modalTriggers[index].comparatorHrid = \"\";\r\n modalTriggers[index].value = 0;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerConditionSelectHandler(event, index) {\r\n modalTriggers[index].conditionHrid = event.target.value;\r\n modalTriggers[index].comparatorHrid = \"\";\r\n modalTriggers[index].value = 0;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerComparatorSelectHander(event, index) {\r\n modalTriggers[index].comparatorHrid = event.target.value;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerValueInputHandler(event, index) {\r\n modalTriggers[index].value = Number(event.target.value);\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerRemoveButtonHandler(event, index) {\r\n modalTriggers.splice(index, 1);\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerAddButtonHandler(event) {\r\n if (modalTriggers.length == 4) {\r\n return;\r\n }\r\n\r\n modalTriggers.push({\r\n dependencyHrid: \"\",\r\n conditionHrid: \"\",\r\n comparatorHrid: \"\",\r\n value: 0,\r\n });\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerDefaultButtonHandler(event) {\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n let triggerTarget = triggerTargetnput.value;\r\n\r\n if (triggerTarget.startsWith(\"/items/\")) {\r\n modalTriggers = structuredClone(itemDetailMap[triggerTarget].consumableDetail.defaultCombatTriggers);\r\n } else {\r\n modalTriggers = structuredClone(abilityDetailMap[triggerTarget].defaultCombatTriggers);\r\n }\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction updateTriggerModal() {\r\n let triggerStartTextElement = document.getElementById(\"triggerStartText\");\r\n if (modalTriggers.length == 0) {\r\n triggerStartTextElement.innerHTML = \"Activate as soon as it's off cooldown\";\r\n } else {\r\n triggerStartTextElement.innerHTML = \"Activate when:\";\r\n }\r\n\r\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\r\n triggerAddButton.disabled = modalTriggers.length == 4;\r\n\r\n let triggersValid = true;\r\n\r\n for (let i = 0; i < 4; i++) {\r\n let triggerElement = document.getElementById(\"modalTrigger_\" + i);\r\n\r\n if (!modalTriggers[i]) {\r\n hideElement(triggerElement);\r\n continue;\r\n }\r\n\r\n showElement(triggerElement);\r\n\r\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\r\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\r\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\r\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\r\n\r\n showElement(triggerDependencySelect);\r\n fillTriggerDependencySelect(triggerDependencySelect);\r\n\r\n if (modalTriggers[i].dependencyHrid == \"\") {\r\n hideElement(triggerConditionSelect);\r\n hideElement(triggerComparatorSelect);\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerDependencySelect.value = modalTriggers[i].dependencyHrid;\r\n showElement(triggerConditionSelect);\r\n fillTriggerConditionSelect(triggerConditionSelect, modalTriggers[i].dependencyHrid);\r\n\r\n if (modalTriggers[i].conditionHrid == \"\") {\r\n hideElement(triggerComparatorSelect);\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerConditionSelect.value = modalTriggers[i].conditionHrid;\r\n showElement(triggerComparatorSelect);\r\n fillTriggerComparatorSelect(triggerComparatorSelect, modalTriggers[i].conditionHrid);\r\n\r\n if (modalTriggers[i].comparatorHrid == \"\") {\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerComparatorSelect.value = modalTriggers[i].comparatorHrid;\r\n\r\n if (combatTriggerComparatorDetailMap[modalTriggers[i].comparatorHrid].allowValue) {\r\n showElement(triggerValueInput);\r\n triggerValueInput.value = modalTriggers[i].value;\r\n } else {\r\n hideElement(triggerValueInput);\r\n }\r\n }\r\n\r\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\r\n triggerSaveButton.disabled = !triggersValid;\r\n\r\n updateContent();\r\n}\r\n\r\nfunction fillTriggerDependencySelect(element) {\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const dependency of Object.values(combatTriggerDependencyDetailMap).sort(\r\n (a, b) => a.sortIndex - b.sortIndex\r\n )) {\r\n let opt = new Option(dependency.name, dependency.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerDependencyNames.\" + dependency.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction fillTriggerConditionSelect(element, dependencyHrid) {\r\n let dependency = combatTriggerDependencyDetailMap[dependencyHrid];\r\n\r\n let conditions;\r\n if (dependency.isSingleTarget) {\r\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isSingleTarget);\r\n } else {\r\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isMultiTarget);\r\n }\r\n\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const condition of Object.values(conditions).sort((a, b) => a.sortIndex - b.sortIndex)) {\r\n let opt = new Option(condition.name, condition.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerConditionNames.\" + condition.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction fillTriggerComparatorSelect(element, conditionHrid) {\r\n let condition = combatTriggerConditionDetailMap[conditionHrid];\r\n\r\n let comparators = condition.allowedComparatorHrids.map((hrid) => combatTriggerComparatorDetailMap[hrid]);\r\n\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const comparator of Object.values(comparators).sort((a, b) => a.sortIndex - b.sortIndex)) {\r\n let opt = new Option(comparator.name, comparator.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerComparatorNames.\" + comparator.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction hideElement(element) {\r\n element.classList.remove(\"d-flex\");\r\n element.classList.add(\"d-none\");\r\n}\r\n\r\nfunction showElement(element) {\r\n element.classList.remove(\"d-none\");\r\n element.classList.add(\"d-flex\");\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Zones\r\n\r\nfunction initZones() {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n\r\n // TOOD dungeon wave spawns\r\n let gameZones = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const zone of Object.values(gameZones)) {\r\n let opt = new Option(zone.name, zone.hrid);\r\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + zone.hrid);\r\n zoneSelect.add(opt);\r\n }\r\n\r\n\r\n let zoneCheckBox = document.getElementById(\"zoneCheckBox\");\r\n let checkAllZonesToggle = document.getElementById('checkAllZones');\r\n\r\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\r\n simAllZonesToggle.addEventListener(\"change\", (event) => {\r\n if (simAllZonesToggle.checked) {\r\n zoneCheckBox.classList.remove(\"d-none\");\r\n zoneCheckBox.querySelectorAll(\".zone-checkbox\").forEach(checkbox => checkbox.checked = true);\r\n checkAllZonesToggle.checked = true;\r\n } else {\r\n zoneCheckBox.classList.add(\"d-none\");\r\n }\r\n });\r\n\r\n let zoneHrids = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .flat();\r\n\r\n for (const zoneHrid of zoneHrids) {\r\n const newZone = document.createElement('div');\r\n newZone.classList.add('form-check');\r\n newZone.innerHTML = `\r\n \r\n \r\n `;\r\n zoneCheckBox.append(newZone);\r\n }\r\n\r\n let checkZoneToggles = document.querySelectorAll('.zone-checkbox');\r\n checkAllZonesToggle.addEventListener('change', () => {\r\n checkZoneToggles.forEach(cb => cb.checked = checkAllZonesToggle.checked);\r\n });\r\n\r\n checkZoneToggles.forEach(cb =>\r\n cb.addEventListener('change', () => {\r\n checkAllZonesToggle.checked = [...checkZoneToggles].every(x => x.checked);\r\n })\r\n );\r\n\r\n\r\n let soloCheckBox = document.getElementById(\"soloCheckBox\");\r\n let checkAllSolosToggle = document.getElementById('checkAllSolos');\r\n\r\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\r\n simAllSoloToggle.addEventListener(\"change\", (event) => {\r\n if (simAllSoloToggle.checked) {\r\n soloCheckBox.classList.remove(\"d-none\");\r\n soloCheckBox.querySelectorAll(\".solo-checkbox\").forEach(checkbox => checkbox.checked = true);\r\n checkAllSolosToggle.checked = true;\r\n } else {\r\n soloCheckBox.classList.add(\"d-none\");\r\n }\r\n });\r\n\r\n let soloHrids = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount == 1)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .flat();\r\n\r\n for (const zoneHrid of soloHrids) {\r\n const newZone = document.createElement('div');\r\n newZone.classList.add('form-check');\r\n newZone.innerHTML = `\r\n \r\n \r\n `;\r\n soloCheckBox.append(newZone);\r\n }\r\n\r\n let checkSoloToggles = document.querySelectorAll('.solo-checkbox');\r\n checkAllSolosToggle.addEventListener('change', () => {\r\n checkSoloToggles.forEach(cb => cb.checked = checkAllSolosToggle.checked);\r\n });\r\n\r\n checkSoloToggles.forEach(cb =>\r\n cb.addEventListener('change', () => {\r\n checkAllSolosToggle.checked = [...checkSoloToggles].every(x => x.checked);\r\n })\r\n );\r\n}\r\n\r\nfunction initDungeons() {\r\n let dungeonSelect = document.getElementById(\"selectDungeon\");\r\n\r\n let gameDungeons = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category == \"/action_categories/combat/dungeons\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const dungeon of Object.values(gameDungeons)) {\r\n let opt = new Option(dungeon.name, dungeon.hrid);\r\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + dungeon.hrid);\r\n dungeonSelect.add(opt);\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Simulation Result\r\n\r\nfunction createDamageDoneAccordion(enemyIndex) {\r\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageDoneAccordionEnemy${enemyIndex}`);\r\n\r\n const colDiv = createElement('div', 'col');\r\n const accordionMainDiv = createElement('div', 'accordion');\r\n const accordionItemDiv = createElement('div', 'accordion-item');\r\n\r\n const headerH2 = createElement('h2', 'accordion-header');\r\n const button = createElement('button', 'accordion-button collapsed',\r\n `Damage Done (Enemy ${enemyIndex})`,\r\n `buttonSimulationResultDamageDoneAccordionEnemy${enemyIndex}`\r\n );\r\n button.setAttribute('type', 'button');\r\n button.setAttribute('data-bs-toggle', 'collapse');\r\n button.setAttribute('data-bs-target', `#collapseDamageDone${enemyIndex}`);\r\n button.style.padding = '0.5em';\r\n\r\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageDone${enemyIndex}`);\r\n const accordionBodyDiv = createElement('div', 'accordion-body');\r\n\r\n const headerRow = createElement('div', 'row');\r\n headerRow.innerHTML = `\r\n
Source
\r\n
Hitchance
\r\n
DPS
\r\n
%
\r\n `;\r\n\r\n const resultDiv = createElement('div', '', '', `simulationResultDamageDoneEnemy${enemyIndex}`);\r\n\r\n accordionBodyDiv.appendChild(headerRow);\r\n accordionBodyDiv.appendChild(resultDiv);\r\n collapseDiv.appendChild(accordionBodyDiv);\r\n headerH2.appendChild(button);\r\n accordionItemDiv.appendChild(headerH2);\r\n accordionItemDiv.appendChild(collapseDiv);\r\n accordionMainDiv.appendChild(accordionItemDiv);\r\n colDiv.appendChild(accordionMainDiv);\r\n accordionDiv.appendChild(colDiv);\r\n\r\n return accordionDiv;\r\n}\r\nfunction createDamageTakenAccordion(enemyIndex) {\r\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageTakenAccordionEnemy${enemyIndex}`);\r\n\r\n const colDiv = createElement('div', 'col');\r\n const accordionMainDiv = createElement('div', 'accordion');\r\n const accordionItemDiv = createElement('div', 'accordion-item');\r\n\r\n const headerH2 = createElement('h2', 'accordion-header');\r\n const button = createElement('button', 'accordion-button collapsed',\r\n `Damage Taken (Enemy ${enemyIndex})`,\r\n `buttonSimulationResultDamageTakenAccordionEnemy${enemyIndex}`\r\n );\r\n button.setAttribute('type', 'button');\r\n button.setAttribute('data-bs-toggle', 'collapse');\r\n button.setAttribute('data-bs-target', `#collapseDamageTaken${enemyIndex}`);\r\n button.style.padding = '0.5em';\r\n\r\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageTaken${enemyIndex}`);\r\n const accordionBodyDiv = createElement('div', 'accordion-body');\r\n\r\n const headerRow = createElement('div', 'row');\r\n headerRow.innerHTML = `\r\n
Source
\r\n
Hitchance
\r\n
DPS
\r\n
%
\r\n `;\r\n\r\n const resultDiv = createElement('div', '', '', `simulationResultDamageTakenEnemy${enemyIndex}`);\r\n\r\n accordionBodyDiv.appendChild(headerRow);\r\n accordionBodyDiv.appendChild(resultDiv);\r\n collapseDiv.appendChild(accordionBodyDiv);\r\n headerH2.appendChild(button);\r\n accordionItemDiv.appendChild(headerH2);\r\n accordionItemDiv.appendChild(collapseDiv);\r\n accordionMainDiv.appendChild(accordionItemDiv);\r\n colDiv.appendChild(accordionMainDiv);\r\n accordionDiv.appendChild(colDiv);\r\n\r\n return accordionDiv;\r\n}\r\n\r\n\r\nfunction initDamageDoneTaken() {\r\n for (let i = 64; i > 0; i--) {\r\n document.getElementById(\"simulationResultTotalDamageDone\").insertAdjacentElement('afterend', createDamageDoneAccordion(i));\r\n document.getElementById(\"simulationResultTotalDamageTaken\").insertAdjacentElement('afterend', createDamageTakenAccordion(i));\r\n }\r\n}\r\n\r\nfunction showSimulationResult(simResult) {\r\n currentSimResults = simResult;\r\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\r\n expensesModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\r\n revenueModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let noRngRevenueModalTable = document.querySelector(\"#noRngRevenueTable > tbody\");\r\n noRngRevenueModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let playerToDisplay = \"player1\";\r\n if (selectedPlayers.includes(parseInt(currentPlayerTabId))) {\r\n playerToDisplay = \"player\" + currentPlayerTabId;\r\n }\r\n if (!simResult.dropRateMultiplier[playerToDisplay]) {\r\n return;\r\n }\r\n\r\n showKills(simResult, playerToDisplay);\r\n showDeaths(simResult, playerToDisplay);\r\n showExperienceGained(simResult, playerToDisplay);\r\n showConsumablesUsed(simResult, playerToDisplay);\r\n showHpSpent(simResult, playerToDisplay);\r\n showManaUsed(simResult, playerToDisplay);\r\n showHitpointsGained(simResult, playerToDisplay);\r\n showManapointsGained(simResult, playerToDisplay);\r\n showDamageDone(simResult, playerToDisplay);\r\n showDamageTaken(simResult, playerToDisplay);\r\n renderWipeEvents(simResult);\r\n window.profit = window.revenue - window.expenses;\r\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\r\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\r\n window.noRngProfit = window.noRngRevenue - window.expenses;\r\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\r\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\r\n}\r\n\r\nfunction showAllSimulationResults(simResults) {\r\n let displaySimResults = manipulateSimResultsDataForDisplay(simResults);\r\n updateAllSimsModal(displaySimResults);\r\n}\r\n\r\nfunction manipulateSimResultsDataForDisplay(simResults) {\r\n let displaySimResults = [];\r\n for (let i = 0; i < simResults.length; i++) {\r\n for (let j = 0; j < selectedPlayers.length; j++) {\r\n let playerToDisplay = \"player\" + selectedPlayers[j].toString();\r\n let simResult = simResults[i];\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let zoneName = simResult.zoneName;\r\n let difficultyTier = simResult.difficultyTier;\r\n let encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\r\n let playerDeaths = simResult.deaths[playerToDisplay] ?? 0;\r\n let deathsPerHour = (playerDeaths / hoursSimulated).toFixed(2);\r\n\r\n let totalExperience = 0;\r\n if (simResult.experienceGained[playerToDisplay]) {\r\n totalExperience = Object.values(simResult.experienceGained[playerToDisplay]).reduce((prev, cur) => prev + cur, 0);\r\n }\r\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\r\n\r\n let experiencePerHour = {};\r\n const skills = [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"];\r\n skills.forEach((skill) => {\r\n const skillLower = skill.toLowerCase();\r\n let experience = simResult.experienceGained[playerToDisplay]?.[skillLower] ?? 0;\r\n let experiencePerHourValue = 0;\r\n if (experience != 0) {\r\n experiencePerHourValue = (experience / hoursSimulated).toFixed(0);\r\n }\r\n experiencePerHour[skill] = experiencePerHourValue;\r\n });\r\n getDropProfit(simResult, playerToDisplay);\r\n let noRngRevenue = simResult[\"noRngRevenue\"];\r\n let noRngProfit = simResult[\"noRngProfit\"];\r\n let expenses = simResult[\"expenses\"];\r\n\r\n let displaySimRow = {\r\n \"ZoneName\": zoneName, \"DifficultyTier\": difficultyTier, \"Player\": playerToDisplay, \"Encounters\": encountersPerHour, \"Deaths\": deathsPerHour,\r\n \"TotalExperience\": totalExperiencePerHour, \"Stamina\": experiencePerHour[\"Stamina\"],\r\n \"Intelligence\": experiencePerHour[\"Intelligence\"], \"Attack\": experiencePerHour[\"Attack\"],\r\n \"Magic\": experiencePerHour[\"Magic\"], \"Ranged\": experiencePerHour[\"Ranged\"],\r\n \"Melee\": experiencePerHour[\"Melee\"], \"Defense\": experiencePerHour[\"Defense\"],\r\n \"noRngRevenue\": noRngRevenue,\r\n \"expenses\": expenses,\r\n \"noRngProfit\": noRngProfit\r\n };\r\n displaySimResults.push(displaySimRow);\r\n }\r\n }\r\n return displaySimResults;\r\n}\r\n\r\nfunction fidDropAmount(dropAmount) {\r\n if (Number.isInteger(dropAmount)) return dropAmount;\r\n\r\n const intPart = Math.floor(dropAmount);\r\n const fracPart = dropAmount - intPart;\r\n return Math.random() < fracPart ? intPart + 1 : intPart;\r\n}\r\n\r\nfunction calcDropMaps(simResult, playerToDisplay) {\r\n let dropRateMultiplier = simResult.dropRateMultiplier[playerToDisplay];\r\n let rareFindMultiplier = simResult.rareFindMultiplier[playerToDisplay];\r\n let combatDropQuantity = simResult.combatDropQuantity[playerToDisplay];\r\n let debuffOnLevelGap = simResult.debuffOnLevelGap[playerToDisplay];\r\n\r\n let numberOfPlayers = simResult.numberOfPlayers;\r\n let monsters = Object.keys(simResult.deaths)\r\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\r\n .sort();\r\n\r\n const totalDropMap = new Map();\r\n const noRngTotalDropMap = new Map();\r\n for (const monster of monsters) {\r\n const dropMap = new Map();\r\n const rareDropMap = new Map();\r\n if (combatMonsterDetailMap[monster].dropTable) {\r\n for (const drop of combatMonsterDetailMap[monster].dropTable) {\r\n if (drop.minDifficultyTier > simResult.difficultyTier) {\r\n continue;\r\n }\r\n\r\n let multiplier = 1.0 + 0.1 * simResult.difficultyTier;\r\n let dropRate = Math.min(1.0, multiplier * (drop.dropRate + (drop.dropRatePerDifficultyTier ?? 0) * simResult.difficultyTier));\r\n if (dropRate <= 0) continue;\r\n\r\n dropMap.set(drop.itemHrid, { \"dropRate\": Math.min(1.0, dropRate * dropRateMultiplier), \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\r\n }\r\n if (combatMonsterDetailMap[monster].rareDropTable)\r\n for (const drop of combatMonsterDetailMap[monster].rareDropTable) {\r\n if (drop.minDifficultyTier > simResult.difficultyTier) {\r\n continue;\r\n }\r\n rareDropMap.set(drop.itemHrid, { \"dropRate\": drop.dropRate * rareFindMultiplier, \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\r\n }\r\n\r\n for (let dropObject of dropMap.values()) {\r\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\r\n\r\n }\r\n for (let dropObject of rareDropMap.values()) {\r\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\r\n }\r\n\r\n for (let i = 0; i < simResult.deaths[monster]; i++) {\r\n for (let dropObject of dropMap.values()) {\r\n let chance = Math.random();\r\n if (chance <= dropObject.dropRate / numberOfPlayers) {\r\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\r\n dropObject.number = dropObject.number + fidDropAmount(amount);\r\n }\r\n }\r\n for (let dropObject of rareDropMap.values()) {\r\n let chance = Math.random();\r\n if (chance <= dropObject.dropRate / numberOfPlayers) {\r\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\r\n dropObject.number = dropObject.number + fidDropAmount(amount);\r\n }\r\n }\r\n }\r\n for (let [name, dropObject] of dropMap.entries()) {\r\n if (totalDropMap.has(name)) {\r\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\r\n } else {\r\n totalDropMap.set(name, dropObject.number);\r\n }\r\n if (noRngTotalDropMap.has(name)) {\r\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\r\n } else {\r\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\r\n }\r\n }\r\n for (let [name, dropObject] of rareDropMap.entries()) {\r\n if (totalDropMap.has(name)) {\r\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\r\n } else {\r\n totalDropMap.set(name, dropObject.number);\r\n }\r\n if (noRngTotalDropMap.has(name)) {\r\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\r\n } else {\r\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return { totalDropMap, noRngTotalDropMap };\r\n}\r\n\r\nfunction getDropProfit(simResult, playerToDisplay) {\r\n let { totalDropMap, noRngTotalDropMap } = calcDropMaps(simResult, playerToDisplay);\r\n\r\n let noRngTotal = 0;\r\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\r\n let price = -1;\r\n let revenueSetting = document.getElementById('selectPrices_drops').value;\r\n if (window.prices) {\r\n let item = window.prices[name];\r\n if (item) {\r\n if (revenueSetting == 'bid') {\r\n if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n } else if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n }\r\n } else if (revenueSetting == 'ask') {\r\n if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n } else if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n }\r\n }\r\n if (price == -1) {\r\n price = item['vendor'];\r\n }\r\n }\r\n }\r\n noRngTotal += price * dropAmount;\r\n }\r\n\r\n let consumablesUsed = simResult.consumablesUsed?.[playerToDisplay];\r\n\r\n if (consumablesUsed) {\r\n consumablesUsed = Object.entries(consumablesUsed).sort((a, b) => b[1] - a[1]);\r\n } else {\r\n consumablesUsed = [];\r\n }\r\n\r\n let expenses = 0;\r\n for (const [consumable, amount] of consumablesUsed) {\r\n let price = -1;\r\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\r\n if (window.prices) {\r\n let item = window.prices[consumable];\r\n if (item) {\r\n if (expensesSetting == 'bid') {\r\n if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n } else if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n }\r\n } else if (expensesSetting == 'ask') {\r\n if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n } else if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n }\r\n }\r\n if (price == -1) {\r\n price = item['vendor'];\r\n }\r\n }\r\n }\r\n expenses += price * amount;\r\n }\r\n\r\n simResult[\"noRngRevenue\"] = (noRngTotal).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n simResult[\"expenses\"] = (expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n simResult[\"noRngProfit\"] = (noRngTotal - expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n}\r\n\r\nfunction updateAllSimsModal(data) {\r\n const tableBody = document.getElementById('allZonesData').getElementsByTagName('tbody')[0];\r\n tableBody.innerHTML = '';\r\n data.forEach(item => {\r\n const row = document.createElement('tr');\r\n\r\n Object.keys(item).forEach(key => {\r\n const cell = document.createElement('td');\r\n cell.textContent = item[key];\r\n if (key === 'ZoneName') {\r\n cell.setAttribute(\"data-i18n\", \"actionNames.\" + item[key]);\r\n }\r\n row.appendChild(cell);\r\n });\r\n\r\n tableBody.appendChild(row);\r\n });\r\n\r\n const table = document.getElementById('allZonesData');\r\n const rows = table.getElementsByTagName('tr');\r\n const numCols = rows[0].cells.length;\r\n\r\n // 遍历每一列\r\n for (let col = 5; col < numCols; col++) {\r\n let max = -Infinity;\r\n let maxCell = null;\r\n\r\n // 找到最大值及其单元格\r\n for (let row = 1; row < rows.length; row++) {\r\n const cell = rows[row].cells[col];\r\n const value = parseFloat(cell.textContent.replace(/,/g, ''));\r\n if (value > max) {\r\n max = value;\r\n maxCell = cell;\r\n }\r\n }\r\n\r\n // 将最大值单元格的背景色设置为绿色\r\n if (maxCell && max != 0) {\r\n maxCell.style.backgroundColor = 'green';\r\n maxCell.style.color = 'white'; // 设置文字颜色为白色以提高可读性\r\n }\r\n }\r\n}\r\n\r\nlet currentSortColumn = null;\r\nlet currentSortDirection = 'desc';\r\n\r\nfunction sortTable(tableId, columnIndex, direction) {\r\n const table = document.getElementById(tableId);\r\n const tbody = table.querySelector('tbody');\r\n const rows = Array.from(tbody.querySelectorAll('tr'));\r\n\r\n const sortedRows = rows.sort((rowA, rowB) => {\r\n const cellA = rowA.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\r\n const cellB = rowB.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\r\n\r\n const valueA = parseFloat(cellA.replace(/,/g, ''));\r\n const valueB = parseFloat(cellB.replace(/,/g, ''));\r\n\r\n return direction === 'asc' ? valueA - valueB : valueB - valueA;\r\n });\r\n\r\n sortedRows.forEach(row => tbody.appendChild(row));\r\n updateSortIndicators(tableId, columnIndex, direction);\r\n}\r\n\r\nfunction updateSortIndicators(tableId, columnIndex, direction) {\r\n const headers = document.querySelectorAll(`#${tableId} th`);\r\n headers.forEach((header, index) => {\r\n header.classList.remove('sort-asc', 'sort-desc');\r\n if (index === columnIndex) {\r\n header.classList.add(direction === 'asc' ? 'sort-asc' : 'sort-desc');\r\n }\r\n });\r\n}\r\n\r\ndocument.querySelectorAll('#allZonesData th').forEach((header, index) => {\r\n if (index === 0) return;\r\n if (index === 1) return;\r\n if (index === 2) return;\r\n\r\n header.addEventListener('click', () => {\r\n if (currentSortColumn === index) {\r\n currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';\r\n } else {\r\n currentSortColumn = index;\r\n currentSortDirection = 'desc';\r\n }\r\n sortTable('allZonesData', currentSortColumn, currentSortDirection);\r\n });\r\n});\r\n\r\ndocument.getElementById('buttonExportResults').addEventListener('click', function () {\r\n var table = document.getElementById('allZonesData');\r\n var csv = [];\r\n var rows = table.querySelectorAll('tr');\r\n\r\n for (var i = 0; i < rows.length; i++) {\r\n var row = rows[i];\r\n var cols = row.querySelectorAll('th, td');\r\n var csvRow = [];\r\n\r\n cols.forEach(function (col) {\r\n csvRow.push('\"' + col.innerText.replace(/\"/g, '\"\"') + '\"');\r\n });\r\n\r\n csv.push(csvRow.join(','));\r\n }\r\n\r\n var csvFile = new Blob([csv.join('\\n')], { type: 'text/csv' });\r\n var downloadLink = document.createElement('a');\r\n downloadLink.download = 'simData.csv';\r\n downloadLink.href = URL.createObjectURL(csvFile);\r\n downloadLink.style.display = 'none';\r\n document.body.appendChild(downloadLink);\r\n downloadLink.click();\r\n document.body.removeChild(downloadLink);\r\n});\r\n\r\nfunction showKills(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultKills\");\r\n let dropsResultDiv = document.getElementById(\"simulationResultDrops\");\r\n let noRngDropsResultDiv = document.getElementById(\"noRngDrops\");\r\n let newChildren = [];\r\n let newDropChildren = [];\r\n let newNoRngDropChildren = [];\r\n\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let encountersPerHour = 0;\r\n let encountersRow = null;\r\n if (simResult.isDungeon) {\r\n let wavesCompletedRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Wave Reached\", simResult.maxWaveReached]);\r\n wavesCompletedRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxWaveReached\");\r\n newChildren.push(wavesCompletedRow);\r\n let completedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Completed Dungeons\", simResult.dungeonsCompleted]);\r\n completedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsCompleted\");\r\n newChildren.push(completedDungeonsRow);\r\n if (simResult.dungeonsFailed > 0) {\r\n let failedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Failed Dungeons\", simResult.dungeonsFailed]);\r\n failedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsFailed\");\r\n newChildren.push(failedDungeonsRow);\r\n }\r\n encountersPerHour = (simResult.dungeonsCompleted / hoursSimulated).toFixed(1);\r\n let averageTime = (hoursSimulated * 60 / simResult.dungeonsCompleted).toFixed(1);\r\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Average Time\", averageTime]);\r\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.averageTime\");\r\n } else {\r\n encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\r\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Encounters\", encountersPerHour]);\r\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.encounters\");\r\n }\r\n\r\n if (simResult.maxEnrageStack > 0) {\r\n let enrageRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Enrage Stack\", simResult.maxEnrageStack]);\r\n enrageRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxEnrageStack\");\r\n newChildren.push(enrageRow);\r\n }\r\n\r\n if (simResult.debuffOnLevelGap[playerToDisplay] != 0) {\r\n let debuffOnLevelGapRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Debuff on Level Gap\", Math.round(simResult.debuffOnLevelGap[playerToDisplay] * 100) + \"%\"]);\r\n debuffOnLevelGapRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.debuffOnLevelGap\");\r\n newChildren.push(debuffOnLevelGapRow);\r\n }\r\n\r\n newChildren.push(encountersRow);\r\n\r\n Object.keys(simResult.deaths)\r\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\r\n .sort()\r\n .forEach(monster => {\r\n let killsPerHour = (simResult.deaths[monster] / hoursSimulated).toFixed(1);\r\n let monsterRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [combatMonsterDetailMap[monster].name, killsPerHour]\r\n );\r\n monsterRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + monster);\r\n newChildren.push(monsterRow);\r\n });\r\n\r\n let { totalDropMap, noRngTotalDropMap } = !simResult.isDungeon ? calcDropMaps(simResult, playerToDisplay) : {totalDropMap:new Map(), noRngTotalDropMap:new Map()};\r\n\r\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\r\n let total = 0;\r\n for (let [name, dropAmount] of totalDropMap.entries()) {\r\n let dropRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [name, dropAmount.toLocaleString()]\r\n );\r\n dropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\r\n newDropChildren.push(dropRow);\r\n\r\n let tableRow = ' tbody\");\r\n let noRngTotal = 0;\r\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\r\n let noRngDropRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [name, dropAmount.toLocaleString()]\r\n );\r\n noRngDropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\r\n newNoRngDropChildren.push(noRngDropRow);\r\n\r\n let tableRow = ' prev + cur, 0);\r\n }\r\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\r\n let totalRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Total\", totalExperiencePerHour]);\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"].forEach((skill) => {\r\n let experience = simResult.experienceGained[playerToDisplay]?.[skill.toLowerCase()] ?? 0;\r\n if (experience == 0) {\r\n return;\r\n }\r\n let experiencePerHour = (experience / hoursSimulated).toFixed(0);\r\n let experienceRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [skill, experiencePerHour]);\r\n experienceRow.firstElementChild.setAttribute(\"data-i18n\", \"leaderboardCategoryNames.\" + skill.toLowerCase());\r\n newChildren.push(experienceRow);\r\n });\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showHpSpent(simResult, playerToDisplay) {\r\n let hpSpentHeadingDiv = document.getElementById(\"simulationHpSpentHeading\");\r\n hpSpentHeadingDiv.classList.add(\"d-none\");\r\n let hpSpentDiv = document.getElementById(\"simulationHpSpent\");\r\n hpSpentDiv.classList.add(\"d-none\");\r\n\r\n if (simResult.hitpointsSpent[playerToDisplay]) {\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let hpSpentSources = [];\r\n for (const source of Object.keys(simResult.hitpointsSpent[playerToDisplay])) {\r\n let hpSpentPerHour = (simResult.hitpointsSpent[playerToDisplay][source] / hoursSimulated).toFixed(2);\r\n let hpSpentRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [abilityDetailMap[source].name, hpSpentPerHour]);\r\n hpSpentRow.firstElementChild.setAttribute(\"data-i18n\", \"abilityNames.\" + source);\r\n hpSpentSources.push(hpSpentRow);\r\n }\r\n hpSpentDiv.replaceChildren(...hpSpentSources);\r\n hpSpentHeadingDiv.classList.remove(\"d-none\");\r\n hpSpentDiv.classList.remove(\"d-none\");\r\n }\r\n}\r\n\r\nfunction showConsumablesUsed(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultConsumablesUsed\");\r\n let newChildren = [];\r\n\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n\r\n if (!simResult.consumablesUsed[playerToDisplay]) {\r\n resultDiv.replaceChildren(...newChildren);\r\n window.expenses = 0;\r\n return;\r\n }\r\n\r\n let consumablesUsed = Object.entries(simResult.consumablesUsed[playerToDisplay]).sort((a, b) => b[1] - a[1]);\r\n\r\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\r\n let total = 0;\r\n for (const [consumable, amount] of consumablesUsed) {\r\n let consumablesPerHour = (amount / hoursSimulated).toFixed(0);\r\n let consumableRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [itemDetailMap[consumable].name, consumablesPerHour]\r\n );\r\n consumableRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + consumable);\r\n newChildren.push(consumableRow);\r\n\r\n let tableRow = ' b[1] - a[1]);\r\n\r\n let totalHitpointsGained = hitpointsGained.reduce((prev, cur) => prev + cur[1], 0);\r\n let totalHitpointsPerSecond = (totalHitpointsGained / secondsSimulated).toFixed(2);\r\n let totalRow = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [\"Total\", totalHitpointsPerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [source, amount] of hitpointsGained) {\r\n if (amount == 0) {\r\n continue;\r\n }\r\n\r\n let sourceText;\r\n let sourceFullHrid;\r\n switch (source) {\r\n case \"regen\":\r\n sourceText = \"Regen\";\r\n sourceFullHrid = \"combatStats.hpRegenPer10\";\r\n break;\r\n case \"lifesteal\":\r\n sourceText = \"Life Steal\";\r\n sourceFullHrid = \"combatStats.lifeSteal\";\r\n break;\r\n case \"bloom\":\r\n sourceText = \"Bloom\";\r\n sourceFullHrid = \"combatStats.bloom\";\r\n break;\r\n default:\r\n if (itemDetailMap[source]) {\r\n sourceText = itemDetailMap[source].name;\r\n sourceFullHrid = \"itemNames.\" + source;\r\n } else if (abilityDetailMap[source]) {\r\n sourceText = abilityDetailMap[source].name;\r\n sourceFullHrid = \"abilityNames.\" + source;\r\n }\r\n break;\r\n }\r\n let hitpointsPerSecond = (amount / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * amount) / totalHitpointsGained).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [sourceText, hitpointsPerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showManapointsGained(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultManaRestored\");\r\n let newChildren = [];\r\n\r\n let secondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n if (!simResult.manapointsGained[playerToDisplay]) {\r\n resultDiv.replaceChildren(...newChildren);\r\n return;\r\n }\r\n\r\n let manapointsGained = Object.entries(simResult.manapointsGained[playerToDisplay]).sort((a, b) => b[1] - a[1]);\r\n\r\n let totalManapointsGained = manapointsGained.reduce((prev, cur) => prev + cur[1], 0);\r\n let totalManapointsPerSecond = (totalManapointsGained / secondsSimulated).toFixed(2);\r\n let totalRow = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [\"Total\", totalManapointsPerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [source, amount] of manapointsGained) {\r\n if (amount == 0) {\r\n continue;\r\n }\r\n\r\n let sourceText;\r\n let sourceFullHrid;\r\n switch (source) {\r\n case \"regen\":\r\n sourceText = \"Regen\";\r\n sourceFullHrid = \"combatStats.mpRegenPer10\";\r\n break;\r\n case \"manaLeech\":\r\n sourceText = \"Mana Leech\";\r\n sourceFullHrid = \"combatStats.manaLeech\";\r\n break;\r\n case \"ripple\":\r\n sourceText = \"Ripple\";\r\n sourceFullHrid = \"combatStats.ripple\";\r\n break;\r\n default:\r\n sourceText = itemDetailMap[source].name;\r\n sourceFullHrid = \"itemNames.\" + source;\r\n break;\r\n }\r\n let manapointsPerSecond = (amount / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * amount) / totalManapointsGained).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [sourceText, manapointsPerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n let ranOutOfManaText = simResult.playerRanOutOfMana[playerToDisplay] ? \"Yes\" : \"No\";\r\n let ranOutOfManaRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Ran out of mana\", ranOutOfManaText]);\r\n ranOutOfManaRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfMana\");\r\n ranOutOfManaRow.lastElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.\" + ranOutOfManaText);\r\n newChildren.push(ranOutOfManaRow);\r\n\r\n if (simResult.playerRanOutOfMana[playerToDisplay]) {\r\n let ranOutOfManaStat = simResult.playerRanOutOfManaTime[playerToDisplay]; // {isOutOfMana: false, startTimeForOutOfMana:0, totalTimeForOutOfMana:0};\r\n let totalTimeForOut = ranOutOfManaStat.totalTimeForOutOfMana + (ranOutOfManaStat.isOutOfMana ? (simResult.simulatedTime - ranOutOfManaStat.startTimeForOutOfMana) : 0);\r\n\r\n let ranOutOfManaStatRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [\r\n \"Run Out Ratio\",\r\n (totalTimeForOut / simResult.simulatedTime * 100).toFixed(2) + \"%\"\r\n ]\r\n );\r\n ranOutOfManaStatRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfManaRatio\");\r\n newChildren.push(ranOutOfManaStatRow);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showDamageDone(simResult, playerToDisplay) {\r\n let totalDamageDone = {};\r\n let enemyIndex = 1;\r\n\r\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n for (let i = 1; i < 64; i++) {\r\n let accordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + i);\r\n hideElement(accordion);\r\n }\r\n\r\n let bossTimeHeadingDiv = document.getElementById(\"simulationBossTimeHeading\");\r\n bossTimeHeadingDiv.classList.add(\"d-none\");\r\n let bossTimeDiv = document.getElementById(\"simulationBossTime\");\r\n bossTimeDiv.classList.add(\"d-none\");\r\n\r\n if (!simResult.attacks[playerToDisplay]) {\r\n return;\r\n }\r\n\r\n for (const [target, abilities] of Object.entries(simResult.attacks[playerToDisplay])) {\r\n let targetDamageDone = {};\r\n\r\n const i = simResult.timeSpentAlive.findIndex(e => e.name === target);\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\r\n\r\n for (const [ability, abilityCasts] of Object.entries(abilities)) {\r\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\r\n let misses = abilityCasts[\"miss\"] ?? 0;\r\n let damage = Object.entries(abilityCasts)\r\n .filter((entry) => entry[0] != \"miss\")\r\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\r\n\r\n targetDamageDone[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n if (totalDamageDone[ability]) {\r\n totalDamageDone[ability].casts += casts;\r\n totalDamageDone[ability].misses += misses;\r\n totalDamageDone[ability].damage += damage;\r\n } else {\r\n totalDamageDone[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n }\r\n }\r\n\r\n let resultDiv = document.getElementById(\"simulationResultDamageDoneEnemy\" + enemyIndex);\r\n createDamageTable(resultDiv, targetDamageDone, aliveSecondsSimulated);\r\n\r\n let resultAccordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + enemyIndex);\r\n showElement(resultAccordion);\r\n\r\n let resultAccordionButton = document.getElementById(\r\n \"buttonSimulationResultDamageDoneAccordionEnemy\" + enemyIndex\r\n );\r\n let targetName = combatMonsterDetailMap[target].name;\r\n resultAccordionButton.innerHTML = \"Damage Done (\" + \"\" + targetName + \"\" + \")\";\r\n\r\n if (simResult.bossSpawns.includes(target)) {\r\n let hoursSpentOnBoss = (aliveSecondsSimulated / 60 / 60).toFixed(2);\r\n let percentSpentOnBoss = (aliveSecondsSimulated / totalSecondsSimulated * 100).toFixed(2);\r\n\r\n let bossRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [targetName, hoursSpentOnBoss + \"h(\" + percentSpentOnBoss + \"%)\"]);\r\n bossRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + target);\r\n bossTimeDiv.replaceChildren(bossRow);\r\n\r\n bossTimeHeadingDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.classList.remove(\"d-none\");\r\n }\r\n\r\n enemyIndex++;\r\n }\r\n\r\n if (simResult.isDungeon) {\r\n let newChildren = [];\r\n for (const waveName of simResult.bossSpawns) {\r\n // waveName is something like \"#15,/monsters/jackalope,/monsters/butterjerry\"\r\n let waveNumber = waveName.split(\",\")[0];\r\n const idx = simResult.timeSpentAlive.findIndex(e => e.name === waveNumber);\r\n if (idx == -1 || simResult.timeSpentAlive[idx].count == 0) {\r\n continue;\r\n }\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[idx].timeSpentAlive / ONE_SECOND / simResult.timeSpentAlive[idx].count;\r\n let bossRow = createRow([\"col-md-6\", \"col-md-2\", \"col-md-4 text-end\"], [waveNumber, simResult.timeSpentAlive[idx].count, aliveSecondsSimulated.toFixed(1) + \"s\"]);\r\n newChildren.push(bossRow);\r\n }\r\n if (newChildren.length > 0) {\r\n bossTimeHeadingDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.replaceChildren(...newChildren);\r\n }\r\n }\r\n\r\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageDone\");\r\n createDamageTable(totalResultDiv, totalDamageDone, totalSecondsSimulated);\r\n}\r\n\r\nfunction showDamageTaken(simResult, playerToDisplay) {\r\n let totalDamageTaken = {};\r\n let enemyIndex = 1;\r\n\r\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n for (let i = 1; i < 64; i++) {\r\n let accordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + i);\r\n hideElement(accordion);\r\n }\r\n\r\n for (const [source, targets] of Object.entries(simResult.attacks)) {\r\n const validSources = [\"player1\", \"player2\", \"player3\", \"player4\", \"player5\"];\r\n if (validSources.includes(source)) {\r\n continue;\r\n }\r\n const i = simResult.timeSpentAlive.findIndex(e => e.name === source);\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\r\n let sourceDamageTaken = {};\r\n if (targets[playerToDisplay] && Object.keys(targets[playerToDisplay]).length > 0) {\r\n for (const [ability, abilityCasts] of Object.entries(targets[playerToDisplay])) {\r\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\r\n let misses = abilityCasts[\"miss\"] ?? 0;\r\n let damage = Object.entries(abilityCasts)\r\n .filter((entry) => entry[0] != \"miss\")\r\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\r\n\r\n sourceDamageTaken[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n if (totalDamageTaken[ability]) {\r\n totalDamageTaken[ability].casts += casts;\r\n totalDamageTaken[ability].misses += misses;\r\n totalDamageTaken[ability].damage += damage;\r\n } else {\r\n totalDamageTaken[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n }\r\n }\r\n }\r\n\r\n let resultDiv = document.getElementById(\"simulationResultDamageTakenEnemy\" + enemyIndex);\r\n createDamageTable(resultDiv, sourceDamageTaken, aliveSecondsSimulated);\r\n\r\n let resultAccordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + enemyIndex);\r\n showElement(resultAccordion);\r\n\r\n let resultAccordionButton = document.getElementById(\r\n \"buttonSimulationResultDamageTakenAccordionEnemy\" + enemyIndex\r\n );\r\n let sourceName = combatMonsterDetailMap[source].name;\r\n resultAccordionButton.innerHTML = \"Damage Taken (\" + \"\" + sourceName + \"\" + \")\";\r\n\r\n enemyIndex++;\r\n }\r\n\r\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageTaken\");\r\n createDamageTable(totalResultDiv, totalDamageTaken, totalSecondsSimulated);\r\n}\r\n\r\nfunction createDamageTable(resultDiv, damageDone, secondsSimulated) {\r\n let newChildren = [];\r\n\r\n let sortedDamageDone = Object.entries(damageDone).sort((a, b) => b[1].damage - a[1].damage);\r\n\r\n let totalCasts = sortedDamageDone.reduce((prev, cur) => prev + cur[1].casts, 0);\r\n let totalMisses = sortedDamageDone.reduce((prev, cur) => prev + cur[1].misses, 0);\r\n let totalDamage = sortedDamageDone.reduce((prev, cur) => prev + cur[1].damage, 0);\r\n let totalHitChance = ((100 * (totalCasts - totalMisses)) / totalCasts).toFixed(1);\r\n let totalDamagePerSecond = (totalDamage / secondsSimulated).toFixed(2);\r\n\r\n let totalRow = createRow(\r\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\r\n [\"Total\", totalHitChance + \"%\", totalDamagePerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [ability, damageInfo] of sortedDamageDone) {\r\n let abilityText;\r\n let abilityFullHrid;\r\n switch (ability) {\r\n case \"autoAttack\":\r\n abilityText = \"Auto Attack\";\r\n abilityFullHrid = \"combatUnit.autoAttack\";\r\n break;\r\n case \"parry\":\r\n abilityText = \"Parry Attack\";\r\n abilityFullHrid = \"common:simulationResults.parryAttack\";\r\n break;\r\n case \"damageOverTime\":\r\n abilityText = \"Damage Over Time\";\r\n abilityFullHrid = \"common:simulationResults.damageOverTime\";\r\n break;\r\n case \"physicalThorns\":\r\n abilityText = \"Physical Thorns\";\r\n abilityFullHrid = \"combatStats.physicalThorns\";\r\n break;\r\n case \"elementalThorns\":\r\n abilityText = \"Elemental Thorns\";\r\n abilityFullHrid = \"combatStats.elementalThorns\";\r\n break;\r\n case \"retaliation\":\r\n abilityText = \"Retaliation\";\r\n abilityFullHrid = \"combatStats.retaliation\";\r\n break;\r\n case 'blaze':\r\n abilityText = \"Blaze\";\r\n abilityFullHrid = \"combatStats.blaze\";\r\n break;\r\n default:\r\n abilityText = abilityDetailMap[ability].name;\r\n abilityFullHrid = \"abilityNames.\" + ability;\r\n break;\r\n }\r\n\r\n let hitChance = ((100 * (damageInfo.casts - damageInfo.misses)) / damageInfo.casts).toFixed(1);\r\n let damagePerSecond = (damageInfo.damage / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * damageInfo.damage) / totalDamage).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\r\n [abilityText, hitChance + \"%\", damagePerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", abilityFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction createRow(columnClassNames, columnValues) {\r\n let row = createElement(\"div\", \"row\");\r\n\r\n for (let i = 0; i < columnClassNames.length; i++) {\r\n let column = createElement(\"div\", columnClassNames[i], columnValues[i]);\r\n row.appendChild(column);\r\n }\r\n\r\n return row;\r\n}\r\n\r\nfunction createElement(tagName, className, innerHTML = \"\", id = \"\") {\r\n let element = document.createElement(tagName);\r\n element.className = className;\r\n element.innerHTML = innerHTML;\r\n if (id) element.id = id;\r\n return element;\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Simulation Controls\r\n\r\ndocument.addEventListener('DOMContentLoaded', function () {\r\n const simDungeonToggle = document.getElementById('simDungeonToggle');\r\n const playerContainer = document.getElementById('playerCheckBox');\r\n\r\n function addPlayers() {\r\n const player4 = document.createElement('div');\r\n player4.classList.add('form-check');\r\n player4.innerHTML = `\r\n \r\n \r\n `;\r\n\r\n const player5 = document.createElement('div');\r\n player5.classList.add('form-check');\r\n player5.innerHTML = `\r\n \r\n \r\n `;\r\n\r\n playerContainer.appendChild(player4);\r\n playerContainer.appendChild(player5);\r\n }\r\n\r\n function removePlayers() {\r\n const player4 = document.getElementById('player4');\r\n const player5 = document.getElementById('player5');\r\n if (player4) player4.parentElement.remove();\r\n if (player5) player5.parentElement.remove();\r\n }\r\n\r\n function updatePlayerNames() {\r\n const tabLinks = document.querySelectorAll('#playerTab .nav-link');\r\n tabLinks.forEach((tabLink, index) => {\r\n const label = document.querySelector(`label[for=\"player${index + 1}\"]`);\r\n if (label) {\r\n label.textContent = tabLink.textContent.trim();\r\n }\r\n });\r\n }\r\n\r\n function updatePlayersCheckbox(isCheck) {\r\n const boxes = playerContainer.querySelectorAll('.player-checkbox');\r\n boxes.forEach((checkBox) => { checkBox.checked = isCheck });\r\n }\r\n\r\n function updateDifficultySelect(isCheck) {\r\n const difficultySelect = document.getElementById('selectDifficulty');\r\n // disable last four option\r\n if (isCheck && Number(difficultySelect.value) >= 3) {\r\n difficultySelect.value = 0;\r\n }\r\n for (let i = 3; i < difficultySelect.options.length; i++) {\r\n difficultySelect.options[i].disabled = isCheck;\r\n }\r\n }\r\n\r\n simDungeonToggle.addEventListener('change', function () {\r\n if (simDungeonToggle.checked) {\r\n addPlayers();\r\n updatePlayersCheckbox(true);\r\n updateDifficultySelect(true);\r\n } else {\r\n removePlayers();\r\n updatePlayersCheckbox(false);\r\n updateDifficultySelect(false);\r\n }\r\n updatePlayerNames();\r\n });\r\n\r\n document.getElementById('buttonSimulationSetup').addEventListener('click', function () {\r\n updatePlayerNames();\r\n });\r\n});\r\n\r\nfunction onTabChange(event) {\r\n const nextPlayerTabId = event.target.getAttribute('href').substring(7);\r\n savePreviousPlayer(currentPlayerTabId);\r\n updateNextPlayer(nextPlayerTabId);\r\n currentPlayerTabId = nextPlayerTabId;\r\n updateState();\r\n updateUI();\r\n if (Object.keys(currentSimResults).length !== 0) {\r\n showSimulationResult(currentSimResults);\r\n }\r\n\r\n updateContent();\r\n}\r\n\r\ndocument.querySelectorAll('#playerTab .nav-link').forEach(tab => {\r\n tab.addEventListener('shown.bs.tab', onTabChange);\r\n});\r\n\r\nfunction initSimulationControls() {\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n simulationTimeInput.value = 24;\r\n\r\n buttonStartSimulation.addEventListener(\"click\", (event) => {\r\n let invalidElements = document.querySelectorAll(\":invalid\");\r\n if (invalidElements.length > 0) {\r\n invalidElements.forEach((element) => element.reportValidity());\r\n return;\r\n }\r\n savePreviousPlayer(currentPlayerTabId);\r\n\r\n const simDungeonToggle = document.getElementById(\"simDungeonToggle\");\r\n const checkboxes = document.querySelectorAll('.player-checkbox');\r\n selectedPlayers = [];\r\n checkboxes.forEach(checkbox => {\r\n if (checkbox.checked) {\r\n const playerNumber = parseInt(checkbox.id.replace('player', ''));\r\n selectedPlayers.push(playerNumber);\r\n }\r\n });\r\n\r\n if (selectedPlayers.length === 0) {\r\n alert(\"You need to select at least one player to sim.\");\r\n return;\r\n }\r\n // buttonStartSimulation.disabled = true;\r\n buttonStopSimulation.style.display = 'block';\r\n startSimulation(selectedPlayers);\r\n });\r\n\r\n buttonStopSimulation.style.display = 'none';\r\n buttonStopSimulation.addEventListener(\"click\", (event) => {\r\n progressbar.style.width = \"0%\";\r\n progressbar.innerHTML = \"0%\";\r\n if (worker) {\r\n worker.terminate();\r\n }\r\n worker = new Worker(new URL(\"worker.js\", import.meta.url));\r\n\r\n if (multiWorker) {\r\n multiWorker.terminate();\r\n }\r\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n\r\n for (let worker of workerPool) {\r\n worker.worker.terminate();\r\n }\r\n\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n });\r\n}\r\n\r\nfunction startSimulation(selectedPlayers) {\r\n let playersToSim = [];\r\n for (let j = 1; j < 6; j++) {\r\n if (selectedPlayers.includes(j)) {\r\n updateNextPlayer(j);\r\n updateState();\r\n updateUI();\r\n player.hrid = \"player\" + j.toString();\r\n for (let i = 0; i < 3; i++) {\r\n if (food[i] && i < player.combatDetails.combatStats.foodSlots) {\r\n let consumable = new Consumable(food[i], triggerMap[food[i]]);\r\n player.food[i] = consumable;\r\n } else {\r\n player.food[i] = null;\r\n }\r\n\r\n if (drinks[i] && i < player.combatDetails.combatStats.drinkSlots) {\r\n let consumable = new Consumable(drinks[i], triggerMap[drinks[i]]);\r\n player.drinks[i] = consumable;\r\n } else {\r\n player.drinks[i] = null;\r\n }\r\n }\r\n\r\n for (let i = 0; i < 5; i++) {\r\n if (abilities[i] && player.intelligenceLevel >= abilitySlotsLevelRequirementList[i + 1]) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let ability = new Ability(abilities[i], Number(abilityLevelInput.value), triggerMap[abilities[i]]);\r\n player.abilities[i] = ability;\r\n } else {\r\n player.abilities[i] = null;\r\n }\r\n }\r\n\r\n playersToSim.push(structuredClone(player));\r\n }\r\n }\r\n updateNextPlayer(currentPlayerTabId);\r\n updateState();\r\n updateUI();\r\n\r\n let maxPlayerCombatLevel = 1;\r\n for (let player of playersToSim) {\r\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\r\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\r\n }\r\n\r\n for (let player of playersToSim) {\r\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\r\n const maxDebuffOnLevelGap = 0.9;\r\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\r\n\r\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\r\n\r\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\r\n }\r\n else {\r\n player.debuffOnLevelGap = 0;\r\n }\r\n }\r\n\r\n let extra = {};\r\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\r\n extra.comExp = 0;\r\n if (document.getElementById(\"comExpToggle\").checked) {\r\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\r\n }\r\n extra.comDrop = 0;\r\n if (document.getElementById(\"comDropToggle\").checked) {\r\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\r\n }\r\n\r\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\r\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\r\n let simDungeonToggle = document.getElementById(\"simDungeonToggle\");\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let dungeonSelect = document.getElementById(\"selectDungeon\");\r\n let difficultySelect = document.getElementById(\"selectDifficulty\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let simulationTimeLimit = Number(simulationTimeInput.value) * ONE_HOUR;\r\n buttonStopSimulation.style.display = 'block';\r\n if (!simAllZonesToggle.checked && !simAllSoloToggle.checked) {\r\n let zoneHrid = zoneSelect.value;\r\n let difficultyTier = Number(difficultySelect.value);\r\n if (simDungeonToggle.checked) {\r\n zoneHrid = dungeonSelect.value;\r\n }\r\n let workerMessage = {\r\n type: \"start_simulation\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n simStartTime = Date.now();\r\n if (!worker) {\r\n worker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n }\r\n worker.onmessage = onWorkerMessage;\r\n worker.postMessage(workerMessage);\r\n } else {\r\n let targetHrids = {};\r\n\r\n if (simAllZonesToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1 &&\r\n document.getElementById(a.hrid)?.checked\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n if (simAllSoloToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount === 1 &&\r\n document.getElementById(a.hrid)?.checked\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n let simHrids = Object.values(targetHrids)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .map(action => {\r\n let result = [];\r\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\r\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\r\n }\r\n return result;\r\n })\r\n .flat();\r\n\r\n let workerMessage = {\r\n type: \"start_simulation_all_zones\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zones: simHrids,\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra: extra\r\n };\r\n simStartTime = Date.now();\r\n if (!multiWorker) {\r\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n }\r\n multiWorker.onmessage = onMultiWorkerMessage;\r\n multiWorker.postMessage(workerMessage);\r\n }\r\n}\r\n\r\nfunction parsePlayerJson(playerJson, hrid) {\r\n let playerData = {\r\n hrid: hrid,\r\n food: [],\r\n drinks: [],\r\n abilities: [],\r\n ...playerJson.player,\r\n houseRooms: playerJson.houseRooms,\r\n };\r\n playerData.equipment = {};\r\n const triggerMap = playerJson.triggerMap;\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"main_hand\", \"two_hand\", \"charm\"].forEach((type) => {\r\n let currentEquipment = playerJson.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment){\r\n playerData.equipment[`/equipment_types/${type}`] = new Equipment(currentEquipment.itemHrid, currentEquipment.enhancementLevel);\r\n }\r\n });\r\n\r\n for (const foodHrid of playerJson.food[\"/action_types/combat\"]) {\r\n if (foodHrid.itemHrid === \"\") continue;\r\n const food = new Consumable(foodHrid.itemHrid, triggerMap[foodHrid.itemHrid]);\r\n playerData.food.push(food);\r\n }\r\n for (const drinkHrid of playerJson.drinks[\"/action_types/combat\"]) {\r\n if (drinkHrid.itemHrid === \"\") continue;\r\n const drink = new Consumable(drinkHrid.itemHrid, triggerMap[drinkHrid.itemHrid]);\r\n playerData.drinks.push(drink);\r\n }\r\n for (const ability of playerJson.abilities) {\r\n if (ability.abilityHrid === \"\") continue;\r\n const abilityLevel = Number(ability.level);\r\n const abilityHrid = ability.abilityHrid;\r\n if (abilityLevel > 0) {\r\n const abilityObj = new Ability(abilityHrid, abilityLevel, triggerMap[abilityHrid]);\r\n playerData.abilities.push(abilityObj);\r\n }\r\n }\r\n const player = Player.createFromDTO(playerData)\r\n player.updateCombatDetails();\r\n player.houseRooms = playerJson.houseRooms;\r\n player.achievements = playerJson.achievements ?? {};\r\n return player;\r\n}\r\n// read JSON file to simulate\r\ndocument.getElementById(\"buttonUploadJSONSimulate\").addEventListener(\"click\", (event) => {\r\n let extra = {};\r\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\r\n extra.comExp = 0;\r\n if (document.getElementById(\"comExpToggle\").checked) {\r\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\r\n }\r\n extra.comDrop = 0;\r\n if (document.getElementById(\"comDropToggle\").checked) {\r\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\r\n }\r\n\r\n let fileInput = document.getElementById(\"inputUploadJSONSimulation\");\r\n let file = fileInput.files[0];\r\n if (!file) {\r\n alert(\"Please select a file to upload.\");\r\n return;\r\n }\r\n\r\n let reader = new FileReader();\r\n reader.onload = function (event) {\r\n let fileContent = event.target.result;\r\n const jsonDataList = JSON.parse(fileContent);\r\n try {\r\n const simDataList = [];\r\n for (const key in jsonDataList) {\r\n if (jsonDataList[key].cases) {\r\n const cases = getProductCases(jsonDataList[key], jsonDataList[key].cases);\r\n simDataList.push(...cases);\r\n } else {\r\n simDataList.push(jsonDataList[key]);\r\n }\r\n }\r\n for (const key in simDataList) {\r\n const jsonData = simDataList[key];\r\n if (!jsonData || !jsonData.zone || !jsonData.players) {\r\n alert(\"Invalid JSON file format. Please ensure it contains a 'simulationResult' property.\");\r\n return;\r\n }\r\n const playersToSim = Object.values(jsonData.players).map(\r\n (player, index) => parsePlayerJson(player, `player${index + 1}`)\r\n );\r\n\r\n let maxPlayerCombatLevel = 1;\r\n for (let player of playersToSim) {\r\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\r\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\r\n }\r\n\r\n for (let player of playersToSim) {\r\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\r\n const maxDebuffOnLevelGap = 0.9;\r\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\r\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\r\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\r\n }\r\n else {\r\n player.debuffOnLevelGap = 0;\r\n }\r\n }\r\n\r\n const simulationTimeLimit = (jsonData.simulationTimeLimit || 24) * ONE_HOUR;\r\n const simName = jsonData.name || `Json ${key}`;\r\n const zoneHrid = jsonData.zone;\r\n if (zoneHrid === \"all\") {\r\n let targetHrids = {};\r\n\r\n if (simAllZonesToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n let simHrids = Object.values(targetHrids)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .map(action => {\r\n let result = [];\r\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\r\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\r\n }\r\n return result;\r\n })\r\n .flat();\r\n\r\n let workerMessage = {\r\n simulationName: simName,\r\n type: \"start_simulation_all_zones\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zones: simHrids,\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \r\n worker.onmessage = mainWorkerOnMessage;\r\n worker.postMessage(workerMessage);\r\n customAlert(\"Simulation task Created\", \"info\")\r\n workerPool.push({\r\n workerId: workerMessage.workerId,\r\n worker: worker,\r\n });\r\n } else {\r\n let difficultyTier = jsonData.difficultyTier || 0;\r\n let workerMessage = {\r\n simulationName: simName,\r\n type: \"start_simulation\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \r\n worker.onmessage = mainWorkerOnMessage;\r\n worker.postMessage(workerMessage);\r\n customAlert(\"Simulation task Created\", \"info\")\r\n workerPool.push({\r\n workerId: workerMessage.workerId,\r\n worker: worker,\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n // alert(\"Error parsing JSON file: \" + error.message);\r\n customAlert(\"Error parsing JSON file: \" + error.message, \"danger\");\r\n }\r\n }\r\n reader.readAsText(file);\r\n});\r\n\r\n\r\n// #endregion\r\n\r\n// #region WipeEvents\r\n\r\nfunction renderWipeEvents(simResult) {\r\n const selector = document.getElementById('wipeEventSelector');\r\n const logsContainer = document.getElementById('wipeLogsContainer');\r\n const waveBadge = document.getElementById('wipeWaveBadge');\r\n const timeInfo = document.getElementById('wipeTimeInfo');\r\n\r\n selector.innerHTML = '';\r\n logsContainer.innerHTML = '';\r\n\r\n if (!simResult.wipeEvents || simResult.wipeEvents.length === 0) {\r\n selector.innerHTML = ``;\r\n logsContainer.innerHTML = `
No Wipe Events Detected
`;\r\n waveBadge.textContent = '';\r\n timeInfo.textContent = '';\r\n return;\r\n }\r\n\r\n simResult.wipeEvents.forEach((event, index) => {\r\n const wave = event.wave || '?';\r\n // const time = (event.simulationTime / 1e9).toFixed(2);\r\n // const timestamp = new Date(event.timestamp).toLocaleTimeString();\r\n\r\n const option = document.createElement('option');\r\n option.value = index;\r\n option.textContent = `#${index + 1} - 波次: ${wave}`;\r\n selector.appendChild(option);\r\n });\r\n\r\n selector.value = 0;\r\n renderSelectedWipeEvent(0, simResult);\r\n\r\n selector.addEventListener('change', () => {\r\n renderSelectedWipeEvent(selector.value, simResult);\r\n });\r\n}\r\n\r\n// 渲染选中的团灭事件\r\nfunction renderSelectedWipeEvent(index, simResult) {\r\n const logsContainer = document.getElementById('wipeLogsContainer');\r\n const waveBadge = document.getElementById('wipeWaveBadge');\r\n const timeInfo = document.getElementById('wipeTimeInfo');\r\n\r\n logsContainer.innerHTML = '';\r\n\r\n if (index < 0 || index >= simResult.wipeEvents.length) {\r\n logsContainer.innerHTML = `
No Wipe Events
`;\r\n waveBadge.textContent = '';\r\n timeInfo.textContent = '';\r\n return;\r\n }\r\n\r\n const wipeEvent = simResult.wipeEvents[index];\r\n const wave = wipeEvent.wave || '?';\r\n const time = (wipeEvent.simulationTime / 1e9).toFixed(2);\r\n const timestamp = new Date(wipeEvent.timestamp).toLocaleString();\r\n\r\n waveBadge.textContent = `波次: ${wave}`;\r\n timeInfo.textContent = `模拟时间: ${time}s | 记录时间: ${timestamp}`;\r\n\r\n const logsByTime = groupLogsByTime(wipeEvent.logs);\r\n\r\n const baseTime = logsByTime.length > 0 ? logsByTime[0].time : 0;\r\n\r\n logsByTime.forEach(group => {\r\n const timeGroupElement = document.createElement('div');\r\n timeGroupElement.className = 'log-time-group';\r\n\r\n const relativeTime = (group.time - baseTime) / 1e9;\r\n\r\n // 时间标题\r\n const timeHeader = document.createElement('div');\r\n timeHeader.className = 'log-time-header';\r\n timeHeader.textContent = `[${relativeTime.toFixed(2)}s] [Wave#${group.wave}]`;\r\n timeGroupElement.appendChild(timeHeader);\r\n\r\n // 事件列表\r\n const eventsList = document.createElement('div');\r\n eventsList.className = 'log-events';\r\n\r\n const damagedPlayers = new Set();\r\n\r\n group.logs.forEach(log => {\r\n const eventElement = document.createElement('div');\r\n eventElement.className = 'log-event';\r\n\r\n damagedPlayers.add(log.target);\r\n\r\n const sourceSpan = document.createElement('span');\r\n sourceSpan.className = 'log-source';\r\n if (log.ability === \"damageOverTime\") {\r\n sourceSpan.textContent = log.target;\r\n } else if(log.source == 'UNKNOWN_SOURCE') {\r\n sourceSpan.textContent = 'UNKNOWN';\r\n } else {\r\n sourceSpan.setAttribute('data-i18n', `monsterNames.${log.source}`);\r\n sourceSpan.textContent = log.source;\r\n }\r\n\r\n const castSpan = document.createElement('span');\r\n castSpan.className = 'log-cast';\r\n castSpan.setAttribute('data-i18n', `common:cast`);\r\n castSpan.textContent = ' cast ';\r\n\r\n const abilitySpan = document.createElement('span');\r\n abilitySpan.className = 'log-ability';\r\n if (log.ability === \"autoAttack\") {\r\n abilitySpan.setAttribute('data-i18n', 'combatUnit.autoAttack');\r\n abilitySpan.textContent = 'Auto Attack';\r\n } else if (log.ability === \"physicalThorns\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.physicalThorns`);\r\n abilitySpan.textContent = 'Physical Thorns';\r\n } else if (log.ability === \"elementalThorns\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.elementalThorns`);\r\n abilitySpan.textContent = 'Elemental Thorns';\r\n } else if (log.ability === \"retaliation\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.retaliation`);\r\n abilitySpan.textContent = 'Retaliation';\r\n } else if (log.ability === \"damageOverTime\") {\r\n abilitySpan.setAttribute('data-i18n', `common:simulationResults.damageOverTime`);\r\n abilitySpan.textContent = 'Damage Over Time';\r\n } else {\r\n abilitySpan.setAttribute('data-i18n', `abilityNames.${log.ability}`);\r\n abilitySpan.textContent = log.ability;\r\n }\r\n\r\n const toSpan = document.createElement('span');\r\n toSpan.className = 'log-to';\r\n toSpan.setAttribute('data-i18n', `common:to`);\r\n toSpan.textContent = ' to ';\r\n\r\n const targetSpan = document.createElement('span');\r\n targetSpan.className = 'log-target';\r\n targetSpan.textContent = log.target;\r\n\r\n const dealDamageSpan = document.createElement('span');\r\n dealDamageSpan.className = 'log-deal-damage';\r\n dealDamageSpan.setAttribute('data-i18n', `common:dealDamage`);\r\n dealDamageSpan.textContent = ' deal damage ';\r\n\r\n const damageDoneSpan = document.createElement('span');\r\n damageDoneSpan.className = 'log-damage-done';\r\n damageDoneSpan.textContent = log.damage;\r\n if (log.isCrit) {\r\n damageDoneSpan.style.fontWeight = 'bold';\r\n damageDoneSpan.textContent += '!!!';\r\n }\r\n\r\n eventElement.appendChild(sourceSpan);\r\n eventElement.appendChild(castSpan);\r\n eventElement.appendChild(abilitySpan);\r\n eventElement.appendChild(toSpan);\r\n eventElement.appendChild(targetSpan);\r\n eventElement.appendChild(dealDamageSpan);\r\n eventElement.appendChild(damageDoneSpan);\r\n eventElement.appendChild(document.createTextNode(` , HP ${log.beforeHp} → ${log.afterHp}`));\r\n\r\n eventsList.appendChild(eventElement);\r\n });\r\n\r\n timeGroupElement.appendChild(eventsList);\r\n\r\n const lastLog = group.logs[group.logs.length - 1];\r\n const playersHpElement = document.createElement('div');\r\n\r\n const playerHpTitle = document.createElement('span');\r\n playerHpTitle.className = 'log-players-hp';\r\n playerHpTitle.setAttribute('data-i18n', `common:playersHp`);\r\n playerHpTitle.textContent = 'Players HP: ';\r\n playersHpElement.appendChild(playerHpTitle);\r\n\r\n lastLog.playersHp.forEach((player, idx) => {\r\n const playerElement = document.createElement('span');\r\n playerElement.className = 'log-player-hp';\r\n playerElement.textContent = `${player.hrid}: ${player.current}/${player.max}`;\r\n\r\n if (player.current <= 0) {\r\n playerElement.style.color = darkModeToggle.checked ? '#FF6347' : '#CC0000';\r\n } else if (damagedPlayers.has(player.hrid)) {\r\n playerElement.style.color = darkModeToggle.checked ? '#00BFFF' : '#007BFF';\r\n }\r\n\r\n if (idx > 0) {\r\n playersHpElement.appendChild(document.createTextNode(' | '));\r\n }\r\n playersHpElement.appendChild(playerElement);\r\n });\r\n const spacer = document.createElement('div');\r\n spacer.style.height = '15px';\r\n logsContainer.appendChild(spacer);\r\n timeGroupElement.appendChild(playersHpElement);\r\n logsContainer.appendChild(timeGroupElement);\r\n });\r\n\r\n // 更新汉化\r\n updateContent()\r\n}\r\n\r\n// 按时间分组日志\r\nfunction groupLogsByTime(logs) {\r\n const groups = [];\r\n let currentGroup = null;\r\n\r\n logs.forEach(log => {\r\n if (!currentGroup || currentGroup.time !== log.time) {\r\n currentGroup = {\r\n time: log.time,\r\n wave: log.wave,\r\n logs: [log]\r\n };\r\n groups.push(currentGroup);\r\n } else {\r\n currentGroup.logs.push(log);\r\n }\r\n });\r\n\r\n groups.forEach(group => {\r\n let hpMap = {};\r\n if (group.logs.length > 0) {\r\n group.logs[0].playersHp.forEach(p => {\r\n hpMap[p.hrid] = { current: p.current, max: p.max };\r\n });\r\n }\r\n group.logs.forEach(log => {\r\n if (hpMap[log.target]) {\r\n hpMap[log.target].current = log.afterHp;\r\n }\r\n });\r\n group.logs.forEach(log => {\r\n log.playersHp = Object.entries(hpMap).map(([hrid, val]) => ({\r\n hrid,\r\n current: val.current,\r\n max: val.max\r\n }));\r\n });\r\n });\r\n\r\n return groups;\r\n}\r\n\r\n// #endregion\r\n\r\n\r\n// #region Equipment Sets\r\n\r\nfunction initEquipmentSetsModal() {\r\n let equipmentSetsModal = document.getElementById(\"equipmentSetsModal\");\r\n equipmentSetsModal.addEventListener(\"show.bs.modal\", equipmentSetsModalShownHandler);\r\n\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n equipmentSetNameInput.addEventListener(\"input\", (event) => equipmentSetNameChangedHandler(event));\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.addEventListener(\"click\", createNewEquipmentSetHandler);\r\n}\r\n\r\nfunction equipmentSetsModalShownHandler() {\r\n resetNewEquipmentSetControls();\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction resetNewEquipmentSetControls() {\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n equipmentSetNameInput.value = \"\";\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.disabled = true;\r\n}\r\n\r\nfunction updateEquipmentSetList() {\r\n let newChildren = [];\r\n let equipmentSets = loadEquipmentSets();\r\n\r\n for (const equipmentSetName of Object.keys(equipmentSets)) {\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let nameCol = createElement(\"div\", \"col align-self-center\", equipmentSetName);\r\n row.appendChild(nameCol);\r\n\r\n let loadButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let loadButton = createElement(\"button\", \"btn btn-primary\", \"Load\");\r\n loadButton.setAttribute(\"data-i18n\", \"common:controls.load\");\r\n loadButton.setAttribute(\"type\", \"button\");\r\n loadButton.addEventListener(\"click\", (_) => loadEquipmentSetHandler(equipmentSetName));\r\n loadButtonCol.appendChild(loadButton);\r\n row.appendChild(loadButtonCol);\r\n\r\n let saveButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let saveButton = createElement(\"button\", \"btn btn-primary\", \"Save\");\r\n saveButton.setAttribute(\"data-i18n\", \"common:controls.save\");\r\n saveButton.setAttribute(\"type\", \"button\");\r\n saveButton.addEventListener(\"click\", (_) => updateEquipmentSetHandler(equipmentSetName));\r\n saveButtonCol.appendChild(saveButton);\r\n row.appendChild(saveButtonCol);\r\n\r\n let deleteButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let deleteButton = createElement(\"button\", \"btn btn-danger\", \"Delete\");\r\n deleteButton.setAttribute(\"data-i18n\", \"common:controls.delete\");\r\n deleteButton.setAttribute(\"type\", \"button\");\r\n deleteButton.addEventListener(\"click\", (_) => deleteEquipmentSetHandler(equipmentSetName));\r\n deleteButtonCol.appendChild(deleteButton);\r\n row.appendChild(deleteButtonCol);\r\n\r\n newChildren.push(row);\r\n }\r\n\r\n let equipmentSetList = document.getElementById(\"equipmentSetList\");\r\n equipmentSetList.replaceChildren(...newChildren);\r\n\r\n updateContent();\r\n}\r\n\r\nfunction equipmentSetNameChangedHandler(event) {\r\n let invalid = false;\r\n\r\n if (event.target.value.length == 0) {\r\n invalid = true;\r\n }\r\n\r\n let equipmentSets = loadEquipmentSets();\r\n if (equipmentSets[event.target.value]) {\r\n invalid = true;\r\n }\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.disabled = invalid;\r\n}\r\n\r\nfunction createNewEquipmentSetHandler() {\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n let equipmentSetName = equipmentSetNameInput.value;\r\n\r\n let equipmentSet = getEquipmentSetFromUI();\r\n let equipmentSets = loadEquipmentSets();\r\n equipmentSets[equipmentSetName] = equipmentSet;\r\n saveEquipmentSets(equipmentSets);\r\n\r\n resetNewEquipmentSetControls();\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction loadEquipmentSetHandler(name) {\r\n let equipmentSets = loadEquipmentSets();\r\n loadEquipmentSetIntoUI(equipmentSets[name]);\r\n}\r\n\r\nfunction updateEquipmentSetHandler(name) {\r\n let equipmentSet = getEquipmentSetFromUI();\r\n let equipmentSets = loadEquipmentSets();\r\n equipmentSets[name] = equipmentSet;\r\n saveEquipmentSets(equipmentSets);\r\n}\r\n\r\nfunction deleteEquipmentSetHandler(name) {\r\n let equipmentSets = loadEquipmentSets();\r\n delete equipmentSets[name];\r\n saveEquipmentSets(equipmentSets);\r\n\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction loadEquipmentSets() {\r\n return JSON.parse(localStorage.getItem(\"equipmentSets\")) ?? {};\r\n}\r\n\r\nfunction saveEquipmentSets(equipmentSets) {\r\n localStorage.setItem(\"equipmentSets\", JSON.stringify(equipmentSets));\r\n}\r\n\r\nfunction getEquipmentSetFromUI() {\r\n let equipmentSet = {\r\n levels: {},\r\n equipment: {},\r\n food: {},\r\n drinks: {},\r\n abilities: {},\r\n triggerMap: {},\r\n houseRooms: {},\r\n achievements: {},\r\n };\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n equipmentSet.levels[skill] = Number(levelInput.value);\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n\r\n equipmentSet.equipment[type] = {\r\n equipment: equipmentSelect.value,\r\n enhancementLevel: Number(enhancementLevelInput.value),\r\n };\r\n });\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n equipmentSet.food[i] = foodSelect.value;\r\n }\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n equipmentSet.drinks[i] = drinkSelect.value;\r\n }\r\n\r\n for (let i = 0; i < 5; i++) {\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n equipmentSet.abilities[i] = {\r\n ability: abilitySelect.value,\r\n level: Number(abilityLevelInput.value),\r\n };\r\n }\r\n\r\n equipmentSet.triggerMap = triggerMap;\r\n\r\n equipmentSet.houseRooms = player.houseRooms;\r\n equipmentSet.achievements = player.achievements;\r\n\r\n return equipmentSet;\r\n}\r\n\r\nfunction fixTriggerMap(triggerMap) {\r\n let delKeys = []\r\n for (const key of Object.keys(triggerMap)) {\r\n let err = false;\r\n if (null == triggerMap[key]) {\r\n triggerMap[key] = [];\r\n }\r\n for (const trigger of triggerMap[key]) {\r\n if (!combatTriggerConditionDetailMap[trigger.conditionHrid]) {\r\n err = true;\r\n break;\r\n }\r\n }\r\n if (err) {\r\n delKeys.push(key);\r\n }\r\n }\r\n for (const key of delKeys) {\r\n delete triggerMap[key];\r\n }\r\n}\r\n\r\nfunction loadEquipmentSetIntoUI(equipmentSet) {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !equipmentSet.levels[\"meleeLevel\"] && equipmentSet.levels[\"powerLevel\"]) {\r\n equipmentSet.levels[\"meleeLevel\"] = equipmentSet.levels[\"powerLevel\"];\r\n }\r\n levelInput.value = equipmentSet.levels[skill] ?? 1;\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n\r\n let currentEquipment = equipmentSet.equipment[type];\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.equipment;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n foodSelect.value = equipmentSet.food[i];\r\n }\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n drinkSelect.value = equipmentSet.drinks[i].replace(\"power\", \"melee\");\r\n }\r\n\r\n let hasSpecial = false;\r\n if (equipmentSet.abilities && Object.keys(equipmentSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n equipmentSet.abilities[i].ability == \"/abilities/aqua_aura\" ||\r\n equipmentSet.abilities[i].ability == \"/abilities/flame_aura\" ||\r\n equipmentSet.abilities[i].ability == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n equipmentSet.abilities[i].ability = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (equipmentSet.abilities[i].ability == \"/abilities/arcane_reflection\") {\r\n equipmentSet.abilities[i].ability = \"/abilities/retribution\";\r\n }\r\n\r\n abilitySelect.value = equipmentSet.abilities[i].ability;\r\n abilityLevelInput.value = equipmentSet.abilities[i].level;\r\n }\r\n\r\n triggerMap = equipmentSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n\r\n if (equipmentSet.houseRooms) {\r\n for (const room in equipmentSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (equipmentSet.houseRooms[room]) {\r\n field.value = equipmentSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = equipmentSet.houseRooms;\r\n } else {\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n\r\n if (equipmentSet.achievements) {\r\n for (const achievement in equipmentSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (equipmentSet.achievements[achievement]) {\r\n field.checked = true;\r\n } else {\r\n field.checked = false;\r\n }\r\n player.achievements[achievement] = field.checked;\r\n }\r\n } else {\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n refreshAchievementStatics();\r\n\r\n updateState();\r\n updateUI();\r\n\r\n updateContent();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Error Handling\r\n\r\nfunction initErrorHandling() {\r\n window.addEventListener(\"error\", (event) => {\r\n showErrorModal(event.message);\r\n });\r\n\r\n let copyErrorButton = document.getElementById(\"buttonCopyError\");\r\n copyErrorButton.addEventListener(\"click\", (event) => {\r\n let errorInput = document.getElementById(\"inputError\");\r\n navigator.clipboard.writeText(errorInput.value);\r\n });\r\n}\r\n\r\nfunction initImportExportModal() {\r\n let exportSetButton = document.getElementById(\"buttonExportSet\");\r\n exportSetButton.addEventListener(\"click\", (event) => {\r\n savePreviousPlayer(currentPlayerTabId);\r\n const activeTab = document.querySelector('#importTab .nav-link.active');\r\n if (activeTab.id === 'group-combat-tab') {\r\n doGroupExport();\r\n } else if (activeTab.id === 'solo-tab') {\r\n doSoloExport();\r\n }\r\n });\r\n\r\n let importSetButton = document.getElementById(\"buttonImportSet\");\r\n importSetButton.addEventListener(\"click\", (event) => {\r\n const activeTab = document.querySelector('#importTab .nav-link.active');\r\n if (activeTab.id === 'group-combat-tab') {\r\n doGroupImport();\r\n } else if (activeTab.id === 'solo-tab') {\r\n doSoloImport();\r\n }\r\n updateState();\r\n updateUI();\r\n resetImportInputs();\r\n });\r\n}\r\n\r\nfunction resetImportInputs() {\r\n document.getElementById('inputSetGroupCombatAll').value = '';\r\n document.getElementById('inputSetGroupCombatplayer1').value = '';\r\n document.getElementById('inputSetGroupCombatplayer2').value = '';\r\n document.getElementById('inputSetGroupCombatplayer3').value = '';\r\n document.getElementById('inputSetGroupCombatplayer4').value = '';\r\n document.getElementById('inputSetGroupCombatplayer5').value = '';\r\n document.getElementById('inputSetSolo').value = '';\r\n}\r\n\r\nfunction doGroupExport() {\r\n try {\r\n navigator.clipboard.writeText(JSON.stringify(playerDataMap)).then(() => alert(\"Current Group has been copied to clipboard.\"));\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction doSoloExport() {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let equipmentArray = [];\r\n for (const item in player.equipment) {\r\n if (player.equipment[item] != null) {\r\n equipmentArray.push({\r\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\r\n \"itemHrid\": player.equipment[item].hrid,\r\n \"enhancementLevel\": player.equipment[item].enhancementLevel\r\n });\r\n }\r\n }\r\n let playerArray = {\r\n \"attackLevel\": player.attackLevel,\r\n \"magicLevel\": player.magicLevel,\r\n \"meleeLevel\": player.meleeLevel,\r\n \"rangedLevel\": player.rangedLevel,\r\n \"defenseLevel\": player.defenseLevel,\r\n \"staminaLevel\": player.staminaLevel,\r\n \"intelligenceLevel\": player.intelligenceLevel,\r\n \"equipment\": equipmentArray\r\n };\r\n let abilitiesArray = [];\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let abilityName = document.getElementById(\"selectAbility_\" + i);\r\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\r\n }\r\n let drinksArray = [];\r\n for (let i = 0; i < drinks?.length; i++) {\r\n drinksArray.push({ \"itemHrid\": drinks[i] });\r\n }\r\n let foodArray = [];\r\n for (let i = 0; i < food?.length; i++) {\r\n foodArray.push({ \"itemHrid\": food[i] });\r\n }\r\n let state = {\r\n player: playerArray,\r\n food: { \"/action_types/combat\": foodArray },\r\n drinks: { \"/action_types/combat\": drinksArray },\r\n abilities: abilitiesArray,\r\n triggerMap: triggerMap,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n houseRooms: player.houseRooms,\r\n achievements: player.achievements\r\n };\r\n try {\r\n navigator.clipboard.writeText(JSON.stringify(state)).then(() => alert(\"Current set has been copied to clipboard.\"));\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction setPlayerData(playerId, inputElementId) {\r\n const inputElement = document.getElementById(inputElementId);\r\n const value = inputElement ? inputElement.value.trim() : \"\";\r\n\r\n // Only set the value in the map if it's not null, undefined, or empty\r\n if (value) {\r\n playerDataMap[playerId] = value;\r\n return true;\r\n }\r\n return false;\r\n}\r\n\r\nfunction doGroupImport() {\r\n let needUpdateCurrentTab = false;\r\n const value = document.getElementById(\"inputSetGroupCombatAll\")?.value || \"\";\r\n if (!value.trim()) {\r\n for (let i of ['1', '2', '3', '4', '5']) {\r\n if (setPlayerData(i, \"inputSetGroupCombatplayer\" + i) && currentPlayerTabId == i) {\r\n needUpdateCurrentTab = true;\r\n }\r\n }\r\n } else {\r\n playerDataMap = JSON.parse(value);\r\n needUpdateCurrentTab = true;\r\n }\r\n\r\n if (needUpdateCurrentTab) {\r\n updateNextPlayer(currentPlayerTabId);\r\n }\r\n}\r\n\r\nfunction doSoloImport() {\r\n let importSet = document.getElementById(\"inputSetSolo\").value;\r\n importSet = JSON.parse(importSet);\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\r\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\r\n }\r\n levelInput.value = importSet.player[skill + \"Level\"];\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.itemHrid;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\r\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\r\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\r\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\r\n if (mainhandWeapon !== undefined) {\r\n weaponSelect.value = mainhandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\r\n } else if (twohandWeapon !== undefined) {\r\n weaponSelect.value = twohandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\r\n } else {\r\n weaponSelect.value = \"\";\r\n weaponEnhancementLevelInput.value = 0;\r\n }\r\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\r\n importSet.food = importSet.food[\"/action_types/combat\"];\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n if (importSet.drinks[i] != null) {\r\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\r\n } else {\r\n drinkSelect.value = \"\";\r\n }\r\n if (importSet.food[i] != null) {\r\n foodSelect.value = importSet.food[i].itemHrid;\r\n } else {\r\n foodSelect.value = \"\";\r\n }\r\n }\r\n\r\n let hasSpecial = false;\r\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\r\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\r\n }\r\n\r\n if (importSet.abilities[i] != null) {\r\n abilitySelect.value = importSet.abilities[i].abilityHrid;\r\n abilityLevelInput.value = String(importSet.abilities[i].level);\r\n } else {\r\n abilitySelect.value = \"\";\r\n abilityLevelInput.value = \"1\";\r\n }\r\n }\r\n\r\n if (importSet.triggerMap) {\r\n triggerMap = importSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n }\r\n\r\n if (importSet.houseRooms) {\r\n for (const room in importSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (importSet.houseRooms[room]) {\r\n field.value = importSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = importSet.houseRooms;\r\n } else {\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n\r\n if (importSet.achievements) {\r\n for (const achievement in importSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (importSet.achievements[achievement]) {\r\n field.checked = true;\r\n } else {\r\n field.checked = false;\r\n }\r\n player.achievements[achievement] = field.checked;\r\n }\r\n } else {\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n refreshAchievementStatics();\r\n\r\n if (\"zone\" in importSet) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n zoneSelect.value = importSet[\"zone\"];\r\n }\r\n\r\n if (\"simulationTime\" in importSet) {\r\n let simulationDuration = document.getElementById(\"inputSimulationTime\");\r\n simulationDuration.value = importSet[\"simulationTime\"];\r\n }\r\n}\r\n\r\nfunction savePreviousPlayer(playerId) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let equipmentArray = [];\r\n for (const item in player.equipment) {\r\n if (player.equipment[item] != null) {\r\n equipmentArray.push({\r\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\r\n \"itemHrid\": player.equipment[item].hrid,\r\n \"enhancementLevel\": player.equipment[item].enhancementLevel\r\n });\r\n }\r\n }\r\n let playerArray = {\r\n \"attackLevel\": player.attackLevel,\r\n \"magicLevel\": player.magicLevel,\r\n \"meleeLevel\": player.meleeLevel,\r\n \"rangedLevel\": player.rangedLevel,\r\n \"defenseLevel\": player.defenseLevel,\r\n \"staminaLevel\": player.staminaLevel,\r\n \"intelligenceLevel\": player.intelligenceLevel,\r\n \"equipment\": equipmentArray\r\n };\r\n let abilitiesArray = [];\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let abilityName = document.getElementById(\"selectAbility_\" + i);\r\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\r\n }\r\n let drinksArray = [];\r\n for (let i = 0; i < drinks?.length; i++) {\r\n drinksArray.push({ \"itemHrid\": drinks[i] });\r\n }\r\n let foodArray = [];\r\n for (let i = 0; i < food?.length; i++) {\r\n foodArray.push({ \"itemHrid\": food[i] });\r\n }\r\n let state = {\r\n player: playerArray,\r\n food: { \"/action_types/combat\": foodArray },\r\n drinks: { \"/action_types/combat\": drinksArray },\r\n abilities: abilitiesArray,\r\n triggerMap: triggerMap,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n houseRooms: player.houseRooms,\r\n achievements: player.achievements\r\n };\r\n try {\r\n playerDataMap[playerId] = JSON.stringify(state);\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction updateNextPlayer(currentPlayerNumber) {\r\n let playerImportData = playerDataMap[currentPlayerNumber];\r\n let importSet = JSON.parse(playerImportData);\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\r\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\r\n }\r\n levelInput.value = importSet.player[skill + \"Level\"];\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.itemHrid;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\r\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\r\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\r\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\r\n if (mainhandWeapon !== undefined) {\r\n weaponSelect.value = mainhandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\r\n } else if (twohandWeapon !== undefined) {\r\n weaponSelect.value = twohandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\r\n } else {\r\n weaponSelect.value = \"\";\r\n weaponEnhancementLevelInput.value = 0;\r\n }\r\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\r\n importSet.food = importSet.food[\"/action_types/combat\"];\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n if (importSet.drinks[i] != null) {\r\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\r\n } else {\r\n drinkSelect.value = \"\";\r\n }\r\n if (importSet.food[i] != null) {\r\n foodSelect.value = importSet.food[i].itemHrid;\r\n } else {\r\n foodSelect.value = \"\";\r\n }\r\n }\r\n\r\n let hasSpecial = false;\r\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\r\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\r\n }\r\n\r\n if (importSet.abilities[i] != null) {\r\n abilitySelect.value = importSet.abilities[i].abilityHrid;\r\n abilityLevelInput.value = String(importSet.abilities[i].level);\r\n } else {\r\n abilitySelect.value = \"\";\r\n abilityLevelInput.value = \"1\";\r\n }\r\n }\r\n\r\n if (importSet.triggerMap) {\r\n triggerMap = importSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n }\r\n\r\n { // reset all houseRooms\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n if (importSet.houseRooms) {\r\n for (const room in importSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (importSet.houseRooms[room]) {\r\n field.value = importSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = importSet.houseRooms;\r\n }\r\n\r\n { // reset all achievements\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n if (importSet.achievements) {\r\n for (const achievement in importSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (importSet.achievements[achievement]) {\r\n field.checked = true;\r\n player.achievements[achievement] = true;\r\n } else {\r\n field.checked = false;\r\n player.achievements[achievement] = false;\r\n }\r\n }\r\n }\r\n refreshAchievementStatics();\r\n}\r\n\r\nfunction showErrorModal(error) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n\r\n let state = {\r\n error: error,\r\n player: player,\r\n food: food,\r\n drinks: drinks,\r\n abilities: abilities,\r\n triggerMap: triggerMap,\r\n modalTriggers: modalTriggers,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n };\r\n\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n state[\"abilityLevel\" + i] = abilityLevelInput.value;\r\n }\r\n\r\n let errorInput = document.getElementById(\"inputError\");\r\n errorInput.value = JSON.stringify(state);\r\n\r\n let errorModal = new bootstrap.Modal(document.getElementById(\"errorModal\"));\r\n errorModal.show();\r\n}\r\n\r\nwindow.prices;\r\n\r\nasync function fetchPrices() {\r\n let response = null;\r\n try {\r\n response = await fetch('https://www.milkywayidle.com/game_data/marketplace.json'\r\n , {\r\n mode: 'cors'\r\n }\r\n );\r\n if (!response.ok) {\r\n console.log('Error fetching prices');\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n\r\n if (response == null) {\r\n try {\r\n response = await fetch('https://www.milkywayidlecn.com/game_data/marketplace.json'\r\n , {\r\n mode: 'cors'\r\n }\r\n );\r\n if (!response.ok) {\r\n console.log('Error fetching prices');\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n }\r\n\r\n if (!response || !response.ok) {\r\n return;\r\n }\r\n\r\n try {\r\n\r\n let btn = document.querySelector('#buttonGetPrices');\r\n btn.style.backgroundColor = 'green';\r\n\r\n const pricesJson = await response.json();\r\n\r\n const priceTmp = pricesJson['marketData'];\r\n window.prices = {};\r\n for (const item in itemDetailMap) {\r\n const hrid = itemDetailMap[item].hrid;\r\n if (hrid in priceTmp) {\r\n window.prices[hrid] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (priceTmp[hrid]['0']) {\r\n window.prices[hrid].ask = priceTmp[hrid]['0'].a;\r\n window.prices[hrid].bid = priceTmp[hrid]['0'].b;\r\n }\r\n }\r\n } \r\n\r\n window.prices[\"/items/coin\"] = { \"ask\": 1, \"bid\": 1, \"vendor\": 1 };\r\n\r\n window.prices[\"/items/small_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n window.prices[\"/items/medium_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n window.prices[\"/items/large_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n}\r\n\r\ndocument.getElementById(\"buttonGetPrices\").onclick = async () => {\r\n await fetchPrices();\r\n};\r\n\r\ndocument.addEventListener(\"input\", (e) => {\r\n let element = e.target;\r\n if (element.tagName == \"TD\" && element.parentNode.parentNode.parentNode.classList.value.includes('profit-table')) {\r\n let tableId = element.parentNode.parentNode.parentNode.id;\r\n let row = element.parentNode.querySelectorAll('td');\r\n let item = row[0].getAttribute('data-i18n').split('.')[1];\r\n let newPrice = element.innerText;\r\n\r\n let revenueSetting = document.getElementById('selectPrices_drops').value;\r\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\r\n\r\n let expensesDifference = 0;\r\n let revenueDifference = 0;\r\n let noRngRevenueDifference = 0;\r\n\r\n if (tableId == 'expensesTable') {\r\n expensesDifference = updateTable('expensesTable', item, newPrice);\r\n if (revenueSetting == expensesSetting) {\r\n revenueDifference = updateTable('revenueTable', item, newPrice);\r\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\r\n }\r\n if (window.prices) {\r\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (expensesSetting == 'bid') {\r\n window.prices[item]['bid'] = newPrice;\r\n } else {\r\n window.prices[item]['ask'] = newPrice;\r\n }\r\n }\r\n } else {\r\n revenueDifference = updateTable('revenueTable', item, newPrice);\r\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\r\n if (revenueSetting == expensesSetting) {\r\n expensesDifference = updateTable('expensesTable', item, newPrice);\r\n }\r\n if (window.prices) {\r\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (revenueSetting == 'bid') {\r\n window.prices[item]['bid'] = newPrice;\r\n } else {\r\n window.prices[item]['ask'] = newPrice;\r\n }\r\n }\r\n }\r\n\r\n window.expenses += expensesDifference;\r\n document.getElementById('expensesSpan').innerText = window.expenses.toLocaleString();\r\n window.revenue += revenueDifference;\r\n document.getElementById('revenueSpan').innerText = window.revenue.toLocaleString();\r\n window.noRngRevenue += noRngRevenueDifference;\r\n document.getElementById('noRngRevenueSpan').innerText = window.noRngRevenue.toLocaleString();\r\n\r\n window.profit = window.revenue - window.expenses;\r\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\r\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\r\n window.noRngProfit = window.noRngRevenue - window.expenses;\r\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\r\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\r\n }\r\n});\r\n\r\nfunction updateTable(tableId, item, price) {\r\n let row = document.querySelector('#' + tableId + ' .' + CSS.escape(item));\r\n if (row == null) {\r\n return 0;\r\n }\r\n\r\n row = row.querySelectorAll('td');\r\n let priceTd = row[1];\r\n let amountTd = row[2];\r\n let totalTd = row[3];\r\n let oldTotal = totalTd.innerText;\r\n let newTotal = price * amountTd.innerText;\r\n\r\n if (priceTd.innerText != price) {\r\n priceTd.innerText = price;\r\n }\r\n totalTd.innerText = newTotal;\r\n\r\n return newTotal - oldTotal;\r\n}\r\n\r\n// #endregion\r\n\r\nfunction initPatchNotes() {\r\n const patchNotesRows = document.getElementById(\"patchNotes\");\r\n for (const pn in patchNote) {\r\n const patchNoteContainer = document.createElement(\"div\");\r\n patchNotesRows.setAttribute('class', 'col-12 mb-4');\r\n\r\n const patchNoteElement = document.createElement(\"h6\");\r\n patchNoteElement.innerHTML = pn;\r\n const patchNoteList = document.createElement(\"ul\");\r\n for (const note of patchNote[pn]) {\r\n const noteElement = document.createElement(\"li\");\r\n noteElement.innerHTML = note;\r\n patchNoteList.appendChild(noteElement);\r\n }\r\n patchNoteContainer.appendChild(patchNoteElement);\r\n patchNoteContainer.appendChild(patchNoteList);\r\n\r\n patchNotesRows.appendChild(patchNoteContainer);\r\n }\r\n}\r\n\r\nfunction initExtraBuffSection() {\r\n // mooPass\r\n let mooPassToggle = document.getElementById(\"mooPassToggle\");\r\n let mooPass = localStorage.getItem('mooPass');\r\n if (mooPass) {\r\n mooPassToggle.checked = Boolean(mooPass);\r\n }\r\n mooPassToggle.onchange = () => {\r\n localStorage.setItem('mooPass', mooPassToggle.checked);\r\n }\r\n \r\n // comExp\r\n let comExpToggle = document.getElementById(\"comExpToggle\");\r\n let comExpInput = document.getElementById(\"comExpInput\");\r\n let comExp = localStorage.getItem('comExp');\r\n if (comExp) {\r\n let comExpNumber = Number(comExp);\r\n if (comExpNumber > 0) {\r\n comExpToggle.checked = true;\r\n comExpInput.value = comExpNumber;\r\n } else {\r\n comExpToggle.checked = false;\r\n comExpInput.disabled = true;\r\n }\r\n }\r\n const updateComExp = () => {\r\n if (comExpToggle.checked) {\r\n let comExp = Number(comExpInput.value);\r\n localStorage.setItem('comExp', comExp); \r\n comExpInput.disabled = false;\r\n } else {\r\n localStorage.setItem('comExp', 0);\r\n comExpInput.disabled = true;\r\n }\r\n }\r\n comExpToggle.onchange = updateComExp;\r\n comExpInput.onchange = updateComExp;\r\n\r\n // comDrop\r\n let comDropToggle = document.getElementById(\"comDropToggle\");\r\n let comDropInput = document.getElementById(\"comDropInput\");\r\n let comDrop = localStorage.getItem('comDrop');\r\n if (comDrop) {\r\n let comDropNumber = Number(comDrop);\r\n if (comDropNumber > 0) {\r\n comDropToggle.checked = true;\r\n comDropInput.value = comDropNumber;\r\n } else {\r\n comDropToggle.checked = false;\r\n comDropInput.disabled = true;\r\n }\r\n }\r\n const updateComDrop = () => {\r\n if (comDropToggle.checked) {\r\n let comDrop = Number(comDropInput.value);\r\n localStorage.setItem('comDrop', comDrop); \r\n comDropInput.disabled = false;\r\n } else {\r\n localStorage.setItem('comDrop', 0);\r\n comDropInput.disabled = true;\r\n }\r\n }\r\n comDropToggle.onchange = updateComDrop;\r\n comDropInput.onchange = updateComDrop;\r\n}\r\n\r\n\r\nfunction updateState() {\r\n updateEquipmentState();\r\n updateLevels();\r\n updateFoodState();\r\n updateDrinksState();\r\n updateAbilityState();\r\n}\r\n\r\nfunction updateUI() {\r\n updateCombatStatsUI();\r\n updateFoodUI();\r\n updateDrinksUI();\r\n updateAbilityUI();\r\n\r\n updateContent();\r\n}\r\n\r\nconst darkModeToggle = document.getElementById('darkModeToggle');\r\nconst body = document.body;\r\n\r\nif (localStorage.getItem('darkModeEnabled') === 'true') {\r\n body.classList.add('dark-mode');\r\n const tables = document.getElementsByClassName('profit-table');\r\n for (const table of tables) {\r\n table.classList.toggle('table-striped');\r\n }\r\n darkModeToggle.checked = true;\r\n}\r\n\r\ndarkModeToggle.addEventListener('change', () => {\r\n body.classList.toggle('dark-mode');\r\n const tables = document.getElementsByClassName('profit-table');\r\n for (const table of tables) {\r\n table.classList.toggle('table-striped');\r\n }\r\n localStorage.setItem('darkModeEnabled', darkModeToggle.checked);\r\n});\r\n\r\nfunction updateContent() {\r\n document.querySelectorAll('[data-i18n]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n');\r\n if (key) {\r\n element.textContent = i18next.t(key);\r\n }\r\n });\r\n\r\n document.querySelectorAll('[data-i18n-placeholder]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n-placeholder');\r\n if (key) {\r\n element.placeholder = i18next.t(key);\r\n }\r\n });\r\n\r\n document.querySelectorAll('option[data-i18n]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n');\r\n if (key) {\r\n element.textContent = i18next.t(key);\r\n }\r\n });\r\n}\r\n\r\ninitEquipmentSection();\r\ninitHouseRoomsModal();\r\ninitAchievementsModal();\r\ninitLevelSection();\r\ninitFoodSection();\r\ninitDrinksSection();\r\ninitAbilitiesSection();\r\ninitZones();\r\ninitDungeons();\r\ninitTriggerModal();\r\ninitSimulationControls();\r\ninitEquipmentSetsModal();\r\ninitErrorHandling();\r\ninitImportExportModal();\r\ninitDamageDoneTaken();\r\ninitPatchNotes();\r\ninitExtraBuffSection();\r\n\r\nupdateState();\r\nupdateUI();\r\n"],"names":[],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"bundle.js","mappings":";;;;;;;;;;;;;;;;;AAA0B;AACkC;AAC5B;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,wDAAgB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,6CAAI;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;ACxNG;AACkD;AACR;AACpE;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,gEAAwB;AAChE;AACA,0CAA0C,4DAAoB;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,WAAW;;;;;;;;;;;;;;AC1B1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,IAAI,EAAC;;;;;;;;;;;;;;;ACdpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wDAAwD;AACtF,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,mBAAmB;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;;AC/gBA;AAC4B;AACtB;AAChC;AACA;AACA;AACA;AACA;AACA,6BAA6B,qDAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;ACtF4B;AACmD;AACzG;AACA;AACA;AACA;AACA,uBAAuB,qDAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,iFAAoC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS,EAAC;;;;;;;;;;;;;;;;;AC/CC;AACsC;AAChE;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,0DAAkB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS;;;;;;;;;;;;;;;;;;;;AC7BQ;AACM;AACA;AACF;AACA;AACI;AACxC;AACA,qBAAqB,mDAAU;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,kDAAS;AACrD;AACA;AACA,qDAAqD,mDAAU;AAC/D,2DAA2D,mDAAU;AACrE,qEAAqE,gDAAO;AAC5E;AACA;AACA,2CAA2C,kDAAS;AACpD;AACA,SAAS;AACT;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,MAAM,EAAC;;;;;;;;;;;;;;;;ACvLsE;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,wEAAgC;AAC5C;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UCjLvB;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;UAEA;UACA;;;;;WCzBA;WACA;WACA;WACA;WACA,yCAAyC,wCAAwC;WACjF;WACA;WACA;;;;;WCPA;WACA;WACA;WACA;WACA;;;;;WCJA;WACA;WACA;WACA;WACA,GAAG;WACH;WACA;WACA,CAAC;;;;;WCPD;;;;;WCAA;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D;;;;;WCNA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;;;;WClBA;;WAEA;WACA;WACA;WACA;WACA;WACA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrBuD;AACN;AAC2B;AACN;AACU;AAC7B;AACM;AACN;AACyD;AACF;AACE;AACA;AAClC;AACc;AACN;AACE;AACF;AACG;AACF;AACnF;AAC0C;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,2FAA4B;AAC5D,qCAAqC,qHAAiC;AACtE;AACA;AACA;AACA,iBAAiB,kEAAM;AACvB;AACA,IAAI,SAAI;AACR,IAAI,WAAM;AACV,IAAI,cAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA,sCAAsC,qEAAa;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,mCAAmC,0EAAkB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,iFAAkB;AAClD;AACA,4FAA4F,eAAe;AAC3G;AACA;AACA;AACA,uDAAuD,eAAe;AACtE,6BAA6B,KAAK,GAAG,MAAM;AAC3C;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,iFAAkB;AAClD;AACA,sCAAsC,6EAAoB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,eAAe;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oEAAoE,eAAe;AACnF,mCAAmC,eAAe;AAClD;AACA;AACA,gGAAgG,eAAe;AAC/G;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,gCAAgC,eAAe;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,iBAAiB;AACpD;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,iDAAiD,iBAAiB;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,qEAAa;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,qEAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,qEAAS;AACvD,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,6EAAoB;AACvD;AACA;AACA,qBAAqB,4EAAmB;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,sCAAsC,qEAAa;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,SAAI;AACZ,YAAY,SAAI,mBAAmB,SAAI;AACvC,2BAA2B,qEAAa,CAAC,SAAI;AAC7C,uBAAuB,SAAI;AAC3B;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,qFAAqF,SAAI;AACzF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,uCAAuC,qEAAa;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,WAAM;AACd,YAAY,WAAM,mBAAmB,WAAM;AAC3C,2BAA2B,qEAAa,CAAC,WAAM;AAC/C,uBAAuB,WAAM;AAC7B;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,sFAAsF,WAAM;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D,UAAU;AACV,0CAA0C,wEAAgB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,cAAS;AACjB,YAAY,cAAS,mBAAmB,cAAS;AACjD,8BAA8B,wEAAgB,CAAC,cAAS;AACxD,uBAAuB,cAAS;AAChC;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,4DAA4D,yFAAgC;AAC5F,2DAA2D,yFAAgC;AAC3F,4DAA4D,yFAAgC,YAAY,cAAS;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAS;AACjC,IAAI,cAAS,iBAAiB,cAAS;AACvC,IAAI,cAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,cAAS;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,SAAI;AAChC;AACA;AACA,4BAA4B,WAAM;AAClC;AACA;AACA,4BAA4B,cAAS;AACrC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,qEAAa;AACrD,MAAM;AACN,wCAAwC,wEAAgB;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,yFAAgC;AAC5C;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,wFAAgC;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,wFAAgC;AACrD;AACA;AACA;AACA,mCAAmC,uFAA+B;AAClE,MAAM;AACN,mCAAmC,uFAA+B;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uFAA+B;AACnD;AACA,qEAAqE,yFAAgC;AACrG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,wEAAe;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2GAA2G,WAAW;AACtH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,WAAW;AAC5C,yDAAyD,WAAW;AACpE;AACA;AACA;AACA,gEAAgE,WAAW;AAC3E;AACA;AACA,qGAAqG,WAAW;AAChH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qFAAqF,WAAW;AAChG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4GAA4G,WAAW;AACvH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,WAAW;AAC7C,0DAA0D,WAAW;AACrE;AACA;AACA;AACA,iEAAiE,WAAW;AAC5E;AACA;AACA,sGAAsG,WAAW;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sFAAsF,WAAW;AACjG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,OAAO;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uBAAuB;AAC3C,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,+EAAsB;AAClC,+BAA+B,+EAAsB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,iJAAiJ;AAC9L;AACA,gBAAgB,+EAAsB;AACtC,mCAAmC,+EAAsB;AACzD;AACA;AACA;AACA,qDAAqD,uIAAuI;AAC5L;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,+BAA+B;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,UAAU,kCAAkC;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uEAAuE,oDAAoD;AAC3H,iEAAiE,oDAAoD;AACrH,iFAAiF,oDAAoD;AACrI;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,eAAe;AACrC;AACA;AACA;AACA;AACA,0BAA0B,mBAAmB;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,SAAS;AAC3D;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,KAAK;AACL,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,iBAAiB;AACrC;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,+CAA+C,kBAAkB;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,+EAAsB;AACvC;AACA;AACA;AACA,SAAS;AACT;AACA,UAAU,kCAAkC,qEAAqE;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2EAA2E,wEAAgB;AAC3F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa,qEAAa;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,qEAAa;AACjC,iCAAiC,qEAAa;AAC9C;AACA,kBAAkB,SAAS,wEAAgB;AAC3C,iCAAiC,wEAAgB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,qEAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kFAAkF,IAAI;AACtF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,QAAQ;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,QAAQ;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wEAAgB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,6BAA6B;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qEAAqE,UAAU;AAC/E;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,sCAAsC,4BAA4B;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,qCAAqC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC,2FAA4B;AAChE;AACA;AACA;AACA;AACA,yCAAyC,qHAAiC;AAC1E;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,SAAI;AACxB,yCAAyC,sEAAU,CAAC,SAAI,gBAAgB,SAAI;AAC5E;AACA,kBAAkB;AAClB;AACA;AACA;AACA,oBAAoB,WAAM;AAC1B,yCAAyC,sEAAU,CAAC,WAAM,gBAAgB,WAAM;AAChF;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,cAAS,mCAAmC,yFAAgC;AAChG;AACA,sCAAsC,mEAAO,CAAC,cAAS,iDAAiD,cAAS;AACjH;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,oDAAoD;AACxE;AACA;AACA;AACA;AACA;AACA,wCAAwC,qHAAiC;AACzE;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;AACA;AACA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,wCAAwC;AACrF,kCAAkC,uDAAuD;AACzF;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,qHAAiC;AAC9E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,KAAK,SAAS,qEAAS;AAC5E;AACA,KAAK;AACL;AACA;AACA;AACA,yBAAyB,sEAAU;AACnC;AACA;AACA;AACA;AACA,0BAA0B,sEAAU;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,mEAAO;AAC1C;AACA;AACA;AACA,mBAAmB,kEAAM;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wEAAwE,UAAU;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,IAAI;AAC7D;AACA;AACA;AACA;AACA;AACA,sCAAsC,wEAAe;AACrD;AACA;AACA;AACA;AACA;AACA,4CAA4C,0BAA0B;AACtE;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,wCAAwC;AACjG,8CAA8C,uDAAuD;AACrG;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,oDAAoD;AACpF;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,WAAW,QAAQ,KAAK;AACzD;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,KAAK;AACxC,oCAAoC,KAAK,YAAY,UAAU;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,wBAAwB,WAAW,WAAW;AACnF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd,qEAAqE,WAAW;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd,sEAAsE,YAAY;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sEAAsE,cAAc,IAAI,YAAY;AACpG;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,YAAY,IAAI,eAAe,GAAG,WAAW;AACxF;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,kCAAkC;AAClC,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB,qBAAqB;AACrB,gBAAgB;AAChB,kBAAkB;AAClB,qBAAqB;AACrB,sBAAsB;AACtB,sBAAsB;AACtB,wBAAwB;AACxB;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,uFAA+B;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,SAAI;AAClB,gBAAgB,WAAM;AACtB,mBAAmB,cAAS;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,qEAAa;AACxC,yBAAyB,qEAAa;AACtC;AACA,wCAAwC,gCAAgC,qEAAa;AACrF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yCAAyC;AACzC;AACA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;AACA;AACA;AACA,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,sBAAsB,4GAAmD;AACzE;AACA,aAAa;AACb;AACA;AACA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,6CAAS;AAC9B;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,6CAAS;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sources":["webpack://mwicombatsimulator/./src/combatsimulator/ability.js","webpack://mwicombatsimulator/./src/combatsimulator/achievement.js","webpack://mwicombatsimulator/./src/combatsimulator/buff.js","webpack://mwicombatsimulator/./src/combatsimulator/combatUnit.js","webpack://mwicombatsimulator/./src/combatsimulator/consumable.js","webpack://mwicombatsimulator/./src/combatsimulator/equipment.js","webpack://mwicombatsimulator/./src/combatsimulator/houseRoom.js","webpack://mwicombatsimulator/./src/combatsimulator/player.js","webpack://mwicombatsimulator/./src/combatsimulator/trigger.js","webpack://mwicombatsimulator/webpack/bootstrap","webpack://mwicombatsimulator/webpack/runtime/define property getters","webpack://mwicombatsimulator/webpack/runtime/get javascript chunk filename","webpack://mwicombatsimulator/webpack/runtime/global","webpack://mwicombatsimulator/webpack/runtime/hasOwnProperty shorthand","webpack://mwicombatsimulator/webpack/runtime/make namespace object","webpack://mwicombatsimulator/webpack/runtime/publicPath","webpack://mwicombatsimulator/webpack/runtime/jsonp chunk loading","webpack://mwicombatsimulator/./src/main.js"],"sourcesContent":["import Buff from \"./buff\";\r\nimport abilityDetailMap from \"./data/abilityDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nconst abilityFromCombatStat = {\r\n \"blaze\":\r\n {\r\n \"hrid\": \"/abilities/blaze\",\r\n \"name\": \"Blaze\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"allEnemies\",\r\n \"effectType\": \"/ability_effect_types/damage\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"/damage_types/fire\",\r\n \"baseDamageFlat\": 0,\r\n \"baseDamageFlatLevelBonus\": 0.0,\r\n \"baseDamageRatio\": 0.3,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/number_of_active_units\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n },\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/current_hp\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n }\r\n ],\r\n },\r\n \"bloom\":\r\n {\r\n \"hrid\": \"/abilities/bloom\",\r\n \"name\": \"Bloom\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"lowestHpAlly\",\r\n \"effectType\": \"/ability_effect_types/heal\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"\",\r\n \"baseDamageFlat\": 10,\r\n \"baseDamageFlatLevelBonus\": 0,\r\n \"baseDamageRatio\": 0.15,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_allies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/lowest_hp_percentage\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/less_than_equal\",\r\n \"value\": 100\r\n }\r\n ],\r\n }\r\n}\r\n\r\nclass Ability {\r\n constructor(hrid, level = 1, triggers = null) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameAbility = abilityDetailMap[hrid];\r\n if (!gameAbility) {\r\n gameAbility = abilityFromCombatStat[hrid];\r\n }\r\n if (!gameAbility) {\r\n throw new Error(\"No ability found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.manaCost = gameAbility.manaCost;\r\n this.cooldownDuration = gameAbility.cooldownDuration;\r\n this.castDuration = gameAbility.castDuration;\r\n this.isSpecialAbility = gameAbility.isSpecialAbility;\r\n\r\n this.abilityEffects = [];\r\n\r\n for (const effect of gameAbility.abilityEffects) {\r\n let abilityEffect = {\r\n targetType: effect.targetType,\r\n effectType: effect.effectType,\r\n combatStyleHrid: effect.combatStyleHrid,\r\n damageType: effect.damageType,\r\n damageFlat: effect.baseDamageFlat + (this.level - 1) * effect.baseDamageFlatLevelBonus,\r\n damageRatio: effect.baseDamageRatio + (this.level - 1) * effect.baseDamageRatioLevelBonus,\r\n bonusAccuracyRatio: effect.bonusAccuracyRatio + (this.level - 1) * effect.bonusAccuracyRatioLevelBonus,\r\n damageOverTimeRatio: effect.damageOverTimeRatio,\r\n damageOverTimeDuration: effect.damageOverTimeDuration,\r\n armorDamageRatio: effect.armorDamageRatio + (this.level - 1) * effect.armorDamageRatioLevelBonus,\r\n hpDrainRatio: effect.hpDrainRatio,\r\n pierceChance: effect.pierceChance,\r\n blindChance: effect.blindChance,\r\n blindDuration: effect.blindDuration,\r\n silenceChance: effect.silenceChance,\r\n silenceDuration: effect.silenceDuration,\r\n stunChance: effect.stunChance,\r\n stunDuration: effect.stunDuration,\r\n spendHpRatio: effect.spendHpRatio,\r\n buffs: null,\r\n };\r\n if (effect.buffs) {\r\n abilityEffect.buffs = [];\r\n for (const buff of effect.buffs) {\r\n abilityEffect.buffs.push(new Buff(buff, this.level));\r\n }\r\n }\r\n this.abilityEffects.push(abilityEffect);\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameAbility.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let ability = new Ability(dto.hrid, dto.level, triggers);\r\n\r\n return ability;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n\r\n if (source.isSilenced) {\r\n return false;\r\n }\r\n\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = this.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Ability;\r\n","import Buff from \"./buff\";\r\nimport achievementTierDetailMap from \"./data/achievementTierDetailMap.json\";\r\nimport achievementDetailMap from \"./data/achievementDetailMap.json\";\r\n\r\nclass Achievement {\r\n constructor(achievements) {\r\n this.achievements = achievements;\r\n this.buffs = [];\r\n\r\n for(const tier of Object.values(achievementTierDetailMap)) {\r\n let isGetAll = true;\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid)\r\n for(const achievement of Object.values(detailMap)) {\r\n if(!this.achievements[achievement.hrid] || this.achievements[achievement.hrid] == false) {\r\n isGetAll = false;\r\n break;\r\n }\r\n }\r\n if(isGetAll) {\r\n let buff = new Buff(tier.buff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default Achievement;","class Buff {\r\n startTime;\r\n\r\n constructor(buff, level = 1) {\r\n this.uniqueHrid = buff.uniqueHrid;\r\n this.typeHrid = buff.typeHrid;\r\n this.ratioBoost = buff.ratioBoost + (level - 1) * buff.ratioBoostLevelBonus;\r\n this.flatBoost = buff.flatBoost + (level - 1) * buff.flatBoostLevelBonus;\r\n this.duration = buff.duration;\r\n this.multiplierForSkillHrid = buff.multiplierForSkillHrid ?? \"\";\r\n this.multiplierPerSkillLevel = buff.multiplierPerSkillLevel ?? 0;\r\n }\r\n}\r\n\r\nexport default Buff;\r\n","class CombatUnit {\r\n isPlayer;\r\n isStunned = false;\r\n stunExpireTime = null;\r\n isBlinded = false;\r\n blindExpireTime = null;\r\n isSilenced = false;\r\n silenceExpireTime = null;\r\n\r\n isOutOfMana = false;\r\n\r\n // Base levels which don't change after initialization\r\n staminaLevel = 1;\r\n intelligenceLevel = 1;\r\n attackLevel = 1;\r\n meleeLevel = 1;\r\n defenseLevel = 1;\r\n rangedLevel = 1;\r\n magicLevel = 1;\r\n\r\n experience = 0;\r\n experienceRate = 0;\r\n enrageTime = 0;\r\n\r\n abilities = [null, null, null, null];\r\n food = [null, null, null];\r\n drinks = [null, null, null];\r\n houseRooms = [];\r\n achievements = null;\r\n dropTable = [];\r\n rareDropTable = [];\r\n abilityManaCosts = new Map();\r\n\r\n // Calculated combat stats including temporary buffs\r\n combatDetails = {\r\n staminaLevel: 1,\r\n intelligenceLevel: 1,\r\n attackLevel: 1,\r\n meleeLevel: 1,\r\n defenseLevel: 1,\r\n rangedLevel: 1,\r\n magicLevel: 1,\r\n maxHitpoints: 110,\r\n currentHitpoints: 110,\r\n maxManapoints: 110,\r\n currentManapoints: 110,\r\n stabAccuracyRating: 11,\r\n slashAccuracyRating: 11,\r\n smashAccuracyRating: 11,\r\n rangedAccuracyRating: 11,\r\n magicAccuracyRating: 11,\r\n stabMaxDamage: 11,\r\n slashMaxDamage: 11,\r\n smashMaxDamage: 11,\r\n rangedMaxDamage: 11,\r\n magicMaxDamage: 11,\r\n stabEvasionRating: 11,\r\n slashEvasionRating: 11,\r\n smashEvasionRating: 11,\r\n rangedEvasionRating: 11,\r\n magicEvasionRating: 11,\r\n defensiveMaxDamage: 0,\r\n totalArmor: 0.2,\r\n totalWaterResistance: 0.4,\r\n totalNatureResistance: 0.4,\r\n totalFireResistance: 0.4,\r\n abilityHaste: 0,\r\n tenacity: 0,\r\n totalThreat: 100,\r\n combatStats: {\r\n combatStyleHrid: \"/combat_styles/smash\",\r\n damageType: \"/damage_types/physical\",\r\n attackInterval: 3000000000,\r\n autoAttackDamage: 0,\r\n abilityDamage: 0,\r\n criticalRate: 0,\r\n criticalDamage: 0,\r\n stabAccuracy: 0,\r\n slashAccuracy: 0,\r\n smashAccuracy: 0,\r\n rangedAccuracy: 0,\r\n magicAccuracy: 0,\r\n stabDamage: 0,\r\n slashDamage: 0,\r\n smashDamage: 0,\r\n rangedDamage: 0,\r\n magicDamage: 0,\r\n defensiveDamage: 0,\r\n taskDamage: 0,\r\n physicalAmplify: 0,\r\n waterAmplify: 0,\r\n natureAmplify: 0,\r\n fireAmplify: 0,\r\n healingAmplify: 0,\r\n physicalThorns: 0,\r\n elementalThorns: 0,\r\n maxHitpoints: 0,\r\n maxManapoints: 0,\r\n stabEvasion: 0,\r\n slashEvasion: 0,\r\n smashEvasion: 0,\r\n rangedEvasion: 0,\r\n magicEvasion: 0,\r\n armor: 0,\r\n waterResistance: 0,\r\n natureResistance: 0,\r\n fireResistance: 0,\r\n lifeSteal: 0,\r\n hpRegenPer10: 0.01,\r\n mpRegenPer10: 0.01,\r\n combatDropRate: 0,\r\n combatDropQuantity: 0,\r\n combatRareFind: 0,\r\n combatExperience: 0,\r\n foodSlots: 1,\r\n drinkSlots: 1,\r\n armorPenetration: 0,\r\n waterPenetration: 0,\r\n naturePenetration: 0,\r\n firePenetration: 0,\r\n manaLeech: 0,\r\n castSpeed: 0,\r\n threat: 100,\r\n parry: 0,\r\n mayhem: 0,\r\n pierce: 0,\r\n curse: 0,\r\n ripple: 0,\r\n bloom: 0,\r\n blaze: 0,\r\n weaken: 0,\r\n fury: 0,\r\n foodHaste: 0,\r\n drinkConcentration: 0,\r\n damageTaken: 0,\r\n attackSpeed: 0,\r\n armorDamageRatio: 0,\r\n hpDrainRatio: 0,\r\n primaryTraining: \"\",\r\n focusTraining: \"\",\r\n staminaExperience: 0,\r\n intelligenceExperience: 0,\r\n attackExperience: 0,\r\n defenseExperience: 0,\r\n meleeExperience: 0,\r\n rangedExperience: 0,\r\n magicExperience: 0,\r\n retaliation: 0,\r\n },\r\n };\r\n combatBuffs = {};\r\n permanentBuffs = {};\r\n zoneBuffs = {};\r\n extraBuffs = {};\r\n\r\n constructor() { }\r\n\r\n updateCombatDetails() {\r\n if (this.isPlayer) {\r\n if (this.combatDetails.combatStats.hpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01 + this.combatDetails.combatStats.hpRegenPer10;\r\n }\r\n if (this.combatDetails.combatStats.mpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01 + this.combatDetails.combatStats.mpRegenPer10;\r\n }\r\n }\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((stat) => {\r\n this.combatDetails[stat + \"Level\"] = this[stat + \"Level\"];\r\n let boosts = this.getBuffBoosts(\"/buff_types/\" + stat + \"_level\");\r\n boosts.forEach((buff) => {\r\n this.combatDetails[stat + \"Level\"] += (this[stat + \"Level\"] * buff.ratioBoost);\r\n this.combatDetails[stat + \"Level\"] += buff.flatBoost;\r\n });\r\n });\r\n\r\n this.combatDetails.maxHitpoints = Math.floor\r\n (10 * (10 + this.combatDetails.staminaLevel) + this.combatDetails.combatStats.maxHitpoints);\r\n this.combatDetails.maxManapoints = Math.floor\r\n (10 * (10 + this.combatDetails.intelligenceLevel) + this.combatDetails.combatStats.maxManapoints);\r\n\r\n let accuracyRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_accuracy\").ratioBoost;\r\n let damageRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_damage\").ratioBoost;\r\n // if (accuracyRatioBoostFromFury > 0) {\r\n // console.log(\"Fury Boost: \" + accuracyRatioBoostFromFury);\r\n // }\r\n\r\n let accuracyRatioBoost = this.getBuffBoost(\"/buff_types/accuracy\").ratioBoost;\r\n let damageRatioBoost = this.getBuffBoost(\"/buff_types/damage\").ratioBoost;\r\n\r\n [\"stab\", \"slash\", \"smash\"].forEach((style) => {\r\n this.combatDetails[style + \"AccuracyRating\"] =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Accuracy\"]) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails[style + \"MaxDamage\"] =\r\n (10 + this.combatDetails.meleeLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Damage\"]) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n let baseEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats[style + \"Evasion\"]);\r\n this.combatDetails[style + \"EvasionRating\"] = baseEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails[style + \"EvasionRating\"] += boost.flatBoost;\r\n this.combatDetails[style + \"EvasionRating\"] += baseEvasion * boost.ratioBoost;\r\n }\r\n });\r\n\r\n this.combatDetails.defensiveMaxDamage = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.defensiveDamage);\r\n\r\n // when equiped bulwark\r\n if (this.equipment?.['/equipment_types/two_hand']?.hrid.includes(\"bulwark\")) {\r\n this.combatDetails.smashMaxDamage += this.combatDetails.defensiveMaxDamage;\r\n }\r\n\r\n this.combatDetails.rangedAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.rangedAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.rangedMaxDamage =\r\n (10 + this.combatDetails.rangedLevel) *\r\n (1 + this.combatDetails.combatStats.rangedDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseRangedEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.rangedEvasion);\r\n this.combatDetails.rangedEvasionRating = baseRangedEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.rangedEvasionRating += boost.flatBoost;\r\n this.combatDetails.rangedEvasionRating += baseRangedEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.damageTaken = this.getBuffBoost(\"/buff_types/damage_taken\").flatBoost;\r\n // if (this.combatDetails.combatStats.damageTaken > 0) {\r\n // console.log(\"Damage taken: \" + this.combatDetails.combatStats.damageTaken);\r\n // }\r\n\r\n this.combatDetails.magicAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.magicAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.magicMaxDamage =\r\n (10 + this.combatDetails.magicLevel) *\r\n (1 + this.combatDetails.combatStats.magicDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseMagicEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.magicEvasion);\r\n this.combatDetails.magicEvasionRating = baseMagicEvasion;\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.magicEvasionRating += boost.flatBoost;\r\n this.combatDetails.magicEvasionRating += baseMagicEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.physicalAmplify += this.getBuffBoost(\"/buff_types/physical_amplify\").flatBoost;\r\n this.combatDetails.combatStats.waterAmplify += this.getBuffBoost(\"/buff_types/water_amplify\").flatBoost;\r\n this.combatDetails.combatStats.natureAmplify += this.getBuffBoost(\"/buff_types/nature_amplify\").flatBoost;\r\n this.combatDetails.combatStats.fireAmplify += this.getBuffBoost(\"/buff_types/fire_amplify\").flatBoost;\r\n\r\n this.combatDetails.combatStats.attackInterval /= (1 + (this.combatDetails.attackLevel / 2000));\r\n\r\n let baseAttackSpeed = this.combatDetails.combatStats.attackSpeed;\r\n this.combatDetails.combatStats.attackInterval /= (1 + baseAttackSpeed);\r\n let attackIntervalBoosts = this.getBuffBoosts(\"/buff_types/attack_speed\");\r\n let attackIntervalRatioBoost = attackIntervalBoosts\r\n .map((boost) => boost.ratioBoost)\r\n .reduce((prev, cur) => prev + cur, 0);\r\n this.combatDetails.combatStats.attackInterval /= (1 + attackIntervalRatioBoost);\r\n\r\n let baseArmor = 0.2 * this.combatDetails.defenseLevel + this.combatDetails.combatStats.armor;\r\n this.combatDetails.totalArmor = baseArmor;\r\n let armorBoosts = this.getBuffBoosts(\"/buff_types/armor\");\r\n for (const boost of armorBoosts) {\r\n this.combatDetails.totalArmor += boost.flatBoost;\r\n this.combatDetails.totalArmor += baseArmor * boost.ratioBoost;\r\n }\r\n\r\n let baseWaterResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.waterResistance;\r\n this.combatDetails.totalWaterResistance = baseWaterResistance;\r\n let waterResistanceBoosts = this.getBuffBoosts(\"/buff_types/water_resistance\");\r\n for (const boost of waterResistanceBoosts) {\r\n this.combatDetails.totalWaterResistance += boost.flatBoost;\r\n this.combatDetails.totalWaterResistance += baseWaterResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseNatureResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.natureResistance;\r\n this.combatDetails.totalNatureResistance = baseNatureResistance;\r\n let natureResistanceBoosts = this.getBuffBoosts(\"/buff_types/nature_resistance\");\r\n for (const boost of natureResistanceBoosts) {\r\n this.combatDetails.totalNatureResistance += boost.flatBoost;\r\n this.combatDetails.totalNatureResistance += baseNatureResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseFireResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.fireResistance;\r\n this.combatDetails.totalFireResistance = baseFireResistance;\r\n let fireResistanceBoosts = this.getBuffBoosts(\"/buff_types/fire_resistance\");\r\n for (const boost of fireResistanceBoosts) {\r\n this.combatDetails.totalFireResistance += boost.flatBoost;\r\n this.combatDetails.totalFireResistance += baseFireResistance * boost.ratioBoost;\r\n }\r\n\r\n let hpRegenBoosts = this.getBuffBoost(\"/buff_types/hp_regen\");\r\n this.combatDetails.combatStats.hpRegenPer10 += this.combatDetails.combatStats.hpRegenPer10 * hpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.hpRegenPer10 += hpRegenBoosts.flatBoost;\r\n\r\n let mpRegenBoosts = this.getBuffBoost(\"/buff_types/mp_regen\");\r\n this.combatDetails.combatStats.mpRegenPer10 += this.combatDetails.combatStats.mpRegenPer10 * mpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.mpRegenPer10 += mpRegenBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.lifeSteal += this.getBuffBoost(\"/buff_types/life_steal\").flatBoost;\r\n this.combatDetails.combatStats.physicalThorns += this.getBuffBoost(\r\n \"/buff_types/physical_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.elementalThorns += this.getBuffBoost(\r\n \"/buff_types/elemental_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.combatExperience += this.getBuffBoost(\"/buff_types/wisdom\").flatBoost;\r\n this.combatDetails.combatStats.criticalRate += this.getBuffBoost(\"/buff_types/critical_rate\").flatBoost;\r\n this.combatDetails.combatStats.criticalDamage += this.getBuffBoost(\"/buff_types/critical_damage\").flatBoost;\r\n\r\n this.combatDetails.combatStats.castSpeed += this.getBuffBoost(\"/buff_types/cast_speed\").flatBoost;\r\n this.combatDetails.combatStats.castSpeed += this.combatDetails[\"attackLevel\"] / 2000;\r\n\r\n let combatDropRateBoosts = this.getBuffBoost(\"/buff_types/combat_drop_rate\");\r\n this.combatDetails.combatStats.combatDropRate += (1 + this.combatDetails.combatStats.combatDropRate) * combatDropRateBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropRate += combatDropRateBoosts.flatBoost;\r\n let combatRareFindBoosts = this.getBuffBoost(\"/buff_types/rare_find\");\r\n this.combatDetails.combatStats.combatRareFind += (1 + this.combatDetails.combatStats.combatRareFind) * combatRareFindBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatRareFind += combatRareFindBoosts.flatBoost;\r\n let combatDropQuantityBoosts = this.getBuffBoost(\"/buff_types/combat_drop_quantity\");\r\n this.combatDetails.combatStats.combatDropQuantity += (1 + this.combatDetails.combatStats.combatDropQuantity) * combatDropQuantityBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropQuantity += combatDropQuantityBoosts.flatBoost;\r\n\r\n let baseThreat = 100 + this.combatDetails.combatStats.threat;\r\n this.combatDetails.totalThreat = baseThreat;\r\n let threatBoosts = this.getBuffBoost(\"/buff_types/threat\");\r\n if (threatBoosts.ratioBoost !== 0) {\r\n this.combatDetails.combatStats.threat += baseThreat * threatBoosts.ratioBoost;\r\n } else {\r\n this.combatDetails.combatStats.threat = baseThreat;\r\n }\r\n this.combatDetails.combatStats.threat += threatBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.retaliation += this.getBuffBoost(\"/buff_types/retaliation\").flatBoost;\r\n }\r\n\r\n addBuff(buff, currentTime) {\r\n buff.startTime = currentTime;\r\n this.combatBuffs[buff.uniqueHrid] = buff;\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n removeBuff(buff) {\r\n if (!this.combatBuffs[buff.uniqueHrid]) {\r\n return;\r\n }\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n addPermanentBuff(buff) {\r\n if (this.permanentBuffs[buff.typeHrid]) {\r\n this.permanentBuffs[buff.typeHrid].flatBoost += buff.flatBoost;\r\n this.permanentBuffs[buff.typeHrid].ratioBoost += buff.ratioBoost;\r\n } else {\r\n this.permanentBuffs[buff.typeHrid] = buff;\r\n }\r\n }\r\n\r\n generatePermanentBuffs() {\r\n for (let i = 0; i < this.houseRooms.length; i++) {\r\n const houseRoom = this.houseRooms[i];\r\n houseRoom.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n\r\n if (this.achievements) {\r\n this.achievements.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.zoneBuffs) {\r\n this.zoneBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.extraBuffs) {\r\n this.extraBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n }\r\n\r\n removeExpiredBuffs(currentTime) {\r\n let expiredBuffs = Object.values(this.combatBuffs).filter(\r\n (buff) => buff.startTime + buff.duration <= currentTime\r\n );\r\n expiredBuffs.forEach((buff) => {\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n });\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearBuffs() {\r\n this.combatBuffs = structuredClone(this.permanentBuffs);\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearCCs() {\r\n this.isStunned = false;\r\n this.stunExpireTime = null;\r\n this.isSilenced = false;\r\n this.silenceExpireTime = null;\r\n this.isBlinded = false;\r\n this.blindExpireTime = null;\r\n this.combatDetails.combatStats.damageTaken = 0;\r\n }\r\n\r\n getBuffBoosts(type) {\r\n let boosts = [];\r\n Object.values(this.combatBuffs)\r\n .filter((buff) => buff.typeHrid == type)\r\n .forEach((buff) => {\r\n boosts.push({ ratioBoost: buff.ratioBoost, flatBoost: buff.flatBoost });\r\n });\r\n\r\n return boosts;\r\n }\r\n\r\n getBuffBoost(type) {\r\n let boosts = this.getBuffBoosts(type);\r\n\r\n let boost = {\r\n ratioBoost: 0,\r\n flatBoost: 0,\r\n };\r\n\r\n for (let i = 0; i < boosts.length; i++) {\r\n boost.ratioBoost += boosts[i]?.ratioBoost ?? 0;\r\n boost.flatBoost += boosts[i]?.flatBoost ?? 0;\r\n }\r\n\r\n return boost;\r\n }\r\n\r\n reset(currentTime = 0) {\r\n this.clearCCs();\r\n this.clearBuffs();\r\n this.updateCombatDetails();\r\n this.resetCooldowns(currentTime);\r\n\r\n this.combatDetails.currentHitpoints = this.combatDetails.maxHitpoints;\r\n this.combatDetails.currentManapoints = this.combatDetails.maxManapoints;\r\n }\r\n\r\n resetCooldowns(currentTime = 0) {\r\n this.food.filter((food) => food != null).forEach((food) => (food.lastUsed = Number.MIN_SAFE_INTEGER));\r\n this.drinks.filter((drink) => drink != null).forEach((drink) => (drink.lastUsed = Number.MIN_SAFE_INTEGER));\r\n\r\n let haste = this.combatDetails.combatStats.abilityHaste;\r\n\r\n this.abilities\r\n .filter((ability) => ability != null)\r\n .forEach((ability) => {\r\n if (this.isPlayer) {\r\n ability.lastUsed = Number.MIN_SAFE_INTEGER;\r\n } else {\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n ability.lastUsed = currentTime - Math.floor(cooldownDuration * 0.5) + Math.floor(Math.random() * cooldownDuration * 0.5);\r\n }\r\n });\r\n }\r\n\r\n addHitpoints(hitpoints) {\r\n let hitpointsAdded = 0;\r\n\r\n if (this.combatDetails.currentHitpoints >= this.combatDetails.maxHitpoints) {\r\n return hitpointsAdded;\r\n }\r\n\r\n let newHitpoints = Math.min(this.combatDetails.currentHitpoints + hitpoints, this.combatDetails.maxHitpoints);\r\n hitpointsAdded = newHitpoints - this.combatDetails.currentHitpoints;\r\n this.combatDetails.currentHitpoints = newHitpoints;\r\n\r\n return hitpointsAdded;\r\n }\r\n\r\n addManapoints(manapoints) {\r\n let manapointsAdded = 0;\r\n\r\n if (this.combatDetails.currentManapoints >= this.combatDetails.maxManapoints) {\r\n return manapointsAdded;\r\n }\r\n\r\n let newManapoints = Math.min(\r\n this.combatDetails.currentManapoints + manapoints,\r\n this.combatDetails.maxManapoints\r\n );\r\n manapointsAdded = newManapoints - this.combatDetails.currentManapoints;\r\n this.combatDetails.currentManapoints = newManapoints;\r\n\r\n return manapointsAdded;\r\n }\r\n}\r\n\r\nexport default CombatUnit;\r\n","import Buff from \"./buff\";\r\nimport itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nclass Consumable {\r\n constructor(hrid, triggers = null) {\r\n this.hrid = hrid;\r\n\r\n let gameConsumable = itemDetailMap[this.hrid];\r\n if (!gameConsumable) {\r\n throw new Error(\"No consumable found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.cooldownDuration = gameConsumable.consumableDetail.cooldownDuration;\r\n this.hitpointRestore = gameConsumable.consumableDetail.hitpointRestore;\r\n this.manapointRestore = gameConsumable.consumableDetail.manapointRestore;\r\n this.recoveryDuration = gameConsumable.consumableDetail.recoveryDuration;\r\n this.catagoryHrid = gameConsumable.categoryHrid;\r\n\r\n this.buffs = [];\r\n if (gameConsumable.consumableDetail.buffs) {\r\n for (const consumableBuff of gameConsumable.consumableDetail.buffs) {\r\n let buff = new Buff(consumableBuff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameConsumable.consumableDetail.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let consumable = new Consumable(dto.hrid, triggers);\r\n\r\n return consumable;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n let consumableHaste;\r\n if (this.catagoryHrid.includes(\"food\")) {\r\n consumableHaste = source.combatDetails.combatStats.foodHaste\r\n } else {\r\n consumableHaste = source.combatDetails.combatStats.drinkConcentration;\r\n }\r\n let cooldownDuration = this.cooldownDuration;\r\n if (consumableHaste > 0) {\r\n cooldownDuration = cooldownDuration / (1 + consumableHaste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Consumable;\r\n","import itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport enhancementLevelTotalMultiplierTable from \"./data/enhancementLevelTotalBonusMultiplierTable.json\";\r\n\r\nclass Equipment {\r\n constructor(hrid, enhancementLevel) {\r\n this.hrid = hrid;\r\n let gameItem = itemDetailMap[this.hrid];\r\n if (!gameItem) {\r\n throw new Error(\"No equipment found for hrid: \" + this.hrid);\r\n }\r\n this.gameItem = gameItem;\r\n this.enhancementLevel = enhancementLevel;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let equipment = new Equipment(dto.hrid, dto.enhancementLevel);\r\n\r\n return equipment;\r\n }\r\n\r\n getCombatStat(combatStat) {\r\n let multiplier = enhancementLevelTotalMultiplierTable[this.enhancementLevel];\r\n if(this.gameItem.equipmentDetail.combatStats[combatStat]) {\r\n let enhancementBonus = this.gameItem.equipmentDetail.combatEnhancementBonuses[combatStat] || 0;\r\n let stat = this.gameItem.equipmentDetail.combatStats[combatStat] + multiplier * enhancementBonus;\r\n return stat;\r\n }\r\n return 0;\r\n }\r\n\r\n getCombatStyle() {\r\n return this.gameItem.equipmentDetail.combatStats.combatStyleHrids[0];\r\n }\r\n\r\n getDamageType() {\r\n return this.gameItem.equipmentDetail.combatStats.damageType;\r\n }\r\n\r\n getPrimaryTraining() {\r\n return this.gameItem.equipmentDetail.combatStats.primaryTraining;\r\n }\r\n\r\n getFocusTraining(){\r\n return this.gameItem.equipmentDetail.combatStats.focusTraining;\r\n }\r\n}\r\n\r\nexport default Equipment;\r\n","import Buff from \"./buff\";\r\nimport houseRoomDetailMap from \"./data/houseRoomDetailMap.json\";\r\n\r\nclass HouseRoom {\r\n constructor(hrid, level) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameHouseRoom = houseRoomDetailMap[this.hrid];\r\n if (!gameHouseRoom) {\r\n throw new Error(\"No house room found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.buffs = [];\r\n if (gameHouseRoom.actionBuffs) {\r\n for (const actionBuff of gameHouseRoom.actionBuffs) {\r\n let buff = new Buff(actionBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n if (gameHouseRoom.globalBuffs) {\r\n for (const globalBuff of gameHouseRoom.globalBuffs) {\r\n let buff = new Buff(globalBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default HouseRoom;","import Ability from \"./ability\";\r\nimport CombatUnit from \"./combatUnit\";\r\nimport Consumable from \"./consumable\";\r\nimport Equipment from \"./equipment\";\r\nimport HouseRoom from \"./houseRoom\";\r\nimport Achievement from \"./achievement\";\r\n\r\nclass Player extends CombatUnit {\r\n equipment = {\r\n \"/equipment_types/head\": null,\r\n \"/equipment_types/body\": null,\r\n \"/equipment_types/legs\": null,\r\n \"/equipment_types/feet\": null,\r\n \"/equipment_types/hands\": null,\r\n \"/equipment_types/main_hand\": null,\r\n \"/equipment_types/two_hand\": null,\r\n \"/equipment_types/off_hand\": null,\r\n \"/equipment_types/pouch\": null,\r\n \"/equipment_types/back\": null,\r\n };\r\n\r\n constructor() {\r\n super();\r\n\r\n this.isPlayer = true;\r\n this.hrid = \"player\";\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let player = new Player();\r\n\r\n player.staminaLevel = dto.staminaLevel;\r\n player.intelligenceLevel = dto.intelligenceLevel;\r\n player.attackLevel = dto.attackLevel;\r\n player.meleeLevel = dto.meleeLevel;\r\n player.defenseLevel = dto.defenseLevel;\r\n player.rangedLevel = dto.rangedLevel;\r\n player.magicLevel = dto.magicLevel;\r\n\r\n player.hrid = dto.hrid;\r\n\r\n for (const [key, value] of Object.entries(dto.equipment)) {\r\n player.equipment[key] = value ? Equipment.createFromDTO(value) : null;\r\n }\r\n\r\n player.food = dto.food.map((food) => (food ? Consumable.createFromDTO(food) : null));\r\n player.drinks = dto.drinks.map((drink) => (drink ? Consumable.createFromDTO(drink) : null));\r\n player.abilities = dto.abilities.map((ability) => (ability ? Ability.createFromDTO(ability) : null));\r\n Object.entries(dto.houseRooms).forEach(houseRoom => {\r\n if (houseRoom[1] > 0) {\r\n player.houseRooms.push(new HouseRoom(houseRoom[0], houseRoom[1]))\r\n }\r\n });\r\n\r\n player.achievements = new Achievement(dto.achievements);\r\n\r\n player.debuffOnLevelGap = dto.debuffOnLevelGap;\r\n\r\n return player;\r\n }\r\n\r\n updateCombatDetails() {\r\n if (this.equipment[\"/equipment_types/main_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/main_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/main_hand\"].getPrimaryTraining();\r\n } else if (this.equipment[\"/equipment_types/two_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/two_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/two_hand\"].getPrimaryTraining();\r\n } else {\r\n this.combatDetails.combatStats.combatStyleHrid = \"/combat_styles/smash\";\r\n this.combatDetails.combatStats.damageType = \"/damage_types/physical\";\r\n this.combatDetails.combatStats.attackInterval = 3000000000;\r\n this.combatDetails.combatStats.primaryTraining = \"/skills/melee\";\r\n }\r\n\r\n if (this.equipment[\"/equipment_types/charm\"]) {\r\n this.combatDetails.combatStats.focusTraining = this.equipment[\"/equipment_types/charm\"].getFocusTraining();\r\n } else {\r\n this.combatDetails.combatStats.focusTraining = \"\";\r\n }\r\n\r\n [\r\n \"stabAccuracy\",\r\n \"slashAccuracy\",\r\n \"smashAccuracy\",\r\n \"rangedAccuracy\",\r\n \"magicAccuracy\",\r\n \"stabDamage\",\r\n \"slashDamage\",\r\n \"smashDamage\",\r\n \"rangedDamage\",\r\n \"magicDamage\",\r\n \"defensiveDamage\",\r\n \"taskDamage\",\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"stabEvasion\",\r\n \"slashEvasion\",\r\n \"smashEvasion\",\r\n \"rangedEvasion\",\r\n \"magicEvasion\",\r\n \"armor\",\r\n \"waterResistance\",\r\n \"natureResistance\",\r\n \"fireResistance\",\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"combatDropRate\",\r\n \"combatRareFind\",\r\n \"combatDropQuantity\",\r\n \"combatExperience\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"abilityHaste\",\r\n \"tenacity\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"threat\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"foodHaste\",\r\n \"drinkConcentration\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\",\r\n \"retaliation\"\r\n ].forEach((stat) => {\r\n this.combatDetails.combatStats[stat] = Object.values(this.equipment)\r\n .filter((equipment) => equipment != null)\r\n .map((equipment) => equipment.getCombatStat(stat))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n });\r\n\r\n if (this.equipment[\"/equipment_types/pouch\"]) {\r\n this.combatDetails.combatStats.foodSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"foodSlots\");\r\n this.combatDetails.combatStats.drinkSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"drinkSlots\");\r\n } else {\r\n this.combatDetails.combatStats.foodSlots = 1;\r\n this.combatDetails.combatStats.drinkSlots = 1;\r\n }\r\n\r\n super.updateCombatDetails();\r\n }\r\n}\r\n\r\nexport default Player;\r\n","import combatTriggerDependencyDetailMap from \"./data/combatTriggerDependencyDetailMap.json\";\r\n\r\nclass Trigger {\r\n constructor(dependencyHrid, conditionHrid, comparatorHrid, value = 0) {\r\n this.dependencyHrid = dependencyHrid;\r\n this.conditionHrid = conditionHrid;\r\n this.comparatorHrid = comparatorHrid;\r\n this.value = value;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let trigger = new Trigger(dto.dependencyHrid, dto.conditionHrid, dto.comparatorHrid, dto.value);\r\n\r\n return trigger;\r\n }\r\n\r\n isActive(source, target, friendlies, enemies, currentTime) {\r\n if (combatTriggerDependencyDetailMap[this.dependencyHrid].isSingleTarget) {\r\n return this.isActiveSingleTarget(source, target, currentTime);\r\n } else {\r\n return this.isActiveMultiTarget(friendlies, enemies, currentTime);\r\n }\r\n }\r\n\r\n isActiveSingleTarget(source, target, currentTime) {\r\n let dependencyValue;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/self\":\r\n dependencyValue = this.getDependencyValue(source, currentTime);\r\n break;\r\n case \"/combat_trigger_dependencies/targeted_enemy\":\r\n if (!target) {\r\n return false;\r\n }\r\n dependencyValue = this.getDependencyValue(target, currentTime);\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n isActiveMultiTarget(friendlies, enemies, currentTime) {\r\n let dependency;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/all_allies\":\r\n dependency = friendlies;\r\n break;\r\n case \"/combat_trigger_dependencies/all_enemies\":\r\n if (!enemies) {\r\n return false;\r\n }\r\n dependency = enemies;\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n let dependencyValue;\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/number_of_active_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints > 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/number_of_dead_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints <= 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/lowest_hp_percentage\":\r\n dependencyValue = dependency.reduce((prev, curr) => {\r\n let currentHpPercentage = curr.combatDetails.currentHitpoints / curr.combatDetails.maxHitpoints;\r\n return currentHpPercentage < prev ? currentHpPercentage : prev;\r\n }, 2) * 100;\r\n break;\r\n default:\r\n dependencyValue = dependency\r\n .map((unit) => this.getDependencyValue(unit, currentTime))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n break;\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n getDependencyValue(source, currentTime) {\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/berserk\":\r\n case \"/combat_trigger_conditions/frenzy\":\r\n case \"/combat_trigger_conditions/precision\":\r\n case \"/combat_trigger_conditions/vampirism\":\r\n case \"/combat_trigger_conditions/attack_coffee\":\r\n case \"/combat_trigger_conditions/defense_coffee\":\r\n case \"/combat_trigger_conditions/lucky_coffee\":\r\n case \"/combat_trigger_conditions/magic_coffee\":\r\n case \"/combat_trigger_conditions/melee_coffee\":\r\n case \"/combat_trigger_conditions/ranged_coffee\":\r\n case \"/combat_trigger_conditions/swiftness_coffee\":\r\n case \"/combat_trigger_conditions/wisdom_coffee\":\r\n case \"/combat_trigger_conditions/ice_spear\":\r\n case \"/combat_trigger_conditions/puncture\":\r\n case \"/combat_trigger_conditions/frost_surge\":\r\n case \"/combat_trigger_conditions/elusiveness\":\r\n case \"/combat_trigger_conditions/channeling_coffee\":\r\n case \"/combat_trigger_conditions/fierce_aura\":\r\n case \"/combat_trigger_conditions/invincible_armor\":\r\n case \"/combat_trigger_conditions/invincible_fire_resistance\":\r\n case \"/combat_trigger_conditions/invincible_nature_resistance\":\r\n case \"/combat_trigger_conditions/invincible_water_resistance\":\r\n case \"/combat_trigger_conditions/provoke\":\r\n case \"/combat_trigger_conditions/taunt\":\r\n case \"/combat_trigger_conditions/crippling_slash\":\r\n case \"/combat_trigger_conditions/mana_spring\":\r\n case \"/combat_trigger_conditions/retribution\":\r\n case \"/combat_trigger_conditions/fracturing_impact\":\r\n case \"/combat_trigger_conditions/maim\":\r\n case \"/combat_trigger_conditions/curse\":\r\n case \"/combat_trigger_conditions/weaken\":\r\n let buffHrid = \"/buff_uniques\";\r\n buffHrid += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n return source.combatBuffs[buffHrid];\r\n case \"/combat_trigger_conditions/critical_aura\":\r\n case \"/combat_trigger_conditions/critical_coffee\":\r\n case \"/combat_trigger_conditions/intelligence_coffee\":\r\n case \"/combat_trigger_conditions/stamina_coffee\":\r\n case \"/combat_trigger_conditions/elemental_affinity\":\r\n case \"/combat_trigger_conditions/fury\":\r\n case \"/combat_trigger_conditions/guardian_aura\":\r\n case \"/combat_trigger_conditions/insanity\":\r\n case \"/combat_trigger_conditions/spike_shell\":\r\n case \"/combat_trigger_conditions/toxic_pollen\":\r\n case \"/combat_trigger_conditions/invincible\":\r\n case \"/combat_trigger_conditions/mystic_aura\":\r\n case \"/combat_trigger_conditions/pestilent_shot\":\r\n case \"/combat_trigger_conditions/smoke_burst\":\r\n case \"/combat_trigger_conditions/speed_aura\":\r\n case \"/combat_trigger_conditions/toughness\":\r\n case \"/combat_trigger_conditions/enrage\":\r\n let buffPrefix = \"/buff_uniques\";\r\n buffPrefix += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n let buffs = Object.keys(source.combatBuffs).filter(buff => buff.startsWith(buffPrefix));\r\n return source.combatBuffs[buffs?.[0]];\r\n case \"/combat_trigger_conditions/current_hp\":\r\n return source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/current_mp\":\r\n return source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/missing_hp\":\r\n return source.combatDetails.maxHitpoints - source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/missing_mp\":\r\n return source.combatDetails.maxManapoints - source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/stun_status\":\r\n // Replicate the game's behaviour of \"stun status active\" triggers activating\r\n // immediately after the stun has worn off\r\n return source.isStunned || source.stunExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/blind_status\":\r\n return source.isBlinded || source.blindExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/silence_status\":\r\n return source.isSilenced || source.silenceExpireTime == currentTime;\r\n default:\r\n throw new Error(\"Unknown conditionHrid in trigger: \" + this.conditionHrid);\r\n }\r\n }\r\n\r\n compareValue(dependencyValue) {\r\n switch (this.comparatorHrid) {\r\n case \"/combat_trigger_comparators/greater_than_equal\":\r\n return dependencyValue >= this.value;\r\n case \"/combat_trigger_comparators/less_than_equal\":\r\n return dependencyValue <= this.value;\r\n case \"/combat_trigger_comparators/is_active\":\r\n return !!dependencyValue;\r\n case \"/combat_trigger_comparators/is_inactive\":\r\n return !dependencyValue;\r\n default:\r\n throw new Error(\"Unknown comparatorHrid in trigger: \" + this.comparatorHrid);\r\n }\r\n }\r\n}\r\n\r\nexport default Trigger;\r\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","// This function allow to reference async chunks\n__webpack_require__.u = (chunkId) => {\n\t// return url for filenames based on template\n\treturn \"\" + chunkId + \".bundle.js\";\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","var scriptUrl;\nif (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + \"\";\nvar document = __webpack_require__.g.document;\nif (!scriptUrl && document) {\n\tif (document.currentScript)\n\t\tscriptUrl = document.currentScript.src;\n\tif (!scriptUrl) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tif(scripts.length) {\n\t\t\tvar i = scripts.length - 1;\n\t\t\twhile (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;\n\t\t}\n\t}\n}\n// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration\n// or pass an empty string (\"\") and set the __webpack_public_path__ variable from your code to use your own logic.\nif (!scriptUrl) throw new Error(\"Automatic publicPath is not supported in this browser\");\nscriptUrl = scriptUrl.replace(/#.*$/, \"\").replace(/\\?.*$/, \"\").replace(/\\/[^\\/]+$/, \"/\");\n__webpack_require__.p = scriptUrl;","__webpack_require__.b = document.baseURI || self.location.href;\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t\"main\": 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n// no on chunks loaded\n\n// no jsonp function","import Equipment from \"./combatsimulator/equipment.js\";\r\nimport Player from \"./combatsimulator/player.js\";\r\nimport abilityDetailMap from \"./combatsimulator/data/abilityDetailMap.json\";\r\nimport itemDetailMap from \"./combatsimulator/data/itemDetailMap.json\";\r\nimport houseRoomDetailMap from \"./combatsimulator/data/houseRoomDetailMap.json\";\r\nimport Ability from \"./combatsimulator/ability.js\";\r\nimport Consumable from \"./combatsimulator/consumable.js\";\r\nimport HouseRoom from \"./combatsimulator/houseRoom\"\r\nimport combatTriggerDependencyDetailMap from \"./combatsimulator/data/combatTriggerDependencyDetailMap.json\";\r\nimport combatTriggerConditionDetailMap from \"./combatsimulator/data/combatTriggerConditionDetailMap.json\";\r\nimport combatTriggerComparatorDetailMap from \"./combatsimulator/data/combatTriggerComparatorDetailMap.json\";\r\nimport abilitySlotsLevelRequirementList from \"./combatsimulator/data/abilitySlotsLevelRequirementList.json\";\r\nimport actionDetailMap from \"./combatsimulator/data/actionDetailMap.json\";\r\nimport combatMonsterDetailMap from \"./combatsimulator/data/combatMonsterDetailMap.json\";\r\nimport damageTypeDetailMap from \"./combatsimulator/data/damageTypeDetailMap.json\";\r\nimport combatStyleDetailMap from \"./combatsimulator/data/combatStyleDetailMap.json\";\r\nimport openableLootDropMap from \"./combatsimulator/data/openableLootDropMap.json\";\r\nimport achievementTierMap from \"./combatsimulator/data/achievementTierDetailMap.json\"\r\nimport achievementDetailMap from \"./combatsimulator/data/achievementDetailMap.json\"\r\n\r\nimport patchNote from \"../patchNote.json\";\r\n\r\nconst ONE_SECOND = 1e9;\r\nconst ONE_HOUR = 60 * 60 * ONE_SECOND;\r\n\r\nlet buttonStartSimulation = document.getElementById(\"buttonStartSimulation\");\r\nlet buttonStopSimulation = document.getElementById(\"buttonStopSimulation\");\r\nlet progressbar = document.getElementById(\"simulationProgressBar\");\r\nlet simStartTime = 0;\r\n\r\nlet worker = new Worker(new URL(\"worker.js\", import.meta.url));\r\nlet multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\nlet workerPool = [];\r\n\r\n\r\nlet player = new Player();\r\nlet selectedPlayers = [];\r\nlet food = [null, null, null];\r\nlet drinks = [null, null, null];\r\nlet abilities = [null, null, null, null];\r\nlet triggerMap = {};\r\nlet modalTriggers = [];\r\nlet currentSimResults = {};\r\n\r\nlet currentPlayerTabId = '1';\r\nlet playerDataMap = {\r\n \"1\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"2\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"3\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"4\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"5\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\"\r\n};\r\nwindow.revenue = 0;\r\nwindow.noRngRevenue = 0;\r\nwindow.expenses = 0;\r\nwindow.profit = 0;\r\nwindow.noRngProfit = 0;\r\n\r\n// #region Worker\r\n\r\nfunction onWorkerMessage(event) {\r\n switch (event.data.type) {\r\n case \"simulation_result\":\r\n progressbar.style.width = \"100%\";\r\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n //console.log(\"SIM RESULTS: \", event.data.simResult);\r\n showSimulationResult(event.data.simResult);\r\n updateContent();\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n document.getElementById('buttonShowAllSimData').style.display = 'none';\r\n break;\r\n case \"simulation_progress\":\r\n let progress = Math.floor(100 * event.data.progress);\r\n progressbar.style.width = progress + \"%\";\r\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n break;\r\n case \"simulation_error\":\r\n showErrorModal(event.data.error.toString());\r\n break;\r\n }\r\n};\r\n\r\nfunction onMultiWorkerMessage(event) {\r\n switch (event.data.type) {\r\n case \"simulation_result_allZones\":\r\n progressbar.style.width = \"100%\";\r\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n showAllSimulationResults(event.data.simResults);\r\n updateContent();\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n document.getElementById('buttonShowAllSimData').style.display = 'block';\r\n break;\r\n case \"simulation_progress\":\r\n let progress = Math.floor(100 * event.data.progress);\r\n progressbar.style.width = progress + \"%\";\r\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n break;\r\n case \"simulation_error\":\r\n showErrorModal(event.data.error.toString());\r\n break;\r\n }\r\n};\r\n\r\n// #endregion\r\n\r\n// #region Equipment\r\n\r\nfunction initEquipmentSection() {\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n initEquipmentSelect(type);\r\n initEnhancementLevelInput(type);\r\n });\r\n}\r\n\r\nfunction initEquipmentSelect(equipmentType) {\r\n let selectId = \"selectEquipment_\";\r\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\r\n selectId += \"weapon\";\r\n } else {\r\n selectId += equipmentType;\r\n }\r\n let selectElement = document.getElementById(selectId);\r\n\r\n let gameEquipment = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/equipment\")\r\n .filter((item) => item.equipmentDetail.type == \"/equipment_types/\" + equipmentType)\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const equipment of Object.values(gameEquipment)) {\r\n let opt = new Option(equipment.name, equipment.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + equipment.hrid);\r\n selectElement.add(opt);\r\n }\r\n\r\n selectElement.addEventListener(\"change\", (event) => {\r\n equipmentSelectHandler(event, equipmentType);\r\n });\r\n}\r\n\r\nfunction initHouseRoomsModal() {\r\n let houseRoomsList = document.getElementById(\"houseRoomsList\");\r\n let newChildren = [];\r\n let houseRooms = Object.values(houseRoomDetailMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n player.houseRooms = {};\r\n\r\n for (const room of Object.values(houseRooms)) {\r\n player.houseRooms[room.hrid] = 0;\r\n\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let nameCol = createElement(\"div\", \"col-md-4 offset-md-3 align-self-center\", room.name);\r\n nameCol.setAttribute(\"data-i18n\", \"houseRoomNames.\" + room.hrid);\r\n row.appendChild(nameCol);\r\n\r\n let levelCol = createElement(\"div\", \"col-md-2\");\r\n let levelInput = createHouseInput(room.hrid);\r\n\r\n levelInput.addEventListener(\"input\", function (e) {\r\n let inputValue = e.target.value;\r\n const hrid = e.target.dataset.houseHrid;\r\n player.houseRooms[hrid] = parseInt(inputValue);\r\n });\r\n\r\n levelCol.appendChild(levelInput);\r\n row.appendChild(levelCol);\r\n\r\n newChildren.push(row);\r\n }\r\n\r\n houseRoomsList.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction createHouseInput(hrid) {\r\n let levelInput = document.createElement(\"input\");\r\n levelInput.className = \"form-control\";\r\n levelInput.type = \"number\";\r\n levelInput.placeholder = 0;\r\n levelInput.min = 0;\r\n levelInput.max = 8;\r\n levelInput.step = 1;\r\n levelInput.dataset.houseHrid = hrid;\r\n\r\n return levelInput;\r\n}\r\n\r\nfunction refreshAchievementStatics() {\r\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n for(const tier of Object.values(tierMap)) {\r\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\r\n const done = Array.from(checks).filter(cb => cb.checked).length;\r\n const total = checks.length;\r\n\r\n const stat = document.getElementById(`AchTier${tier.sortIndex}Statics`);\r\n stat.innerText = `(${done}/${total})`;\r\n if (done == total) {\r\n // set to green\r\n stat.classList.remove(\"text-secondary\");\r\n stat.classList.add(\"text-success\");\r\n } else {\r\n // set to secondary\r\n stat.classList.remove(\"text-success\");\r\n stat.classList.add(\"text-secondary\");\r\n }\r\n }\r\n}\r\n\r\nfunction initAchievementsModal(){\r\n let achievementsList = document.getElementById(\"achievementsList\");\r\n let newChildren = [];\r\n player.achievements = {};\r\n\r\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n for(const tier of Object.values(tierMap)) {\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid).sort((a, b) => a.sortIndex - b.sortIndex);\r\n let detailMapCount = detailMap.length;\r\n if (detailMapCount <= 0) continue;\r\n\r\n let card = createElement(\"div\", \"card\");\r\n let cardHeader = createElement(\"div\", \"card-header d-flex align-items-center\");\r\n\r\n let cardTitle = createElement(\"a\", \"btn\", tier.name);\r\n cardTitle.setAttribute(\"data-bs-toggle\",\"collapse\");\r\n cardTitle.setAttribute(\"href\", `#AchTier${tier.sortIndex}`);\r\n cardTitle.setAttribute(\"data-i18n\", \"achievementTierNames.\"+tier.hrid);\r\n cardHeader.appendChild(cardTitle);\r\n\r\n let bufDesc = createElement(\"div\", \"small text-secondary\");\r\n let buffName = createElement(\"i\", \"\");\r\n buffName.setAttribute(\"data-i18n\", \"buffTypeNames.\"+tier[\"buff\"].typeHrid);\r\n bufDesc.appendChild(buffName);\r\n let buffValue = createElement(\"i\", \"\");\r\n buffValue.innerText = \":+\" + parseFloat(tier[\"buff\"].ratioBoost==0?tier[\"buff\"].flatBoost:tier[\"buff\"].ratioBoost)*100 + \"%\";\r\n bufDesc.appendChild(buffValue);\r\n cardHeader.appendChild(bufDesc);\r\n\r\n let cardStatics = createElement(\"div\", \"ms-auto btn\", `(0/${detailMapCount})`);\r\n cardStatics.id = `AchTier${tier.sortIndex}Statics`;\r\n cardStatics.dataset.checked = \"true\";\r\n cardStatics.addEventListener(\"click\", function (e) {\r\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\r\n for (const check of checks) {\r\n check.checked = cardStatics.dataset.checked == \"true\";\r\n const hrid = check.dataset.achievementHrid;\r\n player.achievements[hrid] = check.checked;\r\n }\r\n cardStatics.dataset.checked = cardStatics.dataset.checked == \"true\" ? \"false\" : \"true\";\r\n refreshAchievementStatics();\r\n });\r\n cardHeader.appendChild(cardStatics);\r\n\r\n card.appendChild(cardHeader);\r\n\r\n let cardMain = createElement(\"div\", \"collapse\");\r\n cardMain.id = `AchTier${tier.sortIndex}`;\r\n let cardBody = createElement(\"div\", \"card-body\");\r\n\r\n for (const detail of Object.values(detailMap)) {\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let formCheck = createElement(\"div\", \"form-check\");\r\n let input = createElement(\"input\", \"form-check-input\");\r\n input.setAttribute(\"type\", \"checkbox\");\r\n input.setAttribute(\"data-tier\", tier.sortIndex);\r\n input.id = `AchDetail${detail.sortIndex}`;\r\n input.dataset.achievementHrid = detail.hrid;\r\n input.addEventListener(\"change\", function (e) {\r\n const hrid = e.target.dataset.achievementHrid;\r\n player.achievements[hrid] = e.target.checked;\r\n\r\n refreshAchievementStatics();\r\n });\r\n formCheck.appendChild(input);\r\n\r\n let name = createElement(\"label\", \"form-check-label\", detail.name);\r\n name.setAttribute(\"data-i18n\", \"achievementNames.\" + detail.hrid);\r\n name.setAttribute(\"for\", `AchDetail${detail.sortIndex}`);\r\n formCheck.appendChild(name);\r\n row.appendChild(formCheck);\r\n cardBody.appendChild(row);\r\n }\r\n cardMain.appendChild(cardBody);\r\n card.appendChild(cardMain);\r\n\r\n newChildren.push(card);\r\n }\r\n\r\n achievementsList.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction initEnhancementLevelInput(equipmentType) {\r\n let inputId = \"inputEquipmentEnhancementLevel_\";\r\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\r\n inputId += \"weapon\";\r\n } else {\r\n inputId += equipmentType;\r\n }\r\n\r\n let inputElement = document.getElementById(inputId);\r\n inputElement.value = 0;\r\n inputElement.addEventListener(\"change\", enhancementLevelInputHandler);\r\n}\r\n\r\nfunction equipmentSelectHandler(event, type) {\r\n let equipmentType = \"/equipment_types/\" + type;\r\n\r\n if (!event.target.value) {\r\n updateEquipmentState();\r\n updateUI();\r\n return;\r\n }\r\n\r\n let gameItem = itemDetailMap[event.target.value];\r\n\r\n // Weapon select has two handlers because of mainhand and twohand weapons. Ignore the handler with the wrong type\r\n if (gameItem.equipmentDetail.type != equipmentType) {\r\n return;\r\n }\r\n\r\n if (type == \"two_hand\") {\r\n document.getElementById(\"selectEquipment_off_hand\").value = \"\";\r\n document.getElementById(\"inputEquipmentEnhancementLevel_off_hand\").value = 0;\r\n }\r\n if (type == \"off_hand\" && player.equipment[\"/equipment_types/two_hand\"]) {\r\n document.getElementById(\"selectEquipment_weapon\").value = \"\";\r\n document.getElementById(\"inputEquipmentEnhancementLevel_weapon\").value = 0;\r\n }\r\n\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\nfunction enhancementLevelInputHandler() {\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\nfunction updateEquipmentState() {\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentType = \"/equipment_types/\" + type;\r\n let selectType = type;\r\n if (type == \"main_hand\" || type == \"two_hand\") {\r\n selectType = \"weapon\";\r\n }\r\n\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + selectType);\r\n let equipmentHrid = equipmentSelect.value;\r\n\r\n if (!equipmentHrid) {\r\n player.equipment[equipmentType] = null;\r\n return;\r\n }\r\n\r\n let gameItem = itemDetailMap[equipmentHrid];\r\n\r\n // Clear old weapon if a weapon of a different type is equipped\r\n if (gameItem.equipmentDetail.type != equipmentType) {\r\n player.equipment[equipmentType] = null;\r\n return;\r\n }\r\n\r\n let enhancementLevel = Number(document.getElementById(\"inputEquipmentEnhancementLevel_\" + selectType).value);\r\n player.equipment[equipmentType] = new Equipment(gameItem.hrid, enhancementLevel);\r\n });\r\n}\r\n\r\ndocument.getElementById(\"selectEquipment_set\").onchange = changeEquipmentSetListener;\r\n\r\nfunction changeEquipmentSetListener() {\r\n let value = this.value\r\n let optgroupType = this.options[this.selectedIndex].parentNode.label;\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\"].forEach((type) => {\r\n let selectType = type;\r\n\r\n let currentEquipment = document.getElementById(\"selectEquipment_\" + selectType);\r\n if (type === \"feet\") {\r\n type = \"_boots\";\r\n }\r\n if (type === \"hands\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_bracers\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_gloves\";\r\n } else {\r\n type = \"_gauntlets\";\r\n }\r\n }\r\n if (type === \"head\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_hood\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_hat\";\r\n } else {\r\n type = \"_helmet\";\r\n }\r\n }\r\n if (type === \"legs\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_chaps\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_robe_bottoms\";\r\n } else {\r\n type = \"_plate_legs\";\r\n }\r\n }\r\n if (type === \"body\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_tunic\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_robe_top\";\r\n } else {\r\n type = \"_plate_body\";\r\n }\r\n }\r\n currentEquipment.value = \"/items/\" + value.toLowerCase() + type;\r\n });\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Combat Stats\r\n\r\nfunction updateCombatStatsUI() {\r\n player.updateCombatDetails();\r\n\r\n let combatStyleElement = document.getElementById(\"combatStat_combatStyleHrid\");\r\n let combatStyle = player.combatDetails.combatStats.combatStyleHrid;\r\n combatStyleElement.setAttribute(\"data-i18n\", \"combatStyleNames.\" + combatStyle);\r\n combatStyleElement.innerHTML = combatStyleDetailMap[combatStyle].name;\r\n\r\n let damageTypeElement = document.getElementById(\"combatStat_damageType\");\r\n let damageType = damageTypeDetailMap[player.combatDetails.combatStats.damageType];\r\n damageTypeElement.setAttribute(\"data-i18n\", \"damageTypeNames.\" + damageType.hrid);\r\n damageTypeElement.innerHTML = damageType.name;\r\n\r\n let attackIntervalElement = document.getElementById(\"combatStat_attackInterval\");\r\n attackIntervalElement.innerHTML = (player.combatDetails.combatStats.attackInterval / 1e9).toLocaleString() + \"s\";\r\n\r\n let primaryTrainingElement = document.getElementById(\"combatStat_primaryTraining\");\r\n let primaryTraining = player.combatDetails.combatStats.primaryTraining;\r\n primaryTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + primaryTraining);\r\n primaryTrainingElement.innerHTML = primaryTraining;\r\n\r\n let focusTrainingElement = document.getElementById(\"combatStat_focusTraining\");\r\n let focusTraining = player.combatDetails.combatStats.focusTraining;\r\n if (focusTraining) {\r\n focusTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + focusTraining);\r\n } else {\r\n focusTrainingElement.setAttribute(\"data-i18n\", \"characterSelectPage.slots.empty\");\r\n }\r\n focusTrainingElement.innerHTML = focusTraining;\r\n\r\n [\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"stabAccuracyRating\",\r\n \"stabMaxDamage\",\r\n \"slashAccuracyRating\",\r\n \"slashMaxDamage\",\r\n \"smashAccuracyRating\",\r\n \"smashMaxDamage\",\r\n \"rangedAccuracyRating\",\r\n \"rangedMaxDamage\",\r\n \"magicAccuracyRating\",\r\n \"magicMaxDamage\",\r\n \"defensiveMaxDamage\",\r\n \"stabEvasionRating\",\r\n \"slashEvasionRating\",\r\n \"smashEvasionRating\",\r\n \"rangedEvasionRating\",\r\n \"magicEvasionRating\",\r\n \"totalArmor\",\r\n \"totalWaterResistance\",\r\n \"totalNatureResistance\",\r\n \"totalFireResistance\",\r\n \"totalThreat\"\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n element.innerHTML = Math.floor(player.combatDetails[stat]);\r\n });\r\n\r\n [\r\n \"abilityHaste\",\r\n \"tenacity\"\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n element.innerHTML = Math.floor(player.combatDetails.combatStats[stat]);\r\n });\r\n\r\n [\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"combatExperience\",\r\n \"taskDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"drinkConcentration\",\r\n \"foodHaste\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\"\r\n\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n let value = (100 * player.combatDetails.combatStats[stat]).toLocaleString([], {\r\n minimumFractionDigits: 0,\r\n maximumFractionDigits: 4,\r\n });\r\n element.innerHTML = value + \"%\";\r\n });\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Level\r\n\r\nfunction initLevelSection() {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n levelInput.value = 1;\r\n levelInput.addEventListener(\"change\", levelInputHandler);\r\n });\r\n}\r\n\r\nfunction levelInputHandler() {\r\n updateLevels();\r\n updateUI();\r\n}\r\n\r\nfunction updateLevels() {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n player[skill + \"Level\"] = Number(levelInput.value);\r\n });\r\n updateCombatLevel();\r\n}\r\n\r\nfunction calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel) {\r\n return Math.floor(\r\n 0.1 * (staminaLevel + intelligenceLevel + attackLevel + defenseLevel + Math.max(meleeLevel, rangedLevel, magicLevel))\r\n + 0.5 * Math.max(attackLevel, defenseLevel, meleeLevel, rangedLevel, magicLevel)\r\n );\r\n}\r\n\r\n\r\nfunction updateCombatLevel() {\r\n let staminaLevel = player[\"staminaLevel\"];\r\n let intelligenceLevel = player[\"intelligenceLevel\"];\r\n let defenseLevel = player[\"defenseLevel\"];\r\n let attackLevel = player[\"attackLevel\"];\r\n let meleeLevel = player[\"meleeLevel\"];\r\n let rangedLevel = player[\"rangedLevel\"];\r\n let magicLevel = player[\"magicLevel\"];\r\n\r\n let levelInput = document.getElementById(\"inputLevel_combat\");\r\n levelInput.value = calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel);;\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Food\r\n\r\nfunction initFoodSection() {\r\n for (let i = 0; i < 3; i++) {\r\n let element = document.getElementById(\"selectFood_\" + i);\r\n\r\n let gameFoods = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/food\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const food of Object.values(gameFoods)) {\r\n let opt = new Option(food.name, food.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + food.hrid);\r\n element.add(opt);\r\n }\r\n\r\n element.addEventListener(\"change\", foodSelectHandler);\r\n }\r\n}\r\n\r\nfunction foodSelectHandler() {\r\n updateFoodState();\r\n updateUI();\r\n}\r\n\r\nfunction updateFoodState() {\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n food[i] = foodSelect.value;\r\n if (food[i] && !triggerMap[food[i]]) {\r\n let gameItem = itemDetailMap[food[i]];\r\n triggerMap[food[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateFoodUI() {\r\n for (let i = 0; i < 3; i++) {\r\n let selectElement = document.getElementById(\"selectFood_\" + i);\r\n let triggerButton = document.getElementById(\"buttonFoodTrigger_\" + i);\r\n\r\n selectElement.disabled = i >= player.combatDetails.combatStats.foodSlots;\r\n triggerButton.disabled = i >= player.combatDetails.combatStats.foodSlots || !food[i];\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Drinks\r\n\r\nfunction initDrinksSection() {\r\n for (let i = 0; i < 3; i++) {\r\n let element = document.getElementById(\"selectDrink_\" + i);\r\n\r\n let gameDrinks = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/drink\")\r\n .filter((item) => item.consumableDetail.usableInActionTypeMap[\"/action_types/combat\"])\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const drink of Object.values(gameDrinks)) {\r\n let opt = new Option(drink.name, drink.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + drink.hrid);\r\n element.add(opt);\r\n }\r\n\r\n element.addEventListener(\"change\", drinkSelectHandler);\r\n }\r\n}\r\n\r\nfunction drinkSelectHandler() {\r\n updateDrinksState();\r\n updateDrinksUI();\r\n}\r\n\r\nfunction updateDrinksState() {\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n drinks[i] = drinkSelect.value;\r\n if (drinks[i] && !triggerMap[drinks[i]]) {\r\n let gameItem = itemDetailMap[drinks[i]];\r\n triggerMap[drinks[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateDrinksUI() {\r\n for (let i = 0; i < 3; i++) {\r\n let selectElement = document.getElementById(\"selectDrink_\" + i);\r\n let triggerButton = document.getElementById(\"buttonDrinkTrigger_\" + i);\r\n\r\n selectElement.disabled = i >= player.combatDetails.combatStats.drinkSlots;\r\n triggerButton.disabled = i >= player.combatDetails.combatStats.drinkSlots || !drinks[i];\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Abilities\r\n\r\nfunction initAbilitiesSection() {\r\n for (let i = 0; i < 5; i++) {\r\n let selectElement = document.getElementById(\"selectAbility_\" + i);\r\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\r\n\r\n inputElement.value = 1;\r\n\r\n let gameAbilities;\r\n if (i == 0) {\r\n gameAbilities = Object.values(abilityDetailMap).filter(x => x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\r\n } else {\r\n gameAbilities = Object.values(abilityDetailMap).filter(x => !x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\r\n }\r\n\r\n\r\n for (const ability of Object.values(gameAbilities)) {\r\n let opt = new Option(ability.name, ability.hrid);\r\n opt.setAttribute(\"data-i18n\", \"abilityNames.\" + ability.hrid);\r\n selectElement.add(opt);\r\n }\r\n\r\n selectElement.addEventListener(\"change\", abilitySelectHandler);\r\n }\r\n}\r\n\r\nfunction abilitySelectHandler() {\r\n updateAbilityState();\r\n updateAbilityUI();\r\n}\r\n\r\nfunction updateAbilityState() {\r\n for (let i = 0; i < 5; i++) {\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\r\n abilities[i] = abilitySelect.value;\r\n if (abilities[i] && !triggerMap[abilities[i]]) {\r\n let gameAbility = abilityDetailMap[abilities[i]];\r\n triggerMap[abilities[i]] = structuredClone(gameAbility.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateAbilityUI() {\r\n for (let i = 0; i < 5; i++) {\r\n let selectElement = document.getElementById(\"selectAbility_\" + i);\r\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let triggerButton = document.getElementById(\"buttonAbilityTrigger_\" + i);\r\n\r\n selectElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\r\n inputElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\r\n triggerButton.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1] || !abilities[i];\r\n let moveUpButton = document.getElementById(\"selectAbilityMoveUp_\" + i);\r\n moveUpButton.onclick = () => swapAbilityOrder(i, -1);\r\n }\r\n}\r\n\r\nfunction swapAbilityOrder(abilityIndex, step) {\r\n const swapIndex = abilityIndex + step;\r\n if (swapIndex < 0 || swapIndex > 4) {\r\n return;\r\n }\r\n\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilityIndex);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilityIndex);\r\n\r\n const tempAbility = abilities[abilityIndex];\r\n abilities[abilityIndex] = abilities[swapIndex];\r\n abilities[swapIndex] = tempAbility;\r\n\r\n const tempLevel = abilityLevelInput.value;\r\n abilityLevelInput.value = document.getElementById(\"inputAbilityLevel_\" + swapIndex).value;\r\n document.getElementById(\"inputAbilityLevel_\" + swapIndex).value = tempLevel;\r\n\r\n abilitySelect.value = document.getElementById(\"selectAbility_\" + (swapIndex)).value;\r\n document.getElementById(\"selectAbility_\" + swapIndex).value = abilities[swapIndex];\r\n\r\n updateAbilityState();\r\n updateAbilityUI();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Trigger\r\n\r\nfunction initTriggerModal() {\r\n let modal = document.getElementById(\"triggerModal\");\r\n modal.addEventListener(\"show.bs.modal\", (event) => triggerModalShownHandler(event));\r\n\r\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\r\n triggerSaveButton.addEventListener(\"click\", (event) => triggerModalSaveHandler(event));\r\n\r\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\r\n triggerAddButton.addEventListener(\"click\", (event) => triggerAddButtonHandler(event));\r\n\r\n let triggerDefaultButton = document.getElementById(\"buttonDefaultTrigger\");\r\n triggerDefaultButton.addEventListener(\"click\", (event) => triggerDefaultButtonHandler(event));\r\n\r\n for (let i = 0; i < 4; i++) {\r\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\r\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\r\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\r\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\r\n let triggerRemoveButton = document.getElementById(\"buttonRemoveTrigger_\" + i);\r\n\r\n triggerDependencySelect.addEventListener(\"change\", (event) => triggerDependencySelectHandler(event, i));\r\n triggerConditionSelect.addEventListener(\"change\", (event) => triggerConditionSelectHandler(event, i));\r\n triggerComparatorSelect.addEventListener(\"change\", (event) => triggerComparatorSelectHander(event, i));\r\n triggerValueInput.addEventListener(\"change\", (event) => triggerValueInputHandler(event, i));\r\n triggerRemoveButton.addEventListener(\"click\", (event) => triggerRemoveButtonHandler(event, i));\r\n }\r\n}\r\n\r\nfunction triggerModalShownHandler(event) {\r\n let triggerButton = event.relatedTarget;\r\n\r\n let triggerType = triggerButton.getAttribute(\"data-bs-triggertype\");\r\n let triggerIndex = Number(triggerButton.getAttribute(\"data-bs-triggerindex\"));\r\n\r\n let triggerTarget;\r\n switch (triggerType) {\r\n case \"food\":\r\n triggerTarget = food[triggerIndex];\r\n break;\r\n case \"drink\":\r\n triggerTarget = drinks[triggerIndex];\r\n break;\r\n case \"ability\":\r\n triggerTarget = abilities[triggerIndex];\r\n break;\r\n }\r\n\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n triggerTargetnput.value = triggerTarget;\r\n modalTriggers = triggerMap[triggerTarget];\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerModalSaveHandler(event) {\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n let triggerTarget = triggerTargetnput.value;\r\n\r\n triggerMap[triggerTarget] = modalTriggers;\r\n}\r\n\r\nfunction triggerDependencySelectHandler(event, index) {\r\n modalTriggers[index].dependencyHrid = event.target.value;\r\n modalTriggers[index].conditionHrid = \"\";\r\n modalTriggers[index].comparatorHrid = \"\";\r\n modalTriggers[index].value = 0;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerConditionSelectHandler(event, index) {\r\n modalTriggers[index].conditionHrid = event.target.value;\r\n modalTriggers[index].comparatorHrid = \"\";\r\n modalTriggers[index].value = 0;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerComparatorSelectHander(event, index) {\r\n modalTriggers[index].comparatorHrid = event.target.value;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerValueInputHandler(event, index) {\r\n modalTriggers[index].value = Number(event.target.value);\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerRemoveButtonHandler(event, index) {\r\n modalTriggers.splice(index, 1);\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerAddButtonHandler(event) {\r\n if (modalTriggers.length == 4) {\r\n return;\r\n }\r\n\r\n modalTriggers.push({\r\n dependencyHrid: \"\",\r\n conditionHrid: \"\",\r\n comparatorHrid: \"\",\r\n value: 0,\r\n });\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerDefaultButtonHandler(event) {\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n let triggerTarget = triggerTargetnput.value;\r\n\r\n if (triggerTarget.startsWith(\"/items/\")) {\r\n modalTriggers = structuredClone(itemDetailMap[triggerTarget].consumableDetail.defaultCombatTriggers);\r\n } else {\r\n modalTriggers = structuredClone(abilityDetailMap[triggerTarget].defaultCombatTriggers);\r\n }\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction updateTriggerModal() {\r\n let triggerStartTextElement = document.getElementById(\"triggerStartText\");\r\n if (modalTriggers.length == 0) {\r\n triggerStartTextElement.innerHTML = \"Activate as soon as it's off cooldown\";\r\n } else {\r\n triggerStartTextElement.innerHTML = \"Activate when:\";\r\n }\r\n\r\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\r\n triggerAddButton.disabled = modalTriggers.length == 4;\r\n\r\n let triggersValid = true;\r\n\r\n for (let i = 0; i < 4; i++) {\r\n let triggerElement = document.getElementById(\"modalTrigger_\" + i);\r\n\r\n if (!modalTriggers[i]) {\r\n hideElement(triggerElement);\r\n continue;\r\n }\r\n\r\n showElement(triggerElement);\r\n\r\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\r\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\r\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\r\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\r\n\r\n showElement(triggerDependencySelect);\r\n fillTriggerDependencySelect(triggerDependencySelect);\r\n\r\n if (modalTriggers[i].dependencyHrid == \"\") {\r\n hideElement(triggerConditionSelect);\r\n hideElement(triggerComparatorSelect);\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerDependencySelect.value = modalTriggers[i].dependencyHrid;\r\n showElement(triggerConditionSelect);\r\n fillTriggerConditionSelect(triggerConditionSelect, modalTriggers[i].dependencyHrid);\r\n\r\n if (modalTriggers[i].conditionHrid == \"\") {\r\n hideElement(triggerComparatorSelect);\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerConditionSelect.value = modalTriggers[i].conditionHrid;\r\n showElement(triggerComparatorSelect);\r\n fillTriggerComparatorSelect(triggerComparatorSelect, modalTriggers[i].conditionHrid);\r\n\r\n if (modalTriggers[i].comparatorHrid == \"\") {\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerComparatorSelect.value = modalTriggers[i].comparatorHrid;\r\n\r\n if (combatTriggerComparatorDetailMap[modalTriggers[i].comparatorHrid].allowValue) {\r\n showElement(triggerValueInput);\r\n triggerValueInput.value = modalTriggers[i].value;\r\n } else {\r\n hideElement(triggerValueInput);\r\n }\r\n }\r\n\r\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\r\n triggerSaveButton.disabled = !triggersValid;\r\n\r\n updateContent();\r\n}\r\n\r\nfunction fillTriggerDependencySelect(element) {\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const dependency of Object.values(combatTriggerDependencyDetailMap).sort(\r\n (a, b) => a.sortIndex - b.sortIndex\r\n )) {\r\n let opt = new Option(dependency.name, dependency.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerDependencyNames.\" + dependency.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction fillTriggerConditionSelect(element, dependencyHrid) {\r\n let dependency = combatTriggerDependencyDetailMap[dependencyHrid];\r\n\r\n let conditions;\r\n if (dependency.isSingleTarget) {\r\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isSingleTarget);\r\n } else {\r\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isMultiTarget);\r\n }\r\n\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const condition of Object.values(conditions).sort((a, b) => a.sortIndex - b.sortIndex)) {\r\n let opt = new Option(condition.name, condition.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerConditionNames.\" + condition.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction fillTriggerComparatorSelect(element, conditionHrid) {\r\n let condition = combatTriggerConditionDetailMap[conditionHrid];\r\n\r\n let comparators = condition.allowedComparatorHrids.map((hrid) => combatTriggerComparatorDetailMap[hrid]);\r\n\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const comparator of Object.values(comparators).sort((a, b) => a.sortIndex - b.sortIndex)) {\r\n let opt = new Option(comparator.name, comparator.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerComparatorNames.\" + comparator.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction hideElement(element) {\r\n element.classList.remove(\"d-flex\");\r\n element.classList.add(\"d-none\");\r\n}\r\n\r\nfunction showElement(element) {\r\n element.classList.remove(\"d-none\");\r\n element.classList.add(\"d-flex\");\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Zones\r\n\r\nfunction initZones() {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n\r\n // TOOD dungeon wave spawns\r\n let gameZones = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const zone of Object.values(gameZones)) {\r\n let opt = new Option(zone.name, zone.hrid);\r\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + zone.hrid);\r\n zoneSelect.add(opt);\r\n }\r\n\r\n\r\n let zoneCheckBox = document.getElementById(\"zoneCheckBox\");\r\n let checkAllZonesToggle = document.getElementById('checkAllZones');\r\n\r\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\r\n simAllZonesToggle.addEventListener(\"change\", (event) => {\r\n if (simAllZonesToggle.checked) {\r\n zoneCheckBox.classList.remove(\"d-none\");\r\n zoneCheckBox.querySelectorAll(\".zone-checkbox\").forEach(checkbox => checkbox.checked = true);\r\n checkAllZonesToggle.checked = true;\r\n } else {\r\n zoneCheckBox.classList.add(\"d-none\");\r\n }\r\n });\r\n\r\n let zoneHrids = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .flat();\r\n\r\n for (const zoneHrid of zoneHrids) {\r\n const newZone = document.createElement('div');\r\n newZone.classList.add('form-check');\r\n newZone.innerHTML = `\r\n \r\n \r\n `;\r\n zoneCheckBox.append(newZone);\r\n }\r\n\r\n let checkZoneToggles = document.querySelectorAll('.zone-checkbox');\r\n checkAllZonesToggle.addEventListener('change', () => {\r\n checkZoneToggles.forEach(cb => cb.checked = checkAllZonesToggle.checked);\r\n });\r\n\r\n checkZoneToggles.forEach(cb =>\r\n cb.addEventListener('change', () => {\r\n checkAllZonesToggle.checked = [...checkZoneToggles].every(x => x.checked);\r\n })\r\n );\r\n\r\n\r\n let soloCheckBox = document.getElementById(\"soloCheckBox\");\r\n let checkAllSolosToggle = document.getElementById('checkAllSolos');\r\n\r\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\r\n simAllSoloToggle.addEventListener(\"change\", (event) => {\r\n if (simAllSoloToggle.checked) {\r\n soloCheckBox.classList.remove(\"d-none\");\r\n soloCheckBox.querySelectorAll(\".solo-checkbox\").forEach(checkbox => checkbox.checked = true);\r\n checkAllSolosToggle.checked = true;\r\n } else {\r\n soloCheckBox.classList.add(\"d-none\");\r\n }\r\n });\r\n\r\n let soloHrids = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount == 1)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .flat();\r\n\r\n for (const zoneHrid of soloHrids) {\r\n const newZone = document.createElement('div');\r\n newZone.classList.add('form-check');\r\n newZone.innerHTML = `\r\n \r\n \r\n `;\r\n soloCheckBox.append(newZone);\r\n }\r\n\r\n let checkSoloToggles = document.querySelectorAll('.solo-checkbox');\r\n checkAllSolosToggle.addEventListener('change', () => {\r\n checkSoloToggles.forEach(cb => cb.checked = checkAllSolosToggle.checked);\r\n });\r\n\r\n checkSoloToggles.forEach(cb =>\r\n cb.addEventListener('change', () => {\r\n checkAllSolosToggle.checked = [...checkSoloToggles].every(x => x.checked);\r\n })\r\n );\r\n}\r\n\r\nfunction initDungeons() {\r\n let dungeonSelect = document.getElementById(\"selectDungeon\");\r\n\r\n let gameDungeons = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category == \"/action_categories/combat/dungeons\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const dungeon of Object.values(gameDungeons)) {\r\n let opt = new Option(dungeon.name, dungeon.hrid);\r\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + dungeon.hrid);\r\n dungeonSelect.add(opt);\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Simulation Result\r\n\r\nfunction createDamageDoneAccordion(enemyIndex) {\r\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageDoneAccordionEnemy${enemyIndex}`);\r\n\r\n const colDiv = createElement('div', 'col');\r\n const accordionMainDiv = createElement('div', 'accordion');\r\n const accordionItemDiv = createElement('div', 'accordion-item');\r\n\r\n const headerH2 = createElement('h2', 'accordion-header');\r\n const button = createElement('button', 'accordion-button collapsed',\r\n `Damage Done (Enemy ${enemyIndex})`,\r\n `buttonSimulationResultDamageDoneAccordionEnemy${enemyIndex}`\r\n );\r\n button.setAttribute('type', 'button');\r\n button.setAttribute('data-bs-toggle', 'collapse');\r\n button.setAttribute('data-bs-target', `#collapseDamageDone${enemyIndex}`);\r\n button.style.padding = '0.5em';\r\n\r\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageDone${enemyIndex}`);\r\n const accordionBodyDiv = createElement('div', 'accordion-body');\r\n\r\n const headerRow = createElement('div', 'row');\r\n headerRow.innerHTML = `\r\n
Source
\r\n
Hitchance
\r\n
DPS
\r\n
%
\r\n `;\r\n\r\n const resultDiv = createElement('div', '', '', `simulationResultDamageDoneEnemy${enemyIndex}`);\r\n\r\n accordionBodyDiv.appendChild(headerRow);\r\n accordionBodyDiv.appendChild(resultDiv);\r\n collapseDiv.appendChild(accordionBodyDiv);\r\n headerH2.appendChild(button);\r\n accordionItemDiv.appendChild(headerH2);\r\n accordionItemDiv.appendChild(collapseDiv);\r\n accordionMainDiv.appendChild(accordionItemDiv);\r\n colDiv.appendChild(accordionMainDiv);\r\n accordionDiv.appendChild(colDiv);\r\n\r\n return accordionDiv;\r\n}\r\nfunction createDamageTakenAccordion(enemyIndex) {\r\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageTakenAccordionEnemy${enemyIndex}`);\r\n\r\n const colDiv = createElement('div', 'col');\r\n const accordionMainDiv = createElement('div', 'accordion');\r\n const accordionItemDiv = createElement('div', 'accordion-item');\r\n\r\n const headerH2 = createElement('h2', 'accordion-header');\r\n const button = createElement('button', 'accordion-button collapsed',\r\n `Damage Taken (Enemy ${enemyIndex})`,\r\n `buttonSimulationResultDamageTakenAccordionEnemy${enemyIndex}`\r\n );\r\n button.setAttribute('type', 'button');\r\n button.setAttribute('data-bs-toggle', 'collapse');\r\n button.setAttribute('data-bs-target', `#collapseDamageTaken${enemyIndex}`);\r\n button.style.padding = '0.5em';\r\n\r\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageTaken${enemyIndex}`);\r\n const accordionBodyDiv = createElement('div', 'accordion-body');\r\n\r\n const headerRow = createElement('div', 'row');\r\n headerRow.innerHTML = `\r\n
Source
\r\n
Hitchance
\r\n
DPS
\r\n
%
\r\n `;\r\n\r\n const resultDiv = createElement('div', '', '', `simulationResultDamageTakenEnemy${enemyIndex}`);\r\n\r\n accordionBodyDiv.appendChild(headerRow);\r\n accordionBodyDiv.appendChild(resultDiv);\r\n collapseDiv.appendChild(accordionBodyDiv);\r\n headerH2.appendChild(button);\r\n accordionItemDiv.appendChild(headerH2);\r\n accordionItemDiv.appendChild(collapseDiv);\r\n accordionMainDiv.appendChild(accordionItemDiv);\r\n colDiv.appendChild(accordionMainDiv);\r\n accordionDiv.appendChild(colDiv);\r\n\r\n return accordionDiv;\r\n}\r\n\r\n\r\nfunction initDamageDoneTaken() {\r\n for (let i = 64; i > 0; i--) {\r\n document.getElementById(\"simulationResultTotalDamageDone\").insertAdjacentElement('afterend', createDamageDoneAccordion(i));\r\n document.getElementById(\"simulationResultTotalDamageTaken\").insertAdjacentElement('afterend', createDamageTakenAccordion(i));\r\n }\r\n}\r\n\r\nfunction showSimulationResult(simResult) {\r\n currentSimResults = simResult;\r\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\r\n expensesModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\r\n revenueModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let noRngRevenueModalTable = document.querySelector(\"#noRngRevenueTable > tbody\");\r\n noRngRevenueModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let playerToDisplay = \"player1\";\r\n if (selectedPlayers.includes(parseInt(currentPlayerTabId))) {\r\n playerToDisplay = \"player\" + currentPlayerTabId;\r\n }\r\n if (!simResult.dropRateMultiplier[playerToDisplay]) {\r\n return;\r\n }\r\n\r\n showKills(simResult, playerToDisplay);\r\n showDeaths(simResult, playerToDisplay);\r\n showExperienceGained(simResult, playerToDisplay);\r\n showConsumablesUsed(simResult, playerToDisplay);\r\n showHpSpent(simResult, playerToDisplay);\r\n showManaUsed(simResult, playerToDisplay);\r\n showHitpointsGained(simResult, playerToDisplay);\r\n showManapointsGained(simResult, playerToDisplay);\r\n showDamageDone(simResult, playerToDisplay);\r\n showDamageTaken(simResult, playerToDisplay);\r\n renderWipeEvents(simResult);\r\n window.profit = window.revenue - window.expenses;\r\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\r\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\r\n window.noRngProfit = window.noRngRevenue - window.expenses;\r\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\r\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\r\n}\r\n\r\nfunction showAllSimulationResults(simResults) {\r\n let displaySimResults = manipulateSimResultsDataForDisplay(simResults);\r\n updateAllSimsModal(displaySimResults);\r\n}\r\n\r\nfunction manipulateSimResultsDataForDisplay(simResults) {\r\n let displaySimResults = [];\r\n for (let i = 0; i < simResults.length; i++) {\r\n for (let j = 0; j < selectedPlayers.length; j++) {\r\n let playerToDisplay = \"player\" + selectedPlayers[j].toString();\r\n let simResult = simResults[i];\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let zoneName = simResult.zoneName;\r\n let difficultyTier = simResult.difficultyTier;\r\n let encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\r\n let playerDeaths = simResult.deaths[playerToDisplay] ?? 0;\r\n let deathsPerHour = (playerDeaths / hoursSimulated).toFixed(2);\r\n\r\n let totalExperience = 0;\r\n if (simResult.experienceGained[playerToDisplay]) {\r\n totalExperience = Object.values(simResult.experienceGained[playerToDisplay]).reduce((prev, cur) => prev + cur, 0);\r\n }\r\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\r\n\r\n let experiencePerHour = {};\r\n const skills = [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"];\r\n skills.forEach((skill) => {\r\n const skillLower = skill.toLowerCase();\r\n let experience = simResult.experienceGained[playerToDisplay]?.[skillLower] ?? 0;\r\n let experiencePerHourValue = 0;\r\n if (experience != 0) {\r\n experiencePerHourValue = (experience / hoursSimulated).toFixed(0);\r\n }\r\n experiencePerHour[skill] = experiencePerHourValue;\r\n });\r\n getDropProfit(simResult, playerToDisplay);\r\n let noRngRevenue = simResult[\"noRngRevenue\"];\r\n let noRngProfit = simResult[\"noRngProfit\"];\r\n let expenses = simResult[\"expenses\"];\r\n\r\n let displaySimRow = {\r\n \"ZoneName\": zoneName, \"DifficultyTier\": difficultyTier, \"Player\": playerToDisplay, \"Encounters\": encountersPerHour, \"Deaths\": deathsPerHour,\r\n \"TotalExperience\": totalExperiencePerHour, \"Stamina\": experiencePerHour[\"Stamina\"],\r\n \"Intelligence\": experiencePerHour[\"Intelligence\"], \"Attack\": experiencePerHour[\"Attack\"],\r\n \"Magic\": experiencePerHour[\"Magic\"], \"Ranged\": experiencePerHour[\"Ranged\"],\r\n \"Melee\": experiencePerHour[\"Melee\"], \"Defense\": experiencePerHour[\"Defense\"],\r\n \"noRngRevenue\": noRngRevenue,\r\n \"expenses\": expenses,\r\n \"noRngProfit\": noRngProfit\r\n };\r\n displaySimResults.push(displaySimRow);\r\n }\r\n }\r\n return displaySimResults;\r\n}\r\n\r\nfunction fidDropAmount(dropAmount) {\r\n if (Number.isInteger(dropAmount)) return dropAmount;\r\n\r\n const intPart = Math.floor(dropAmount);\r\n const fracPart = dropAmount - intPart;\r\n return Math.random() < fracPart ? intPart + 1 : intPart;\r\n}\r\n\r\nfunction calcDropMaps(simResult, playerToDisplay) {\r\n let dropRateMultiplier = simResult.dropRateMultiplier[playerToDisplay];\r\n let rareFindMultiplier = simResult.rareFindMultiplier[playerToDisplay];\r\n let combatDropQuantity = simResult.combatDropQuantity[playerToDisplay];\r\n let debuffOnLevelGap = simResult.debuffOnLevelGap[playerToDisplay];\r\n\r\n let numberOfPlayers = simResult.numberOfPlayers;\r\n let monsters = Object.keys(simResult.deaths)\r\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\r\n .sort();\r\n\r\n const totalDropMap = new Map();\r\n const noRngTotalDropMap = new Map();\r\n for (const monster of monsters) {\r\n const dropMap = new Map();\r\n const rareDropMap = new Map();\r\n if (combatMonsterDetailMap[monster].dropTable) {\r\n for (const drop of combatMonsterDetailMap[monster].dropTable) {\r\n if (drop.minDifficultyTier > simResult.difficultyTier) {\r\n continue;\r\n }\r\n\r\n let multiplier = 1.0 + 0.1 * simResult.difficultyTier;\r\n let dropRate = Math.min(1.0, multiplier * (drop.dropRate + (drop.dropRatePerDifficultyTier ?? 0) * simResult.difficultyTier));\r\n if (dropRate <= 0) continue;\r\n\r\n dropMap.set(drop.itemHrid, { \"dropRate\": Math.min(1.0, dropRate * dropRateMultiplier), \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\r\n }\r\n if (combatMonsterDetailMap[monster].rareDropTable)\r\n for (const drop of combatMonsterDetailMap[monster].rareDropTable) {\r\n if (drop.minDifficultyTier > simResult.difficultyTier) {\r\n continue;\r\n }\r\n rareDropMap.set(drop.itemHrid, { \"dropRate\": drop.dropRate * rareFindMultiplier, \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\r\n }\r\n\r\n for (let dropObject of dropMap.values()) {\r\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\r\n\r\n }\r\n for (let dropObject of rareDropMap.values()) {\r\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\r\n }\r\n\r\n for (let i = 0; i < simResult.deaths[monster]; i++) {\r\n for (let dropObject of dropMap.values()) {\r\n let chance = Math.random();\r\n if (chance <= dropObject.dropRate / numberOfPlayers) {\r\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\r\n dropObject.number = dropObject.number + fidDropAmount(amount);\r\n }\r\n }\r\n for (let dropObject of rareDropMap.values()) {\r\n let chance = Math.random();\r\n if (chance <= dropObject.dropRate / numberOfPlayers) {\r\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\r\n dropObject.number = dropObject.number + fidDropAmount(amount);\r\n }\r\n }\r\n }\r\n for (let [name, dropObject] of dropMap.entries()) {\r\n if (totalDropMap.has(name)) {\r\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\r\n } else {\r\n totalDropMap.set(name, dropObject.number);\r\n }\r\n if (noRngTotalDropMap.has(name)) {\r\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\r\n } else {\r\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\r\n }\r\n }\r\n for (let [name, dropObject] of rareDropMap.entries()) {\r\n if (totalDropMap.has(name)) {\r\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\r\n } else {\r\n totalDropMap.set(name, dropObject.number);\r\n }\r\n if (noRngTotalDropMap.has(name)) {\r\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\r\n } else {\r\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return { totalDropMap, noRngTotalDropMap };\r\n}\r\n\r\nfunction getDropProfit(simResult, playerToDisplay) {\r\n let { totalDropMap, noRngTotalDropMap } = calcDropMaps(simResult, playerToDisplay);\r\n\r\n let noRngTotal = 0;\r\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\r\n let price = -1;\r\n let revenueSetting = document.getElementById('selectPrices_drops').value;\r\n if (window.prices) {\r\n let item = window.prices[name];\r\n if (item) {\r\n if (revenueSetting == 'bid') {\r\n if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n } else if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n }\r\n } else if (revenueSetting == 'ask') {\r\n if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n } else if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n }\r\n }\r\n if (price == -1) {\r\n price = item['vendor'];\r\n }\r\n }\r\n }\r\n noRngTotal += price * dropAmount;\r\n }\r\n\r\n let consumablesUsed = simResult.consumablesUsed?.[playerToDisplay];\r\n\r\n if (consumablesUsed) {\r\n consumablesUsed = Object.entries(consumablesUsed).sort((a, b) => b[1] - a[1]);\r\n } else {\r\n consumablesUsed = [];\r\n }\r\n\r\n let expenses = 0;\r\n for (const [consumable, amount] of consumablesUsed) {\r\n let price = -1;\r\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\r\n if (window.prices) {\r\n let item = window.prices[consumable];\r\n if (item) {\r\n if (expensesSetting == 'bid') {\r\n if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n } else if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n }\r\n } else if (expensesSetting == 'ask') {\r\n if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n } else if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n }\r\n }\r\n if (price == -1) {\r\n price = item['vendor'];\r\n }\r\n }\r\n }\r\n expenses += price * amount;\r\n }\r\n\r\n simResult[\"noRngRevenue\"] = (noRngTotal).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n simResult[\"expenses\"] = (expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n simResult[\"noRngProfit\"] = (noRngTotal - expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n}\r\n\r\nfunction updateAllSimsModal(data) {\r\n const tableBody = document.getElementById('allZonesData').getElementsByTagName('tbody')[0];\r\n tableBody.innerHTML = '';\r\n data.forEach(item => {\r\n const row = document.createElement('tr');\r\n\r\n Object.keys(item).forEach(key => {\r\n const cell = document.createElement('td');\r\n cell.textContent = item[key];\r\n if (key === 'ZoneName') {\r\n cell.setAttribute(\"data-i18n\", \"actionNames.\" + item[key]);\r\n }\r\n row.appendChild(cell);\r\n });\r\n\r\n tableBody.appendChild(row);\r\n });\r\n\r\n const table = document.getElementById('allZonesData');\r\n const rows = table.getElementsByTagName('tr');\r\n const numCols = rows[0].cells.length;\r\n\r\n // 遍历每一列\r\n for (let col = 5; col < numCols; col++) {\r\n let max = -Infinity;\r\n let maxCell = null;\r\n\r\n // 找到最大值及其单元格\r\n for (let row = 1; row < rows.length; row++) {\r\n const cell = rows[row].cells[col];\r\n const value = parseFloat(cell.textContent.replace(/,/g, ''));\r\n if (value > max) {\r\n max = value;\r\n maxCell = cell;\r\n }\r\n }\r\n\r\n // 将最大值单元格的背景色设置为绿色\r\n if (maxCell && max != 0) {\r\n maxCell.style.backgroundColor = 'green';\r\n maxCell.style.color = 'white'; // 设置文字颜色为白色以提高可读性\r\n }\r\n }\r\n}\r\n\r\nlet currentSortColumn = null;\r\nlet currentSortDirection = 'desc';\r\n\r\nfunction sortTable(tableId, columnIndex, direction) {\r\n const table = document.getElementById(tableId);\r\n const tbody = table.querySelector('tbody');\r\n const rows = Array.from(tbody.querySelectorAll('tr'));\r\n\r\n const sortedRows = rows.sort((rowA, rowB) => {\r\n const cellA = rowA.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\r\n const cellB = rowB.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\r\n\r\n const valueA = parseFloat(cellA.replace(/,/g, ''));\r\n const valueB = parseFloat(cellB.replace(/,/g, ''));\r\n\r\n return direction === 'asc' ? valueA - valueB : valueB - valueA;\r\n });\r\n\r\n sortedRows.forEach(row => tbody.appendChild(row));\r\n updateSortIndicators(tableId, columnIndex, direction);\r\n}\r\n\r\nfunction updateSortIndicators(tableId, columnIndex, direction) {\r\n const headers = document.querySelectorAll(`#${tableId} th`);\r\n headers.forEach((header, index) => {\r\n header.classList.remove('sort-asc', 'sort-desc');\r\n if (index === columnIndex) {\r\n header.classList.add(direction === 'asc' ? 'sort-asc' : 'sort-desc');\r\n }\r\n });\r\n}\r\n\r\ndocument.querySelectorAll('#allZonesData th').forEach((header, index) => {\r\n if (index === 0) return;\r\n if (index === 1) return;\r\n if (index === 2) return;\r\n\r\n header.addEventListener('click', () => {\r\n if (currentSortColumn === index) {\r\n currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';\r\n } else {\r\n currentSortColumn = index;\r\n currentSortDirection = 'desc';\r\n }\r\n sortTable('allZonesData', currentSortColumn, currentSortDirection);\r\n });\r\n});\r\n\r\ndocument.getElementById('buttonExportResults').addEventListener('click', function () {\r\n var table = document.getElementById('allZonesData');\r\n var csv = [];\r\n var rows = table.querySelectorAll('tr');\r\n\r\n for (var i = 0; i < rows.length; i++) {\r\n var row = rows[i];\r\n var cols = row.querySelectorAll('th, td');\r\n var csvRow = [];\r\n\r\n cols.forEach(function (col) {\r\n csvRow.push('\"' + col.innerText.replace(/\"/g, '\"\"') + '\"');\r\n });\r\n\r\n csv.push(csvRow.join(','));\r\n }\r\n\r\n var csvFile = new Blob([csv.join('\\n')], { type: 'text/csv' });\r\n var downloadLink = document.createElement('a');\r\n downloadLink.download = 'simData.csv';\r\n downloadLink.href = URL.createObjectURL(csvFile);\r\n downloadLink.style.display = 'none';\r\n document.body.appendChild(downloadLink);\r\n downloadLink.click();\r\n document.body.removeChild(downloadLink);\r\n});\r\n\r\nfunction showKills(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultKills\");\r\n let dropsResultDiv = document.getElementById(\"simulationResultDrops\");\r\n let noRngDropsResultDiv = document.getElementById(\"noRngDrops\");\r\n let newChildren = [];\r\n let newDropChildren = [];\r\n let newNoRngDropChildren = [];\r\n\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let encountersPerHour = 0;\r\n let encountersRow = null;\r\n if (simResult.isDungeon) {\r\n let wavesCompletedRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Wave Reached\", simResult.maxWaveReached]);\r\n wavesCompletedRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxWaveReached\");\r\n newChildren.push(wavesCompletedRow);\r\n let completedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Completed Dungeons\", simResult.dungeonsCompleted]);\r\n completedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsCompleted\");\r\n newChildren.push(completedDungeonsRow);\r\n if (simResult.dungeonsFailed > 0) {\r\n let failedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Failed Dungeons\", simResult.dungeonsFailed]);\r\n failedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsFailed\");\r\n newChildren.push(failedDungeonsRow);\r\n }\r\n encountersPerHour = (simResult.dungeonsCompleted / hoursSimulated).toFixed(1);\r\n let averageTime = (hoursSimulated * 60 / simResult.dungeonsCompleted).toFixed(1);\r\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Average Time\", averageTime]);\r\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.averageTime\");\r\n } else {\r\n encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\r\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Encounters\", encountersPerHour]);\r\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.encounters\");\r\n }\r\n\r\n if (simResult.maxEnrageStack > 0) {\r\n let enrageRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Enrage Stack\", simResult.maxEnrageStack]);\r\n enrageRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxEnrageStack\");\r\n newChildren.push(enrageRow);\r\n }\r\n\r\n if (simResult.debuffOnLevelGap[playerToDisplay] != 0) {\r\n let debuffOnLevelGapRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Debuff on Level Gap\", Math.round(simResult.debuffOnLevelGap[playerToDisplay] * 100) + \"%\"]);\r\n debuffOnLevelGapRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.debuffOnLevelGap\");\r\n newChildren.push(debuffOnLevelGapRow);\r\n }\r\n\r\n newChildren.push(encountersRow);\r\n\r\n Object.keys(simResult.deaths)\r\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\r\n .sort()\r\n .forEach(monster => {\r\n let killsPerHour = (simResult.deaths[monster] / hoursSimulated).toFixed(1);\r\n let monsterRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [combatMonsterDetailMap[monster].name, killsPerHour]\r\n );\r\n monsterRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + monster);\r\n newChildren.push(monsterRow);\r\n });\r\n\r\n let { totalDropMap, noRngTotalDropMap } = !simResult.isDungeon ? calcDropMaps(simResult, playerToDisplay) : {totalDropMap:new Map(), noRngTotalDropMap:new Map()};\r\n\r\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\r\n let total = 0;\r\n for (let [name, dropAmount] of totalDropMap.entries()) {\r\n let dropRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [name, dropAmount.toLocaleString()]\r\n );\r\n dropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\r\n newDropChildren.push(dropRow);\r\n\r\n let tableRow = ' tbody\");\r\n let noRngTotal = 0;\r\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\r\n let noRngDropRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [name, dropAmount.toLocaleString()]\r\n );\r\n noRngDropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\r\n newNoRngDropChildren.push(noRngDropRow);\r\n\r\n let tableRow = ' prev + cur, 0);\r\n }\r\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\r\n let totalRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Total\", totalExperiencePerHour]);\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"].forEach((skill) => {\r\n let experience = simResult.experienceGained[playerToDisplay]?.[skill.toLowerCase()] ?? 0;\r\n if (experience == 0) {\r\n return;\r\n }\r\n let experiencePerHour = (experience / hoursSimulated).toFixed(0);\r\n let experienceRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [skill, experiencePerHour]);\r\n experienceRow.firstElementChild.setAttribute(\"data-i18n\", \"leaderboardCategoryNames.\" + skill.toLowerCase());\r\n newChildren.push(experienceRow);\r\n });\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showHpSpent(simResult, playerToDisplay) {\r\n let hpSpentHeadingDiv = document.getElementById(\"simulationHpSpentHeading\");\r\n hpSpentHeadingDiv.classList.add(\"d-none\");\r\n let hpSpentDiv = document.getElementById(\"simulationHpSpent\");\r\n hpSpentDiv.classList.add(\"d-none\");\r\n\r\n if (simResult.hitpointsSpent[playerToDisplay]) {\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let hpSpentSources = [];\r\n for (const source of Object.keys(simResult.hitpointsSpent[playerToDisplay])) {\r\n let hpSpentPerHour = (simResult.hitpointsSpent[playerToDisplay][source] / hoursSimulated).toFixed(2);\r\n let hpSpentRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [abilityDetailMap[source].name, hpSpentPerHour]);\r\n hpSpentRow.firstElementChild.setAttribute(\"data-i18n\", \"abilityNames.\" + source);\r\n hpSpentSources.push(hpSpentRow);\r\n }\r\n hpSpentDiv.replaceChildren(...hpSpentSources);\r\n hpSpentHeadingDiv.classList.remove(\"d-none\");\r\n hpSpentDiv.classList.remove(\"d-none\");\r\n }\r\n}\r\n\r\nfunction showConsumablesUsed(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultConsumablesUsed\");\r\n let newChildren = [];\r\n\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n\r\n if (!simResult.consumablesUsed[playerToDisplay]) {\r\n resultDiv.replaceChildren(...newChildren);\r\n window.expenses = 0;\r\n return;\r\n }\r\n\r\n let consumablesUsed = Object.entries(simResult.consumablesUsed[playerToDisplay]).sort((a, b) => b[1] - a[1]);\r\n\r\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\r\n let total = 0;\r\n for (const [consumable, amount] of consumablesUsed) {\r\n let consumablesPerHour = (amount / hoursSimulated).toFixed(0);\r\n let consumableRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [itemDetailMap[consumable].name, consumablesPerHour]\r\n );\r\n consumableRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + consumable);\r\n newChildren.push(consumableRow);\r\n\r\n let tableRow = ' b[1] - a[1]);\r\n\r\n let totalHitpointsGained = hitpointsGained.reduce((prev, cur) => prev + cur[1], 0);\r\n let totalHitpointsPerSecond = (totalHitpointsGained / secondsSimulated).toFixed(2);\r\n let totalRow = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [\"Total\", totalHitpointsPerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [source, amount] of hitpointsGained) {\r\n if (amount == 0) {\r\n continue;\r\n }\r\n\r\n let sourceText;\r\n let sourceFullHrid;\r\n switch (source) {\r\n case \"regen\":\r\n sourceText = \"Regen\";\r\n sourceFullHrid = \"combatStats.hpRegenPer10\";\r\n break;\r\n case \"lifesteal\":\r\n sourceText = \"Life Steal\";\r\n sourceFullHrid = \"combatStats.lifeSteal\";\r\n break;\r\n case \"bloom\":\r\n sourceText = \"Bloom\";\r\n sourceFullHrid = \"combatStats.bloom\";\r\n break;\r\n default:\r\n if (itemDetailMap[source]) {\r\n sourceText = itemDetailMap[source].name;\r\n sourceFullHrid = \"itemNames.\" + source;\r\n } else if (abilityDetailMap[source]) {\r\n sourceText = abilityDetailMap[source].name;\r\n sourceFullHrid = \"abilityNames.\" + source;\r\n }\r\n break;\r\n }\r\n let hitpointsPerSecond = (amount / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * amount) / totalHitpointsGained).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [sourceText, hitpointsPerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showManapointsGained(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultManaRestored\");\r\n let newChildren = [];\r\n\r\n let secondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n if (!simResult.manapointsGained[playerToDisplay]) {\r\n resultDiv.replaceChildren(...newChildren);\r\n return;\r\n }\r\n\r\n let manapointsGained = Object.entries(simResult.manapointsGained[playerToDisplay]).sort((a, b) => b[1] - a[1]);\r\n\r\n let totalManapointsGained = manapointsGained.reduce((prev, cur) => prev + cur[1], 0);\r\n let totalManapointsPerSecond = (totalManapointsGained / secondsSimulated).toFixed(2);\r\n let totalRow = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [\"Total\", totalManapointsPerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [source, amount] of manapointsGained) {\r\n if (amount == 0) {\r\n continue;\r\n }\r\n\r\n let sourceText;\r\n let sourceFullHrid;\r\n switch (source) {\r\n case \"regen\":\r\n sourceText = \"Regen\";\r\n sourceFullHrid = \"combatStats.mpRegenPer10\";\r\n break;\r\n case \"manaLeech\":\r\n sourceText = \"Mana Leech\";\r\n sourceFullHrid = \"combatStats.manaLeech\";\r\n break;\r\n case \"ripple\":\r\n sourceText = \"Ripple\";\r\n sourceFullHrid = \"combatStats.ripple\";\r\n break;\r\n default:\r\n sourceText = itemDetailMap[source].name;\r\n sourceFullHrid = \"itemNames.\" + source;\r\n break;\r\n }\r\n let manapointsPerSecond = (amount / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * amount) / totalManapointsGained).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [sourceText, manapointsPerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n let ranOutOfManaText = simResult.playerRanOutOfMana[playerToDisplay] ? \"Yes\" : \"No\";\r\n let ranOutOfManaRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Ran out of mana\", ranOutOfManaText]);\r\n ranOutOfManaRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfMana\");\r\n ranOutOfManaRow.lastElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.\" + ranOutOfManaText);\r\n newChildren.push(ranOutOfManaRow);\r\n\r\n if (simResult.playerRanOutOfMana[playerToDisplay]) {\r\n let ranOutOfManaStat = simResult.playerRanOutOfManaTime[playerToDisplay]; // {isOutOfMana: false, startTimeForOutOfMana:0, totalTimeForOutOfMana:0};\r\n let totalTimeForOut = ranOutOfManaStat.totalTimeForOutOfMana + (ranOutOfManaStat.isOutOfMana ? (simResult.simulatedTime - ranOutOfManaStat.startTimeForOutOfMana) : 0);\r\n\r\n let ranOutOfManaStatRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [\r\n \"Run Out Ratio\",\r\n (totalTimeForOut / simResult.simulatedTime * 100).toFixed(2) + \"%\"\r\n ]\r\n );\r\n ranOutOfManaStatRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfManaRatio\");\r\n newChildren.push(ranOutOfManaStatRow);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showDamageDone(simResult, playerToDisplay) {\r\n let totalDamageDone = {};\r\n let enemyIndex = 1;\r\n\r\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n for (let i = 1; i < 64; i++) {\r\n let accordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + i);\r\n hideElement(accordion);\r\n }\r\n\r\n let bossTimeHeadingDiv = document.getElementById(\"simulationBossTimeHeading\");\r\n bossTimeHeadingDiv.classList.add(\"d-none\");\r\n let bossTimeDiv = document.getElementById(\"simulationBossTime\");\r\n bossTimeDiv.classList.add(\"d-none\");\r\n\r\n if (!simResult.attacks[playerToDisplay]) {\r\n return;\r\n }\r\n\r\n for (const [target, abilities] of Object.entries(simResult.attacks[playerToDisplay])) {\r\n let targetDamageDone = {};\r\n\r\n const i = simResult.timeSpentAlive.findIndex(e => e.name === target);\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\r\n\r\n for (const [ability, abilityCasts] of Object.entries(abilities)) {\r\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\r\n let misses = abilityCasts[\"miss\"] ?? 0;\r\n let damage = Object.entries(abilityCasts)\r\n .filter((entry) => entry[0] != \"miss\")\r\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\r\n\r\n targetDamageDone[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n if (totalDamageDone[ability]) {\r\n totalDamageDone[ability].casts += casts;\r\n totalDamageDone[ability].misses += misses;\r\n totalDamageDone[ability].damage += damage;\r\n } else {\r\n totalDamageDone[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n }\r\n }\r\n\r\n let resultDiv = document.getElementById(\"simulationResultDamageDoneEnemy\" + enemyIndex);\r\n createDamageTable(resultDiv, targetDamageDone, aliveSecondsSimulated);\r\n\r\n let resultAccordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + enemyIndex);\r\n showElement(resultAccordion);\r\n\r\n let resultAccordionButton = document.getElementById(\r\n \"buttonSimulationResultDamageDoneAccordionEnemy\" + enemyIndex\r\n );\r\n let targetName = combatMonsterDetailMap[target].name;\r\n resultAccordionButton.innerHTML = \"Damage Done (\" + \"\" + targetName + \"\" + \")\";\r\n\r\n if (simResult.bossSpawns.includes(target)) {\r\n let hoursSpentOnBoss = (aliveSecondsSimulated / 60 / 60).toFixed(2);\r\n let percentSpentOnBoss = (aliveSecondsSimulated / totalSecondsSimulated * 100).toFixed(2);\r\n\r\n let bossRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [targetName, hoursSpentOnBoss + \"h(\" + percentSpentOnBoss + \"%)\"]);\r\n bossRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + target);\r\n bossTimeDiv.replaceChildren(bossRow);\r\n\r\n bossTimeHeadingDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.classList.remove(\"d-none\");\r\n }\r\n\r\n enemyIndex++;\r\n }\r\n\r\n if (simResult.isDungeon) {\r\n let newChildren = [];\r\n for (const waveName of simResult.bossSpawns) {\r\n // waveName is something like \"#15,/monsters/jackalope,/monsters/butterjerry\"\r\n let waveNumber = waveName.split(\",\")[0];\r\n const idx = simResult.timeSpentAlive.findIndex(e => e.name === waveNumber);\r\n if (idx == -1 || simResult.timeSpentAlive[idx].count == 0) {\r\n continue;\r\n }\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[idx].timeSpentAlive / ONE_SECOND / simResult.timeSpentAlive[idx].count;\r\n let bossRow = createRow([\"col-md-6\", \"col-md-2\", \"col-md-4 text-end\"], [waveNumber, simResult.timeSpentAlive[idx].count, aliveSecondsSimulated.toFixed(1) + \"s\"]);\r\n newChildren.push(bossRow);\r\n }\r\n if (newChildren.length > 0) {\r\n bossTimeHeadingDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.replaceChildren(...newChildren);\r\n }\r\n }\r\n\r\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageDone\");\r\n createDamageTable(totalResultDiv, totalDamageDone, totalSecondsSimulated);\r\n}\r\n\r\nfunction showDamageTaken(simResult, playerToDisplay) {\r\n let totalDamageTaken = {};\r\n let enemyIndex = 1;\r\n\r\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n for (let i = 1; i < 64; i++) {\r\n let accordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + i);\r\n hideElement(accordion);\r\n }\r\n\r\n for (const [source, targets] of Object.entries(simResult.attacks)) {\r\n const validSources = [\"player1\", \"player2\", \"player3\", \"player4\", \"player5\"];\r\n if (validSources.includes(source)) {\r\n continue;\r\n }\r\n const i = simResult.timeSpentAlive.findIndex(e => e.name === source);\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\r\n let sourceDamageTaken = {};\r\n if (targets[playerToDisplay] && Object.keys(targets[playerToDisplay]).length > 0) {\r\n for (const [ability, abilityCasts] of Object.entries(targets[playerToDisplay])) {\r\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\r\n let misses = abilityCasts[\"miss\"] ?? 0;\r\n let damage = Object.entries(abilityCasts)\r\n .filter((entry) => entry[0] != \"miss\")\r\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\r\n\r\n sourceDamageTaken[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n if (totalDamageTaken[ability]) {\r\n totalDamageTaken[ability].casts += casts;\r\n totalDamageTaken[ability].misses += misses;\r\n totalDamageTaken[ability].damage += damage;\r\n } else {\r\n totalDamageTaken[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n }\r\n }\r\n }\r\n\r\n let resultDiv = document.getElementById(\"simulationResultDamageTakenEnemy\" + enemyIndex);\r\n createDamageTable(resultDiv, sourceDamageTaken, aliveSecondsSimulated);\r\n\r\n let resultAccordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + enemyIndex);\r\n showElement(resultAccordion);\r\n\r\n let resultAccordionButton = document.getElementById(\r\n \"buttonSimulationResultDamageTakenAccordionEnemy\" + enemyIndex\r\n );\r\n let sourceName = combatMonsterDetailMap[source].name;\r\n resultAccordionButton.innerHTML = \"Damage Taken (\" + \"\" + sourceName + \"\" + \")\";\r\n\r\n enemyIndex++;\r\n }\r\n\r\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageTaken\");\r\n createDamageTable(totalResultDiv, totalDamageTaken, totalSecondsSimulated);\r\n}\r\n\r\nfunction createDamageTable(resultDiv, damageDone, secondsSimulated) {\r\n let newChildren = [];\r\n\r\n let sortedDamageDone = Object.entries(damageDone).sort((a, b) => b[1].damage - a[1].damage);\r\n\r\n let totalCasts = sortedDamageDone.reduce((prev, cur) => prev + cur[1].casts, 0);\r\n let totalMisses = sortedDamageDone.reduce((prev, cur) => prev + cur[1].misses, 0);\r\n let totalDamage = sortedDamageDone.reduce((prev, cur) => prev + cur[1].damage, 0);\r\n let totalHitChance = ((100 * (totalCasts - totalMisses)) / totalCasts).toFixed(1);\r\n let totalDamagePerSecond = (totalDamage / secondsSimulated).toFixed(2);\r\n\r\n let totalRow = createRow(\r\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\r\n [\"Total\", totalHitChance + \"%\", totalDamagePerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [ability, damageInfo] of sortedDamageDone) {\r\n let abilityText;\r\n let abilityFullHrid;\r\n switch (ability) {\r\n case \"autoAttack\":\r\n abilityText = \"Auto Attack\";\r\n abilityFullHrid = \"combatUnit.autoAttack\";\r\n break;\r\n case \"parry\":\r\n abilityText = \"Parry Attack\";\r\n abilityFullHrid = \"common:simulationResults.parryAttack\";\r\n break;\r\n case \"damageOverTime\":\r\n abilityText = \"Damage Over Time\";\r\n abilityFullHrid = \"common:simulationResults.damageOverTime\";\r\n break;\r\n case \"physicalThorns\":\r\n abilityText = \"Physical Thorns\";\r\n abilityFullHrid = \"combatStats.physicalThorns\";\r\n break;\r\n case \"elementalThorns\":\r\n abilityText = \"Elemental Thorns\";\r\n abilityFullHrid = \"combatStats.elementalThorns\";\r\n break;\r\n case \"retaliation\":\r\n abilityText = \"Retaliation\";\r\n abilityFullHrid = \"combatStats.retaliation\";\r\n break;\r\n case 'blaze':\r\n abilityText = \"Blaze\";\r\n abilityFullHrid = \"combatStats.blaze\";\r\n break;\r\n default:\r\n abilityText = abilityDetailMap[ability].name;\r\n abilityFullHrid = \"abilityNames.\" + ability;\r\n break;\r\n }\r\n\r\n let hitChance = ((100 * (damageInfo.casts - damageInfo.misses)) / damageInfo.casts).toFixed(1);\r\n let damagePerSecond = (damageInfo.damage / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * damageInfo.damage) / totalDamage).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\r\n [abilityText, hitChance + \"%\", damagePerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", abilityFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction createRow(columnClassNames, columnValues) {\r\n let row = createElement(\"div\", \"row\");\r\n\r\n for (let i = 0; i < columnClassNames.length; i++) {\r\n let column = createElement(\"div\", columnClassNames[i], columnValues[i]);\r\n row.appendChild(column);\r\n }\r\n\r\n return row;\r\n}\r\n\r\nfunction createElement(tagName, className, innerHTML = \"\", id = \"\") {\r\n let element = document.createElement(tagName);\r\n element.className = className;\r\n element.innerHTML = innerHTML;\r\n if (id) element.id = id;\r\n return element;\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Simulation Controls\r\n\r\ndocument.addEventListener('DOMContentLoaded', function () {\r\n const simDungeonToggle = document.getElementById('simDungeonToggle');\r\n const playerContainer = document.getElementById('playerCheckBox');\r\n\r\n function addPlayers() {\r\n const player4 = document.createElement('div');\r\n player4.classList.add('form-check');\r\n player4.innerHTML = `\r\n \r\n \r\n `;\r\n\r\n const player5 = document.createElement('div');\r\n player5.classList.add('form-check');\r\n player5.innerHTML = `\r\n \r\n \r\n `;\r\n\r\n playerContainer.appendChild(player4);\r\n playerContainer.appendChild(player5);\r\n }\r\n\r\n function removePlayers() {\r\n const player4 = document.getElementById('player4');\r\n const player5 = document.getElementById('player5');\r\n if (player4) player4.parentElement.remove();\r\n if (player5) player5.parentElement.remove();\r\n }\r\n\r\n function updatePlayerNames() {\r\n const tabLinks = document.querySelectorAll('#playerTab .nav-link');\r\n tabLinks.forEach((tabLink, index) => {\r\n const label = document.querySelector(`label[for=\"player${index + 1}\"]`);\r\n if (label) {\r\n label.textContent = tabLink.textContent.trim();\r\n }\r\n });\r\n }\r\n\r\n function updatePlayersCheckbox(isCheck) {\r\n const boxes = playerContainer.querySelectorAll('.player-checkbox');\r\n boxes.forEach((checkBox) => { checkBox.checked = isCheck });\r\n }\r\n\r\n function updateDifficultySelect(isCheck) {\r\n const difficultySelect = document.getElementById('selectDifficulty');\r\n // disable last four option\r\n if (isCheck && Number(difficultySelect.value) >= 3) {\r\n difficultySelect.value = 0;\r\n }\r\n for (let i = 3; i < difficultySelect.options.length; i++) {\r\n difficultySelect.options[i].disabled = isCheck;\r\n }\r\n }\r\n\r\n simDungeonToggle.addEventListener('change', function () {\r\n if (simDungeonToggle.checked) {\r\n addPlayers();\r\n updatePlayersCheckbox(true);\r\n updateDifficultySelect(true);\r\n } else {\r\n removePlayers();\r\n updatePlayersCheckbox(false);\r\n updateDifficultySelect(false);\r\n }\r\n updatePlayerNames();\r\n });\r\n\r\n document.getElementById('buttonSimulationSetup').addEventListener('click', function () {\r\n updatePlayerNames();\r\n });\r\n});\r\n\r\nfunction onTabChange(event) {\r\n const nextPlayerTabId = event.target.getAttribute('href').substring(7);\r\n savePreviousPlayer(currentPlayerTabId);\r\n updateNextPlayer(nextPlayerTabId);\r\n currentPlayerTabId = nextPlayerTabId;\r\n updateState();\r\n updateUI();\r\n if (Object.keys(currentSimResults).length !== 0) {\r\n showSimulationResult(currentSimResults);\r\n }\r\n\r\n updateContent();\r\n}\r\n\r\ndocument.querySelectorAll('#playerTab .nav-link').forEach(tab => {\r\n tab.addEventListener('shown.bs.tab', onTabChange);\r\n});\r\n\r\nfunction initSimulationControls() {\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n simulationTimeInput.value = 24;\r\n\r\n buttonStartSimulation.addEventListener(\"click\", (event) => {\r\n let invalidElements = document.querySelectorAll(\":invalid\");\r\n if (invalidElements.length > 0) {\r\n invalidElements.forEach((element) => element.reportValidity());\r\n return;\r\n }\r\n savePreviousPlayer(currentPlayerTabId);\r\n\r\n const simDungeonToggle = document.getElementById(\"simDungeonToggle\");\r\n const checkboxes = document.querySelectorAll('.player-checkbox');\r\n selectedPlayers = [];\r\n checkboxes.forEach(checkbox => {\r\n if (checkbox.checked) {\r\n const playerNumber = parseInt(checkbox.id.replace('player', ''));\r\n selectedPlayers.push(playerNumber);\r\n }\r\n });\r\n\r\n if (selectedPlayers.length === 0) {\r\n alert(\"You need to select at least one player to sim.\");\r\n return;\r\n }\r\n // buttonStartSimulation.disabled = true;\r\n buttonStopSimulation.style.display = 'block';\r\n startSimulation(selectedPlayers);\r\n });\r\n\r\n buttonStopSimulation.style.display = 'none';\r\n buttonStopSimulation.addEventListener(\"click\", (event) => {\r\n progressbar.style.width = \"0%\";\r\n progressbar.innerHTML = \"0%\";\r\n if (worker) {\r\n worker.terminate();\r\n }\r\n worker = new Worker(new URL(\"worker.js\", import.meta.url));\r\n\r\n if (multiWorker) {\r\n multiWorker.terminate();\r\n }\r\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n\r\n for (let worker of workerPool) {\r\n worker.worker.terminate();\r\n }\r\n\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n });\r\n}\r\n\r\nfunction startSimulation(selectedPlayers) {\r\n let playersToSim = [];\r\n for (let j = 1; j < 6; j++) {\r\n if (selectedPlayers.includes(j)) {\r\n updateNextPlayer(j);\r\n updateState();\r\n updateUI();\r\n player.hrid = \"player\" + j.toString();\r\n for (let i = 0; i < 3; i++) {\r\n if (food[i] && i < player.combatDetails.combatStats.foodSlots) {\r\n let consumable = new Consumable(food[i], triggerMap[food[i]]);\r\n player.food[i] = consumable;\r\n } else {\r\n player.food[i] = null;\r\n }\r\n\r\n if (drinks[i] && i < player.combatDetails.combatStats.drinkSlots) {\r\n let consumable = new Consumable(drinks[i], triggerMap[drinks[i]]);\r\n player.drinks[i] = consumable;\r\n } else {\r\n player.drinks[i] = null;\r\n }\r\n }\r\n\r\n for (let i = 0; i < 5; i++) {\r\n if (abilities[i] && player.intelligenceLevel >= abilitySlotsLevelRequirementList[i + 1]) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let ability = new Ability(abilities[i], Number(abilityLevelInput.value), triggerMap[abilities[i]]);\r\n player.abilities[i] = ability;\r\n } else {\r\n player.abilities[i] = null;\r\n }\r\n }\r\n\r\n playersToSim.push(structuredClone(player));\r\n }\r\n }\r\n updateNextPlayer(currentPlayerTabId);\r\n updateState();\r\n updateUI();\r\n\r\n let maxPlayerCombatLevel = 1;\r\n for (let player of playersToSim) {\r\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\r\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\r\n }\r\n\r\n for (let player of playersToSim) {\r\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\r\n const maxDebuffOnLevelGap = 0.9;\r\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\r\n\r\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\r\n\r\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\r\n }\r\n else {\r\n player.debuffOnLevelGap = 0;\r\n }\r\n }\r\n\r\n let extra = {};\r\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\r\n extra.comExp = 0;\r\n if (document.getElementById(\"comExpToggle\").checked) {\r\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\r\n }\r\n extra.comDrop = 0;\r\n if (document.getElementById(\"comDropToggle\").checked) {\r\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\r\n }\r\n\r\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\r\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\r\n let simDungeonToggle = document.getElementById(\"simDungeonToggle\");\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let dungeonSelect = document.getElementById(\"selectDungeon\");\r\n let difficultySelect = document.getElementById(\"selectDifficulty\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let simulationTimeLimit = Number(simulationTimeInput.value) * ONE_HOUR;\r\n buttonStopSimulation.style.display = 'block';\r\n if (!simAllZonesToggle.checked && !simAllSoloToggle.checked) {\r\n let zoneHrid = zoneSelect.value;\r\n let difficultyTier = Number(difficultySelect.value);\r\n if (simDungeonToggle.checked) {\r\n zoneHrid = dungeonSelect.value;\r\n }\r\n let workerMessage = {\r\n type: \"start_simulation\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n simStartTime = Date.now();\r\n if (!worker) {\r\n worker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n }\r\n worker.onmessage = onWorkerMessage;\r\n worker.postMessage(workerMessage);\r\n } else {\r\n let targetHrids = {};\r\n\r\n if (simAllZonesToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1 &&\r\n document.getElementById(a.hrid)?.checked\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n if (simAllSoloToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount === 1 &&\r\n document.getElementById(a.hrid)?.checked\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n let simHrids = Object.values(targetHrids)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .map(action => {\r\n let result = [];\r\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\r\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\r\n }\r\n return result;\r\n })\r\n .flat();\r\n\r\n let workerMessage = {\r\n type: \"start_simulation_all_zones\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zones: simHrids,\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra: extra\r\n };\r\n simStartTime = Date.now();\r\n if (!multiWorker) {\r\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n }\r\n multiWorker.onmessage = onMultiWorkerMessage;\r\n multiWorker.postMessage(workerMessage);\r\n }\r\n}\r\n\r\nfunction parsePlayerJson(playerJson, hrid) {\r\n let playerData = {\r\n hrid: hrid,\r\n food: [],\r\n drinks: [],\r\n abilities: [],\r\n ...playerJson.player,\r\n houseRooms: playerJson.houseRooms,\r\n };\r\n playerData.equipment = {};\r\n const triggerMap = playerJson.triggerMap;\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"main_hand\", \"two_hand\", \"charm\"].forEach((type) => {\r\n let currentEquipment = playerJson.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment){\r\n playerData.equipment[`/equipment_types/${type}`] = new Equipment(currentEquipment.itemHrid, currentEquipment.enhancementLevel);\r\n }\r\n });\r\n\r\n for (const foodHrid of playerJson.food[\"/action_types/combat\"]) {\r\n if (foodHrid.itemHrid === \"\") continue;\r\n const food = new Consumable(foodHrid.itemHrid, triggerMap[foodHrid.itemHrid]);\r\n playerData.food.push(food);\r\n }\r\n for (const drinkHrid of playerJson.drinks[\"/action_types/combat\"]) {\r\n if (drinkHrid.itemHrid === \"\") continue;\r\n const drink = new Consumable(drinkHrid.itemHrid, triggerMap[drinkHrid.itemHrid]);\r\n playerData.drinks.push(drink);\r\n }\r\n for (const ability of playerJson.abilities) {\r\n if (ability.abilityHrid === \"\") continue;\r\n const abilityLevel = Number(ability.level);\r\n const abilityHrid = ability.abilityHrid;\r\n if (abilityLevel > 0) {\r\n const abilityObj = new Ability(abilityHrid, abilityLevel, triggerMap[abilityHrid]);\r\n playerData.abilities.push(abilityObj);\r\n }\r\n }\r\n const player = Player.createFromDTO(playerData)\r\n player.updateCombatDetails();\r\n player.houseRooms = playerJson.houseRooms;\r\n player.achievements = playerJson.achievements ?? {};\r\n return player;\r\n}\r\n// read JSON file to simulate\r\ndocument.getElementById(\"buttonUploadJSONSimulate\").addEventListener(\"click\", (event) => {\r\n let extra = {};\r\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\r\n extra.comExp = 0;\r\n if (document.getElementById(\"comExpToggle\").checked) {\r\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\r\n }\r\n extra.comDrop = 0;\r\n if (document.getElementById(\"comDropToggle\").checked) {\r\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\r\n }\r\n\r\n let fileInput = document.getElementById(\"inputUploadJSONSimulation\");\r\n let file = fileInput.files[0];\r\n if (!file) {\r\n alert(\"Please select a file to upload.\");\r\n return;\r\n }\r\n\r\n let reader = new FileReader();\r\n reader.onload = function (event) {\r\n let fileContent = event.target.result;\r\n const jsonDataList = JSON.parse(fileContent);\r\n try {\r\n const simDataList = [];\r\n for (const key in jsonDataList) {\r\n if (jsonDataList[key].cases) {\r\n const cases = getProductCases(jsonDataList[key], jsonDataList[key].cases);\r\n simDataList.push(...cases);\r\n } else {\r\n simDataList.push(jsonDataList[key]);\r\n }\r\n }\r\n for (const key in simDataList) {\r\n const jsonData = simDataList[key];\r\n if (!jsonData || !jsonData.zone || !jsonData.players) {\r\n alert(\"Invalid JSON file format. Please ensure it contains a 'simulationResult' property.\");\r\n return;\r\n }\r\n const playersToSim = Object.values(jsonData.players).map(\r\n (player, index) => parsePlayerJson(player, `player${index + 1}`)\r\n );\r\n\r\n let maxPlayerCombatLevel = 1;\r\n for (let player of playersToSim) {\r\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\r\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\r\n }\r\n\r\n for (let player of playersToSim) {\r\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\r\n const maxDebuffOnLevelGap = 0.9;\r\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\r\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\r\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\r\n }\r\n else {\r\n player.debuffOnLevelGap = 0;\r\n }\r\n }\r\n\r\n const simulationTimeLimit = (jsonData.simulationTimeLimit || 24) * ONE_HOUR;\r\n const simName = jsonData.name || `Json ${key}`;\r\n const zoneHrid = jsonData.zone;\r\n if (zoneHrid === \"all\") {\r\n let targetHrids = {};\r\n\r\n if (simAllZonesToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n let simHrids = Object.values(targetHrids)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .map(action => {\r\n let result = [];\r\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\r\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\r\n }\r\n return result;\r\n })\r\n .flat();\r\n\r\n let workerMessage = {\r\n simulationName: simName,\r\n type: \"start_simulation_all_zones\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zones: simHrids,\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \r\n worker.onmessage = mainWorkerOnMessage;\r\n worker.postMessage(workerMessage);\r\n customAlert(\"Simulation task Created\", \"info\")\r\n workerPool.push({\r\n workerId: workerMessage.workerId,\r\n worker: worker,\r\n });\r\n } else {\r\n let difficultyTier = jsonData.difficultyTier || 0;\r\n let workerMessage = {\r\n simulationName: simName,\r\n type: \"start_simulation\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \r\n worker.onmessage = mainWorkerOnMessage;\r\n worker.postMessage(workerMessage);\r\n customAlert(\"Simulation task Created\", \"info\")\r\n workerPool.push({\r\n workerId: workerMessage.workerId,\r\n worker: worker,\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n // alert(\"Error parsing JSON file: \" + error.message);\r\n customAlert(\"Error parsing JSON file: \" + error.message, \"danger\");\r\n }\r\n }\r\n reader.readAsText(file);\r\n});\r\n\r\n\r\n// #endregion\r\n\r\n// #region WipeEvents\r\n\r\nfunction renderWipeEvents(simResult) {\r\n const selector = document.getElementById('wipeEventSelector');\r\n const logsContainer = document.getElementById('wipeLogsContainer');\r\n const waveBadge = document.getElementById('wipeWaveBadge');\r\n const timeInfo = document.getElementById('wipeTimeInfo');\r\n\r\n selector.innerHTML = '';\r\n logsContainer.innerHTML = '';\r\n\r\n if (!simResult.wipeEvents || simResult.wipeEvents.length === 0) {\r\n selector.innerHTML = ``;\r\n logsContainer.innerHTML = `
No Wipe Events Detected
`;\r\n waveBadge.textContent = '';\r\n timeInfo.textContent = '';\r\n return;\r\n }\r\n\r\n simResult.wipeEvents.forEach((event, index) => {\r\n const wave = event.wave || '?';\r\n // const time = (event.simulationTime / 1e9).toFixed(2);\r\n // const timestamp = new Date(event.timestamp).toLocaleTimeString();\r\n\r\n const option = document.createElement('option');\r\n option.value = index;\r\n option.textContent = `#${index + 1} - 波次: ${wave}`;\r\n selector.appendChild(option);\r\n });\r\n\r\n selector.value = 0;\r\n renderSelectedWipeEvent(0, simResult);\r\n\r\n selector.addEventListener('change', () => {\r\n renderSelectedWipeEvent(selector.value, simResult);\r\n });\r\n}\r\n\r\n// 渲染选中的团灭事件\r\nfunction renderSelectedWipeEvent(index, simResult) {\r\n const logsContainer = document.getElementById('wipeLogsContainer');\r\n const waveBadge = document.getElementById('wipeWaveBadge');\r\n const timeInfo = document.getElementById('wipeTimeInfo');\r\n\r\n logsContainer.innerHTML = '';\r\n\r\n if (index < 0 || index >= simResult.wipeEvents.length) {\r\n logsContainer.innerHTML = `
No Wipe Events
`;\r\n waveBadge.textContent = '';\r\n timeInfo.textContent = '';\r\n return;\r\n }\r\n\r\n const wipeEvent = simResult.wipeEvents[index];\r\n const wave = wipeEvent.wave || '?';\r\n const time = (wipeEvent.simulationTime / 1e9).toFixed(2);\r\n const timestamp = new Date(wipeEvent.timestamp).toLocaleString();\r\n\r\n waveBadge.textContent = `波次: ${wave}`;\r\n timeInfo.textContent = `模拟时间: ${time}s | 记录时间: ${timestamp}`;\r\n\r\n const logsByTime = groupLogsByTime(wipeEvent.logs);\r\n\r\n const baseTime = logsByTime.length > 0 ? logsByTime[0].time : 0;\r\n\r\n logsByTime.forEach(group => {\r\n const timeGroupElement = document.createElement('div');\r\n timeGroupElement.className = 'log-time-group';\r\n\r\n const relativeTime = (group.time - baseTime) / 1e9;\r\n\r\n // 时间标题\r\n const timeHeader = document.createElement('div');\r\n timeHeader.className = 'log-time-header';\r\n timeHeader.textContent = `[${relativeTime.toFixed(2)}s] [Wave#${group.wave}]`;\r\n timeGroupElement.appendChild(timeHeader);\r\n\r\n // 事件列表\r\n const eventsList = document.createElement('div');\r\n eventsList.className = 'log-events';\r\n\r\n const damagedPlayers = new Set();\r\n\r\n group.logs.forEach(log => {\r\n const eventElement = document.createElement('div');\r\n eventElement.className = 'log-event';\r\n\r\n damagedPlayers.add(log.target);\r\n\r\n const sourceSpan = document.createElement('span');\r\n sourceSpan.className = 'log-source';\r\n if (log.ability === \"damageOverTime\") {\r\n sourceSpan.textContent = log.target;\r\n } else if(log.source == 'UNKNOWN_SOURCE') {\r\n sourceSpan.textContent = 'UNKNOWN';\r\n } else {\r\n sourceSpan.setAttribute('data-i18n', `monsterNames.${log.source}`);\r\n sourceSpan.textContent = log.source;\r\n }\r\n\r\n const castSpan = document.createElement('span');\r\n castSpan.className = 'log-cast';\r\n castSpan.setAttribute('data-i18n', `common:cast`);\r\n castSpan.textContent = ' cast ';\r\n\r\n const abilitySpan = document.createElement('span');\r\n abilitySpan.className = 'log-ability';\r\n if (log.ability === \"autoAttack\") {\r\n abilitySpan.setAttribute('data-i18n', 'combatUnit.autoAttack');\r\n abilitySpan.textContent = 'Auto Attack';\r\n } else if (log.ability === \"physicalThorns\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.physicalThorns`);\r\n abilitySpan.textContent = 'Physical Thorns';\r\n } else if (log.ability === \"elementalThorns\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.elementalThorns`);\r\n abilitySpan.textContent = 'Elemental Thorns';\r\n } else if (log.ability === \"retaliation\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.retaliation`);\r\n abilitySpan.textContent = 'Retaliation';\r\n } else if (log.ability === \"damageOverTime\") {\r\n abilitySpan.setAttribute('data-i18n', `common:simulationResults.damageOverTime`);\r\n abilitySpan.textContent = 'Damage Over Time';\r\n } else {\r\n abilitySpan.setAttribute('data-i18n', `abilityNames.${log.ability}`);\r\n abilitySpan.textContent = log.ability;\r\n }\r\n\r\n const toSpan = document.createElement('span');\r\n toSpan.className = 'log-to';\r\n toSpan.setAttribute('data-i18n', `common:to`);\r\n toSpan.textContent = ' to ';\r\n\r\n const targetSpan = document.createElement('span');\r\n targetSpan.className = 'log-target';\r\n targetSpan.textContent = log.target;\r\n\r\n const dealDamageSpan = document.createElement('span');\r\n dealDamageSpan.className = 'log-deal-damage';\r\n dealDamageSpan.setAttribute('data-i18n', `common:dealDamage`);\r\n dealDamageSpan.textContent = ' deal damage ';\r\n\r\n const damageDoneSpan = document.createElement('span');\r\n damageDoneSpan.className = 'log-damage-done';\r\n damageDoneSpan.textContent = log.damage;\r\n if (log.isCrit) {\r\n damageDoneSpan.style.fontWeight = 'bold';\r\n damageDoneSpan.textContent += '!!!';\r\n }\r\n\r\n eventElement.appendChild(sourceSpan);\r\n eventElement.appendChild(castSpan);\r\n eventElement.appendChild(abilitySpan);\r\n eventElement.appendChild(toSpan);\r\n eventElement.appendChild(targetSpan);\r\n eventElement.appendChild(dealDamageSpan);\r\n eventElement.appendChild(damageDoneSpan);\r\n eventElement.appendChild(document.createTextNode(` , HP ${log.beforeHp} → ${log.afterHp}`));\r\n\r\n eventsList.appendChild(eventElement);\r\n });\r\n\r\n timeGroupElement.appendChild(eventsList);\r\n\r\n const lastLog = group.logs[group.logs.length - 1];\r\n const playersHpElement = document.createElement('div');\r\n\r\n const playerHpTitle = document.createElement('span');\r\n playerHpTitle.className = 'log-players-hp';\r\n playerHpTitle.setAttribute('data-i18n', `common:playersHp`);\r\n playerHpTitle.textContent = 'Players HP: ';\r\n playersHpElement.appendChild(playerHpTitle);\r\n\r\n lastLog.playersHp.forEach((player, idx) => {\r\n const playerElement = document.createElement('span');\r\n playerElement.className = 'log-player-hp';\r\n playerElement.textContent = `${player.hrid}: ${player.current}/${player.max}`;\r\n\r\n if (player.current <= 0) {\r\n playerElement.style.color = darkModeToggle.checked ? '#FF6347' : '#CC0000';\r\n } else if (damagedPlayers.has(player.hrid)) {\r\n playerElement.style.color = darkModeToggle.checked ? '#00BFFF' : '#007BFF';\r\n }\r\n\r\n if (idx > 0) {\r\n playersHpElement.appendChild(document.createTextNode(' | '));\r\n }\r\n playersHpElement.appendChild(playerElement);\r\n });\r\n const spacer = document.createElement('div');\r\n spacer.style.height = '15px';\r\n logsContainer.appendChild(spacer);\r\n timeGroupElement.appendChild(playersHpElement);\r\n logsContainer.appendChild(timeGroupElement);\r\n });\r\n\r\n // 更新汉化\r\n updateContent()\r\n}\r\n\r\n// 按时间分组日志\r\nfunction groupLogsByTime(logs) {\r\n const groups = [];\r\n let currentGroup = null;\r\n\r\n logs.forEach(log => {\r\n if (!currentGroup || currentGroup.time !== log.time) {\r\n currentGroup = {\r\n time: log.time,\r\n wave: log.wave,\r\n logs: [log]\r\n };\r\n groups.push(currentGroup);\r\n } else {\r\n currentGroup.logs.push(log);\r\n }\r\n });\r\n\r\n groups.forEach(group => {\r\n let hpMap = {};\r\n if (group.logs.length > 0) {\r\n group.logs[0].playersHp.forEach(p => {\r\n hpMap[p.hrid] = { current: p.current, max: p.max };\r\n });\r\n }\r\n group.logs.forEach(log => {\r\n if (hpMap[log.target]) {\r\n hpMap[log.target].current = log.afterHp;\r\n }\r\n });\r\n group.logs.forEach(log => {\r\n log.playersHp = Object.entries(hpMap).map(([hrid, val]) => ({\r\n hrid,\r\n current: val.current,\r\n max: val.max\r\n }));\r\n });\r\n });\r\n\r\n return groups;\r\n}\r\n\r\n// #endregion\r\n\r\n\r\n// #region Equipment Sets\r\n\r\nfunction initEquipmentSetsModal() {\r\n let equipmentSetsModal = document.getElementById(\"equipmentSetsModal\");\r\n equipmentSetsModal.addEventListener(\"show.bs.modal\", equipmentSetsModalShownHandler);\r\n\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n equipmentSetNameInput.addEventListener(\"input\", (event) => equipmentSetNameChangedHandler(event));\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.addEventListener(\"click\", createNewEquipmentSetHandler);\r\n}\r\n\r\nfunction equipmentSetsModalShownHandler() {\r\n resetNewEquipmentSetControls();\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction resetNewEquipmentSetControls() {\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n equipmentSetNameInput.value = \"\";\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.disabled = true;\r\n}\r\n\r\nfunction updateEquipmentSetList() {\r\n let newChildren = [];\r\n let equipmentSets = loadEquipmentSets();\r\n\r\n for (const equipmentSetName of Object.keys(equipmentSets)) {\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let nameCol = createElement(\"div\", \"col align-self-center\", equipmentSetName);\r\n row.appendChild(nameCol);\r\n\r\n let loadButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let loadButton = createElement(\"button\", \"btn btn-primary\", \"Load\");\r\n loadButton.setAttribute(\"data-i18n\", \"common:controls.load\");\r\n loadButton.setAttribute(\"type\", \"button\");\r\n loadButton.addEventListener(\"click\", (_) => loadEquipmentSetHandler(equipmentSetName));\r\n loadButtonCol.appendChild(loadButton);\r\n row.appendChild(loadButtonCol);\r\n\r\n let saveButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let saveButton = createElement(\"button\", \"btn btn-primary\", \"Save\");\r\n saveButton.setAttribute(\"data-i18n\", \"common:controls.save\");\r\n saveButton.setAttribute(\"type\", \"button\");\r\n saveButton.addEventListener(\"click\", (_) => updateEquipmentSetHandler(equipmentSetName));\r\n saveButtonCol.appendChild(saveButton);\r\n row.appendChild(saveButtonCol);\r\n\r\n let deleteButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let deleteButton = createElement(\"button\", \"btn btn-danger\", \"Delete\");\r\n deleteButton.setAttribute(\"data-i18n\", \"common:controls.delete\");\r\n deleteButton.setAttribute(\"type\", \"button\");\r\n deleteButton.addEventListener(\"click\", (_) => deleteEquipmentSetHandler(equipmentSetName));\r\n deleteButtonCol.appendChild(deleteButton);\r\n row.appendChild(deleteButtonCol);\r\n\r\n newChildren.push(row);\r\n }\r\n\r\n let equipmentSetList = document.getElementById(\"equipmentSetList\");\r\n equipmentSetList.replaceChildren(...newChildren);\r\n\r\n updateContent();\r\n}\r\n\r\nfunction equipmentSetNameChangedHandler(event) {\r\n let invalid = false;\r\n\r\n if (event.target.value.length == 0) {\r\n invalid = true;\r\n }\r\n\r\n let equipmentSets = loadEquipmentSets();\r\n if (equipmentSets[event.target.value]) {\r\n invalid = true;\r\n }\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.disabled = invalid;\r\n}\r\n\r\nfunction createNewEquipmentSetHandler() {\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n let equipmentSetName = equipmentSetNameInput.value;\r\n\r\n let equipmentSet = getEquipmentSetFromUI();\r\n let equipmentSets = loadEquipmentSets();\r\n equipmentSets[equipmentSetName] = equipmentSet;\r\n saveEquipmentSets(equipmentSets);\r\n\r\n resetNewEquipmentSetControls();\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction loadEquipmentSetHandler(name) {\r\n let equipmentSets = loadEquipmentSets();\r\n loadEquipmentSetIntoUI(equipmentSets[name]);\r\n}\r\n\r\nfunction updateEquipmentSetHandler(name) {\r\n let equipmentSet = getEquipmentSetFromUI();\r\n let equipmentSets = loadEquipmentSets();\r\n equipmentSets[name] = equipmentSet;\r\n saveEquipmentSets(equipmentSets);\r\n}\r\n\r\nfunction deleteEquipmentSetHandler(name) {\r\n let equipmentSets = loadEquipmentSets();\r\n delete equipmentSets[name];\r\n saveEquipmentSets(equipmentSets);\r\n\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction loadEquipmentSets() {\r\n return JSON.parse(localStorage.getItem(\"equipmentSets\")) ?? {};\r\n}\r\n\r\nfunction saveEquipmentSets(equipmentSets) {\r\n localStorage.setItem(\"equipmentSets\", JSON.stringify(equipmentSets));\r\n}\r\n\r\nfunction getEquipmentSetFromUI() {\r\n let equipmentSet = {\r\n levels: {},\r\n equipment: {},\r\n food: {},\r\n drinks: {},\r\n abilities: {},\r\n triggerMap: {},\r\n houseRooms: {},\r\n achievements: {},\r\n };\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n equipmentSet.levels[skill] = Number(levelInput.value);\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n\r\n equipmentSet.equipment[type] = {\r\n equipment: equipmentSelect.value,\r\n enhancementLevel: Number(enhancementLevelInput.value),\r\n };\r\n });\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n equipmentSet.food[i] = foodSelect.value;\r\n }\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n equipmentSet.drinks[i] = drinkSelect.value;\r\n }\r\n\r\n for (let i = 0; i < 5; i++) {\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n equipmentSet.abilities[i] = {\r\n ability: abilitySelect.value,\r\n level: Number(abilityLevelInput.value),\r\n };\r\n }\r\n\r\n equipmentSet.triggerMap = triggerMap;\r\n\r\n equipmentSet.houseRooms = player.houseRooms;\r\n equipmentSet.achievements = player.achievements;\r\n\r\n return equipmentSet;\r\n}\r\n\r\nfunction fixTriggerMap(triggerMap) {\r\n let delKeys = []\r\n for (const key of Object.keys(triggerMap)) {\r\n let err = false;\r\n if (null == triggerMap[key]) {\r\n triggerMap[key] = [];\r\n }\r\n for (const trigger of triggerMap[key]) {\r\n if (!combatTriggerConditionDetailMap[trigger.conditionHrid]) {\r\n err = true;\r\n break;\r\n }\r\n }\r\n if (err) {\r\n delKeys.push(key);\r\n }\r\n }\r\n for (const key of delKeys) {\r\n delete triggerMap[key];\r\n }\r\n}\r\n\r\nfunction loadEquipmentSetIntoUI(equipmentSet) {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !equipmentSet.levels[\"meleeLevel\"] && equipmentSet.levels[\"powerLevel\"]) {\r\n equipmentSet.levels[\"meleeLevel\"] = equipmentSet.levels[\"powerLevel\"];\r\n }\r\n levelInput.value = equipmentSet.levels[skill] ?? 1;\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n\r\n let currentEquipment = equipmentSet.equipment[type];\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.equipment;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n foodSelect.value = equipmentSet.food[i];\r\n }\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n drinkSelect.value = equipmentSet.drinks[i].replace(\"power\", \"melee\");\r\n }\r\n\r\n let hasSpecial = false;\r\n if (equipmentSet.abilities && Object.keys(equipmentSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n equipmentSet.abilities[i].ability == \"/abilities/aqua_aura\" ||\r\n equipmentSet.abilities[i].ability == \"/abilities/flame_aura\" ||\r\n equipmentSet.abilities[i].ability == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n equipmentSet.abilities[i].ability = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (equipmentSet.abilities[i].ability == \"/abilities/arcane_reflection\") {\r\n equipmentSet.abilities[i].ability = \"/abilities/retribution\";\r\n }\r\n\r\n abilitySelect.value = equipmentSet.abilities[i].ability;\r\n abilityLevelInput.value = equipmentSet.abilities[i].level;\r\n }\r\n\r\n triggerMap = equipmentSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n\r\n if (equipmentSet.houseRooms) {\r\n for (const room in equipmentSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (equipmentSet.houseRooms[room]) {\r\n field.value = equipmentSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = equipmentSet.houseRooms;\r\n } else {\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n\r\n if (equipmentSet.achievements) {\r\n for (const achievement in equipmentSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (equipmentSet.achievements[achievement]) {\r\n field.checked = true;\r\n } else {\r\n field.checked = false;\r\n }\r\n player.achievements[achievement] = field.checked;\r\n }\r\n } else {\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n refreshAchievementStatics();\r\n\r\n updateState();\r\n updateUI();\r\n\r\n updateContent();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Error Handling\r\n\r\nfunction initErrorHandling() {\r\n window.addEventListener(\"error\", (event) => {\r\n showErrorModal(event.message);\r\n });\r\n\r\n let copyErrorButton = document.getElementById(\"buttonCopyError\");\r\n copyErrorButton.addEventListener(\"click\", (event) => {\r\n let errorInput = document.getElementById(\"inputError\");\r\n navigator.clipboard.writeText(errorInput.value);\r\n });\r\n}\r\n\r\nfunction initImportExportModal() {\r\n let exportSetButton = document.getElementById(\"buttonExportSet\");\r\n exportSetButton.addEventListener(\"click\", (event) => {\r\n savePreviousPlayer(currentPlayerTabId);\r\n const activeTab = document.querySelector('#importTab .nav-link.active');\r\n if (activeTab.id === 'group-combat-tab') {\r\n doGroupExport();\r\n } else if (activeTab.id === 'solo-tab') {\r\n doSoloExport();\r\n }\r\n });\r\n\r\n let importSetButton = document.getElementById(\"buttonImportSet\");\r\n importSetButton.addEventListener(\"click\", (event) => {\r\n const activeTab = document.querySelector('#importTab .nav-link.active');\r\n if (activeTab.id === 'group-combat-tab') {\r\n doGroupImport();\r\n } else if (activeTab.id === 'solo-tab') {\r\n doSoloImport();\r\n }\r\n updateState();\r\n updateUI();\r\n resetImportInputs();\r\n });\r\n}\r\n\r\nfunction resetImportInputs() {\r\n document.getElementById('inputSetGroupCombatAll').value = '';\r\n document.getElementById('inputSetGroupCombatplayer1').value = '';\r\n document.getElementById('inputSetGroupCombatplayer2').value = '';\r\n document.getElementById('inputSetGroupCombatplayer3').value = '';\r\n document.getElementById('inputSetGroupCombatplayer4').value = '';\r\n document.getElementById('inputSetGroupCombatplayer5').value = '';\r\n document.getElementById('inputSetSolo').value = '';\r\n}\r\n\r\nfunction doGroupExport() {\r\n try {\r\n navigator.clipboard.writeText(JSON.stringify(playerDataMap)).then(() => alert(\"Current Group has been copied to clipboard.\"));\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction doSoloExport() {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let equipmentArray = [];\r\n for (const item in player.equipment) {\r\n if (player.equipment[item] != null) {\r\n equipmentArray.push({\r\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\r\n \"itemHrid\": player.equipment[item].hrid,\r\n \"enhancementLevel\": player.equipment[item].enhancementLevel\r\n });\r\n }\r\n }\r\n let playerArray = {\r\n \"attackLevel\": player.attackLevel,\r\n \"magicLevel\": player.magicLevel,\r\n \"meleeLevel\": player.meleeLevel,\r\n \"rangedLevel\": player.rangedLevel,\r\n \"defenseLevel\": player.defenseLevel,\r\n \"staminaLevel\": player.staminaLevel,\r\n \"intelligenceLevel\": player.intelligenceLevel,\r\n \"equipment\": equipmentArray\r\n };\r\n let abilitiesArray = [];\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let abilityName = document.getElementById(\"selectAbility_\" + i);\r\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\r\n }\r\n let drinksArray = [];\r\n for (let i = 0; i < drinks?.length; i++) {\r\n drinksArray.push({ \"itemHrid\": drinks[i] });\r\n }\r\n let foodArray = [];\r\n for (let i = 0; i < food?.length; i++) {\r\n foodArray.push({ \"itemHrid\": food[i] });\r\n }\r\n let state = {\r\n player: playerArray,\r\n food: { \"/action_types/combat\": foodArray },\r\n drinks: { \"/action_types/combat\": drinksArray },\r\n abilities: abilitiesArray,\r\n triggerMap: triggerMap,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n houseRooms: player.houseRooms,\r\n achievements: player.achievements\r\n };\r\n try {\r\n navigator.clipboard.writeText(JSON.stringify(state)).then(() => alert(\"Current set has been copied to clipboard.\"));\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction setPlayerData(playerId, inputElementId) {\r\n const inputElement = document.getElementById(inputElementId);\r\n const value = inputElement ? inputElement.value.trim() : \"\";\r\n\r\n // Only set the value in the map if it's not null, undefined, or empty\r\n if (value) {\r\n playerDataMap[playerId] = value;\r\n return true;\r\n }\r\n return false;\r\n}\r\n\r\nfunction doGroupImport() {\r\n let needUpdateCurrentTab = false;\r\n const value = document.getElementById(\"inputSetGroupCombatAll\")?.value || \"\";\r\n if (!value.trim()) {\r\n for (let i of ['1', '2', '3', '4', '5']) {\r\n if (setPlayerData(i, \"inputSetGroupCombatplayer\" + i) && currentPlayerTabId == i) {\r\n needUpdateCurrentTab = true;\r\n }\r\n }\r\n } else {\r\n playerDataMap = JSON.parse(value);\r\n needUpdateCurrentTab = true;\r\n }\r\n\r\n if (needUpdateCurrentTab) {\r\n updateNextPlayer(currentPlayerTabId);\r\n }\r\n}\r\n\r\nfunction doSoloImport() {\r\n let importSet = document.getElementById(\"inputSetSolo\").value;\r\n importSet = JSON.parse(importSet);\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\r\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\r\n }\r\n levelInput.value = importSet.player[skill + \"Level\"];\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.itemHrid;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\r\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\r\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\r\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\r\n if (mainhandWeapon !== undefined) {\r\n weaponSelect.value = mainhandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\r\n } else if (twohandWeapon !== undefined) {\r\n weaponSelect.value = twohandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\r\n } else {\r\n weaponSelect.value = \"\";\r\n weaponEnhancementLevelInput.value = 0;\r\n }\r\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\r\n importSet.food = importSet.food[\"/action_types/combat\"];\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n if (importSet.drinks[i] != null) {\r\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\r\n } else {\r\n drinkSelect.value = \"\";\r\n }\r\n if (importSet.food[i] != null) {\r\n foodSelect.value = importSet.food[i].itemHrid;\r\n } else {\r\n foodSelect.value = \"\";\r\n }\r\n }\r\n\r\n let hasSpecial = false;\r\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\r\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\r\n }\r\n\r\n if (importSet.abilities[i] != null) {\r\n abilitySelect.value = importSet.abilities[i].abilityHrid;\r\n abilityLevelInput.value = String(importSet.abilities[i].level);\r\n } else {\r\n abilitySelect.value = \"\";\r\n abilityLevelInput.value = \"1\";\r\n }\r\n }\r\n\r\n if (importSet.triggerMap) {\r\n triggerMap = importSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n }\r\n\r\n if (importSet.houseRooms) {\r\n for (const room in importSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (importSet.houseRooms[room]) {\r\n field.value = importSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = importSet.houseRooms;\r\n } else {\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n\r\n if (importSet.achievements) {\r\n for (const achievement in importSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (importSet.achievements[achievement]) {\r\n field.checked = true;\r\n } else {\r\n field.checked = false;\r\n }\r\n player.achievements[achievement] = field.checked;\r\n }\r\n } else {\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n refreshAchievementStatics();\r\n\r\n if (\"zone\" in importSet) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n zoneSelect.value = importSet[\"zone\"];\r\n }\r\n\r\n if (\"simulationTime\" in importSet) {\r\n let simulationDuration = document.getElementById(\"inputSimulationTime\");\r\n simulationDuration.value = importSet[\"simulationTime\"];\r\n }\r\n}\r\n\r\nfunction savePreviousPlayer(playerId) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let equipmentArray = [];\r\n for (const item in player.equipment) {\r\n if (player.equipment[item] != null) {\r\n equipmentArray.push({\r\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\r\n \"itemHrid\": player.equipment[item].hrid,\r\n \"enhancementLevel\": player.equipment[item].enhancementLevel\r\n });\r\n }\r\n }\r\n let playerArray = {\r\n \"attackLevel\": player.attackLevel,\r\n \"magicLevel\": player.magicLevel,\r\n \"meleeLevel\": player.meleeLevel,\r\n \"rangedLevel\": player.rangedLevel,\r\n \"defenseLevel\": player.defenseLevel,\r\n \"staminaLevel\": player.staminaLevel,\r\n \"intelligenceLevel\": player.intelligenceLevel,\r\n \"equipment\": equipmentArray\r\n };\r\n let abilitiesArray = [];\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let abilityName = document.getElementById(\"selectAbility_\" + i);\r\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\r\n }\r\n let drinksArray = [];\r\n for (let i = 0; i < drinks?.length; i++) {\r\n drinksArray.push({ \"itemHrid\": drinks[i] });\r\n }\r\n let foodArray = [];\r\n for (let i = 0; i < food?.length; i++) {\r\n foodArray.push({ \"itemHrid\": food[i] });\r\n }\r\n let state = {\r\n player: playerArray,\r\n food: { \"/action_types/combat\": foodArray },\r\n drinks: { \"/action_types/combat\": drinksArray },\r\n abilities: abilitiesArray,\r\n triggerMap: triggerMap,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n houseRooms: player.houseRooms,\r\n achievements: player.achievements\r\n };\r\n try {\r\n playerDataMap[playerId] = JSON.stringify(state);\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction updateNextPlayer(currentPlayerNumber) {\r\n let playerImportData = playerDataMap[currentPlayerNumber];\r\n let importSet = JSON.parse(playerImportData);\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\r\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\r\n }\r\n levelInput.value = importSet.player[skill + \"Level\"];\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.itemHrid;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\r\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\r\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\r\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\r\n if (mainhandWeapon !== undefined) {\r\n weaponSelect.value = mainhandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\r\n } else if (twohandWeapon !== undefined) {\r\n weaponSelect.value = twohandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\r\n } else {\r\n weaponSelect.value = \"\";\r\n weaponEnhancementLevelInput.value = 0;\r\n }\r\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\r\n importSet.food = importSet.food[\"/action_types/combat\"];\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n if (importSet.drinks[i] != null) {\r\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\r\n } else {\r\n drinkSelect.value = \"\";\r\n }\r\n if (importSet.food[i] != null) {\r\n foodSelect.value = importSet.food[i].itemHrid;\r\n } else {\r\n foodSelect.value = \"\";\r\n }\r\n }\r\n\r\n let hasSpecial = false;\r\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\r\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\r\n }\r\n\r\n if (importSet.abilities[i] != null) {\r\n abilitySelect.value = importSet.abilities[i].abilityHrid;\r\n abilityLevelInput.value = String(importSet.abilities[i].level);\r\n } else {\r\n abilitySelect.value = \"\";\r\n abilityLevelInput.value = \"1\";\r\n }\r\n }\r\n\r\n if (importSet.triggerMap) {\r\n triggerMap = importSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n }\r\n\r\n { // reset all houseRooms\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n if (importSet.houseRooms) {\r\n for (const room in importSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (importSet.houseRooms[room]) {\r\n field.value = importSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = importSet.houseRooms;\r\n }\r\n\r\n { // reset all achievements\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n if (importSet.achievements) {\r\n for (const achievement in importSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (importSet.achievements[achievement]) {\r\n field.checked = true;\r\n player.achievements[achievement] = true;\r\n } else {\r\n field.checked = false;\r\n player.achievements[achievement] = false;\r\n }\r\n }\r\n }\r\n refreshAchievementStatics();\r\n}\r\n\r\nfunction showErrorModal(error) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n\r\n let state = {\r\n error: error,\r\n player: player,\r\n food: food,\r\n drinks: drinks,\r\n abilities: abilities,\r\n triggerMap: triggerMap,\r\n modalTriggers: modalTriggers,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n };\r\n\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n state[\"abilityLevel\" + i] = abilityLevelInput.value;\r\n }\r\n\r\n let errorInput = document.getElementById(\"inputError\");\r\n errorInput.value = JSON.stringify(state);\r\n\r\n let errorModal = new bootstrap.Modal(document.getElementById(\"errorModal\"));\r\n errorModal.show();\r\n}\r\n\r\nwindow.prices;\r\n\r\nasync function fetchPrices() {\r\n let response = null;\r\n try {\r\n response = await fetch('https://www.milkywayidle.com/game_data/marketplace.json'\r\n , {\r\n mode: 'cors'\r\n }\r\n );\r\n if (!response.ok) {\r\n console.log('Error fetching prices');\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n\r\n if (response == null) {\r\n try {\r\n response = await fetch('https://www.milkywayidlecn.com/game_data/marketplace.json'\r\n , {\r\n mode: 'cors'\r\n }\r\n );\r\n if (!response.ok) {\r\n console.log('Error fetching prices');\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n }\r\n\r\n if (!response || !response.ok) {\r\n return;\r\n }\r\n\r\n try {\r\n\r\n let btn = document.querySelector('#buttonGetPrices');\r\n btn.style.backgroundColor = 'green';\r\n\r\n const pricesJson = await response.json();\r\n\r\n const priceTmp = pricesJson['marketData'];\r\n window.prices = {};\r\n for (const item in itemDetailMap) {\r\n const hrid = itemDetailMap[item].hrid;\r\n if (hrid in priceTmp) {\r\n window.prices[hrid] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (priceTmp[hrid]['0']) {\r\n window.prices[hrid].ask = priceTmp[hrid]['0'].a;\r\n window.prices[hrid].bid = priceTmp[hrid]['0'].b;\r\n }\r\n }\r\n } \r\n\r\n window.prices[\"/items/coin\"] = { \"ask\": 1, \"bid\": 1, \"vendor\": 1 };\r\n\r\n window.prices[\"/items/small_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n window.prices[\"/items/medium_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n window.prices[\"/items/large_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n}\r\n\r\ndocument.getElementById(\"buttonGetPrices\").onclick = async () => {\r\n await fetchPrices();\r\n};\r\n\r\ndocument.addEventListener(\"input\", (e) => {\r\n let element = e.target;\r\n if (element.tagName == \"TD\" && element.parentNode.parentNode.parentNode.classList.value.includes('profit-table')) {\r\n let tableId = element.parentNode.parentNode.parentNode.id;\r\n let row = element.parentNode.querySelectorAll('td');\r\n let item = row[0].getAttribute('data-i18n').split('.')[1];\r\n let newPrice = element.innerText;\r\n\r\n let revenueSetting = document.getElementById('selectPrices_drops').value;\r\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\r\n\r\n let expensesDifference = 0;\r\n let revenueDifference = 0;\r\n let noRngRevenueDifference = 0;\r\n\r\n if (tableId == 'expensesTable') {\r\n expensesDifference = updateTable('expensesTable', item, newPrice);\r\n if (revenueSetting == expensesSetting) {\r\n revenueDifference = updateTable('revenueTable', item, newPrice);\r\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\r\n }\r\n if (window.prices) {\r\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (expensesSetting == 'bid') {\r\n window.prices[item]['bid'] = newPrice;\r\n } else {\r\n window.prices[item]['ask'] = newPrice;\r\n }\r\n }\r\n } else {\r\n revenueDifference = updateTable('revenueTable', item, newPrice);\r\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\r\n if (revenueSetting == expensesSetting) {\r\n expensesDifference = updateTable('expensesTable', item, newPrice);\r\n }\r\n if (window.prices) {\r\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (revenueSetting == 'bid') {\r\n window.prices[item]['bid'] = newPrice;\r\n } else {\r\n window.prices[item]['ask'] = newPrice;\r\n }\r\n }\r\n }\r\n\r\n window.expenses += expensesDifference;\r\n document.getElementById('expensesSpan').innerText = window.expenses.toLocaleString();\r\n window.revenue += revenueDifference;\r\n document.getElementById('revenueSpan').innerText = window.revenue.toLocaleString();\r\n window.noRngRevenue += noRngRevenueDifference;\r\n document.getElementById('noRngRevenueSpan').innerText = window.noRngRevenue.toLocaleString();\r\n\r\n window.profit = window.revenue - window.expenses;\r\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\r\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\r\n window.noRngProfit = window.noRngRevenue - window.expenses;\r\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\r\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\r\n }\r\n});\r\n\r\nfunction updateTable(tableId, item, price) {\r\n let row = document.querySelector('#' + tableId + ' .' + CSS.escape(item));\r\n if (row == null) {\r\n return 0;\r\n }\r\n\r\n row = row.querySelectorAll('td');\r\n let priceTd = row[1];\r\n let amountTd = row[2];\r\n let totalTd = row[3];\r\n let oldTotal = totalTd.innerText;\r\n let newTotal = price * amountTd.innerText;\r\n\r\n if (priceTd.innerText != price) {\r\n priceTd.innerText = price;\r\n }\r\n totalTd.innerText = newTotal;\r\n\r\n return newTotal - oldTotal;\r\n}\r\n\r\n// #endregion\r\n\r\nfunction initPatchNotes() {\r\n const patchNotesRows = document.getElementById(\"patchNotes\");\r\n for (const pn in patchNote) {\r\n const patchNoteContainer = document.createElement(\"div\");\r\n patchNotesRows.setAttribute('class', 'col-12 mb-4');\r\n\r\n const patchNoteElement = document.createElement(\"h6\");\r\n patchNoteElement.innerHTML = pn;\r\n const patchNoteList = document.createElement(\"ul\");\r\n for (const note of patchNote[pn]) {\r\n const noteElement = document.createElement(\"li\");\r\n noteElement.innerHTML = note;\r\n patchNoteList.appendChild(noteElement);\r\n }\r\n patchNoteContainer.appendChild(patchNoteElement);\r\n patchNoteContainer.appendChild(patchNoteList);\r\n\r\n patchNotesRows.appendChild(patchNoteContainer);\r\n }\r\n}\r\n\r\nfunction initExtraBuffSection() {\r\n // mooPass\r\n let mooPassToggle = document.getElementById(\"mooPassToggle\");\r\n let mooPass = localStorage.getItem('mooPass');\r\n if (mooPass) {\r\n mooPassToggle.checked = Boolean(mooPass);\r\n }\r\n mooPassToggle.onchange = () => {\r\n localStorage.setItem('mooPass', mooPassToggle.checked);\r\n }\r\n \r\n // comExp\r\n let comExpToggle = document.getElementById(\"comExpToggle\");\r\n let comExpInput = document.getElementById(\"comExpInput\");\r\n let comExp = localStorage.getItem('comExp');\r\n if (comExp) {\r\n let comExpNumber = Number(comExp);\r\n if (comExpNumber > 0) {\r\n comExpToggle.checked = true;\r\n comExpInput.value = comExpNumber;\r\n } else {\r\n comExpToggle.checked = false;\r\n comExpInput.disabled = true;\r\n }\r\n }\r\n const updateComExp = () => {\r\n if (comExpToggle.checked) {\r\n let comExp = Number(comExpInput.value);\r\n localStorage.setItem('comExp', comExp); \r\n comExpInput.disabled = false;\r\n } else {\r\n localStorage.setItem('comExp', 0);\r\n comExpInput.disabled = true;\r\n }\r\n }\r\n comExpToggle.onchange = updateComExp;\r\n comExpInput.onchange = updateComExp;\r\n\r\n // comDrop\r\n let comDropToggle = document.getElementById(\"comDropToggle\");\r\n let comDropInput = document.getElementById(\"comDropInput\");\r\n let comDrop = localStorage.getItem('comDrop');\r\n if (comDrop) {\r\n let comDropNumber = Number(comDrop);\r\n if (comDropNumber > 0) {\r\n comDropToggle.checked = true;\r\n comDropInput.value = comDropNumber;\r\n } else {\r\n comDropToggle.checked = false;\r\n comDropInput.disabled = true;\r\n }\r\n }\r\n const updateComDrop = () => {\r\n if (comDropToggle.checked) {\r\n let comDrop = Number(comDropInput.value);\r\n localStorage.setItem('comDrop', comDrop); \r\n comDropInput.disabled = false;\r\n } else {\r\n localStorage.setItem('comDrop', 0);\r\n comDropInput.disabled = true;\r\n }\r\n }\r\n comDropToggle.onchange = updateComDrop;\r\n comDropInput.onchange = updateComDrop;\r\n}\r\n\r\n\r\nfunction updateState() {\r\n updateEquipmentState();\r\n updateLevels();\r\n updateFoodState();\r\n updateDrinksState();\r\n updateAbilityState();\r\n}\r\n\r\nfunction updateUI() {\r\n updateCombatStatsUI();\r\n updateFoodUI();\r\n updateDrinksUI();\r\n updateAbilityUI();\r\n\r\n updateContent();\r\n}\r\n\r\nconst darkModeToggle = document.getElementById('darkModeToggle');\r\nconst body = document.body;\r\n\r\nif (localStorage.getItem('darkModeEnabled') === 'true') {\r\n body.classList.add('dark-mode');\r\n const tables = document.getElementsByClassName('profit-table');\r\n for (const table of tables) {\r\n table.classList.toggle('table-striped');\r\n }\r\n darkModeToggle.checked = true;\r\n}\r\n\r\ndarkModeToggle.addEventListener('change', () => {\r\n body.classList.toggle('dark-mode');\r\n const tables = document.getElementsByClassName('profit-table');\r\n for (const table of tables) {\r\n table.classList.toggle('table-striped');\r\n }\r\n localStorage.setItem('darkModeEnabled', darkModeToggle.checked);\r\n});\r\n\r\nfunction updateContent() {\r\n document.querySelectorAll('[data-i18n]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n');\r\n if (key) {\r\n element.textContent = i18next.t(key);\r\n }\r\n });\r\n\r\n document.querySelectorAll('[data-i18n-placeholder]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n-placeholder');\r\n if (key) {\r\n element.placeholder = i18next.t(key);\r\n }\r\n });\r\n\r\n document.querySelectorAll('option[data-i18n]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n');\r\n if (key) {\r\n element.textContent = i18next.t(key);\r\n }\r\n });\r\n}\r\n\r\ninitEquipmentSection();\r\ninitHouseRoomsModal();\r\ninitAchievementsModal();\r\ninitLevelSection();\r\ninitFoodSection();\r\ninitDrinksSection();\r\ninitAbilitiesSection();\r\ninitZones();\r\ninitDungeons();\r\ninitTriggerModal();\r\ninitSimulationControls();\r\ninitEquipmentSetsModal();\r\ninitErrorHandling();\r\ninitImportExportModal();\r\ninitDamageDoneTaken();\r\ninitPatchNotes();\r\ninitExtraBuffSection();\r\n\r\nupdateState();\r\nupdateUI();\r\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/src/main.js b/src/main.js index 07f6ed27..8459c8e0 100644 --- a/src/main.js +++ b/src/main.js @@ -30,7 +30,7 @@ let simStartTime = 0; let worker = new Worker(new URL("worker.js", import.meta.url)); let multiWorker = new Worker(new URL("multiWorker.js", import.meta.url)); - +let workerPool = []; let player = new Player(); From 1842aa88571666ebf8ed24bdb470efd9b8dd1688 Mon Sep 17 00:00:00 2001 From: shykai Date: Wed, 24 Dec 2025 10:26:35 +0800 Subject: [PATCH 05/46] fix --- dist/bundle.js | 2 +- dist/patchNote.json | 4 ++++ dist/src_worker_js.bundle.js | 11 +++++++---- dist/src_worker_js.bundle.js.map | 2 +- patchNote.json | 4 ++++ src/combatsimulator/combatSimulator.js | 11 +++++++---- 6 files changed, 24 insertions(+), 10 deletions(-) diff --git a/dist/bundle.js b/dist/bundle.js index 5fde8c23..762d1e0e 100644 --- a/dist/bundle.js +++ b/dist/bundle.js @@ -1466,7 +1466,7 @@ class Trigger { \************************/ /***/ ((module) => { -module.exports = /*#__PURE__*/JSON.parse('{"2025年12月18日":["支持成就系统及对应buff效果","地下城怪物的掉落不再生效"],"2025年12月6日":["修复游戏更新后技能在无trigger情况下由[]变为null时造成的异常"],"2025年11月7日":["兼容支持从CN镜像站调用API获取价格"],"2025年10月14日":["修复怪物攻击间隔数值未能适配攻击等级的问题"],"2025年9月17日":["修复暴击光环的trigger缺陷"],"2025年9月9日":["复活时不再错误的清空所有buff","团灭日志增加反伤、荆棘和DOT伤害记录"],"2025年8月21日":["增加单挑战斗批量模拟和对应怪物选项","增加MooPass和社区buff的选项及对应功能","精炼装备数值加强","秘法主教属性削弱","init_client_info_v1.20250819.0.json游戏数据更新"],"2025年8月20日":["修复经验和掉落计算在极端情况下的可能异常"],"2025年8月19日":["合并Test和Temp分支的rework内容","init_client_info_v1.20250818.0.json游戏数据更新"],"2025年8月18日":["修复贯穿技能可能对相同目标造成重复伤害的问题","修复团灭日志在黑夜模式下的显示异常","战斗等级公式更新","钟乳石魔像的荆棘数值调整","init_client_info_v1.20250626.0_0817.json游戏数据更新"],"2025年8月16日":["增加停止模拟按钮 by BKN46","增加技能顺序调整按钮 by BKN46","增加团灭日志 by TruthLight","怪物属性更新","奥术反射更名为报应","init_client_info_v1.20250626.0_0815.json游戏数据更新"],"2025年8月14日":["怪物属性更新","远程和法师装备属性调整","反伤计算上限调整","修复战斗间隔释放技能的异常","修复技能释放判断逻辑的异常","法力值耗尽比例更加准确","调整远程经验的15%和魔法经验的12%映射到攻击经验","init_client_info_v1.20250626.0_0813.json游戏数据更新"],"2025年8月11日":["怪物属性更新","近战和物理技能施法时间更新","盾击和重锤数值调整","双手盾防御经验加成调整","init_client_info_v1.20250626.0_0811.json游戏数据更新"],"2025年8月8日":["实现组队等级差过大时对掉落和经验的惩罚","实现怪物经验随狂暴进度百分比增加","暴击光环数值调整","增加战斗等级数值显示","增加等级差距惩罚数值显示","init_client_info_v1.20250626.0_0807.json游戏数据更新"],"2025年8月7日":["修复组队战斗时一些重复物品掉落数量异常的缺陷 by contr4l","init_client_info_v1.20250626.0_0806.json游戏数据更新"],"2025年8月3日":["怪物狂暴机制及对应trigger生效","精炼装备更新,护符数值调整,守护光环增加闪避率","init_client_info_v1.20250626.0_0802.json游戏数据更新","狂怒层数修正为5层","招架结算机制调整"],"2025年7月31日":["物品数据和怪物属性更新","尖刺外壳和奥术反射重做","强化数值更新","删除异常trigger","狮鹫盾的虚弱重做","君王剑招架对队友生效","狂怒特效最大层数修正为6层","涟漪特效增加10MP恢复","反伤正确显示其命中率","反伤机制调整","同步双手盾属性和反伤荆棘技能数值的调整"],"2025年7月22日":["暴击光环受远程等级加成","光环基础数值和等级加成调整"],"2025年7月17日":["批量模拟支持勾选星球","经验分配比例调整至30%+70%","光环及对应trigger,并按对应技能等级百分比加成","水火自然默认调整为元素光环","init_client_info_v1.20250626.0_0717.json游戏数据更新"],"2025年7月11日":["怪物经验和技能等级公式更新","闪避和抗性计算公式更新","力量更替为近战以及对应的兼容","init_client_info_v1.20250626.0_0711.json游戏数据更新"],"2025年7月10日":["修复贯穿技能由敌人释放时可能多次击中相同目标的缺陷"],"2025年7月9日":["掉落和掉率调整","经验调整","疫病射击和破甲之刺调整","怪物自动恢复移除","疫病射击trigger调整","获取价格使用官方API"],"2025年7月7日":["怪物属性缩放和地图多难度","法师技能调整和装备上\'技能伤害\'词缀生效","攻击等级和房屋等级对施法速度的影响生效","物品调整","精准重做以攻击等级计算","TEST 远程魔法经验的10%映射到攻击经验!","经验重做和护符装备"]}'); +module.exports = /*#__PURE__*/JSON.parse('{"2025年12月24日":["修复技能释放选择的缺陷,之前可能存在异常缺蓝等情况"],"2025年12月18日":["支持成就系统及对应buff效果","地下城怪物的掉落不再生效"],"2025年12月6日":["修复游戏更新后技能在无trigger情况下由[]变为null时造成的异常"],"2025年11月7日":["兼容支持从CN镜像站调用API获取价格"],"2025年10月14日":["修复怪物攻击间隔数值未能适配攻击等级的问题"],"2025年9月17日":["修复暴击光环的trigger缺陷"],"2025年9月9日":["复活时不再错误的清空所有buff","团灭日志增加反伤、荆棘和DOT伤害记录"],"2025年8月21日":["增加单挑战斗批量模拟和对应怪物选项","增加MooPass和社区buff的选项及对应功能","精炼装备数值加强","秘法主教属性削弱","init_client_info_v1.20250819.0.json游戏数据更新"],"2025年8月20日":["修复经验和掉落计算在极端情况下的可能异常"],"2025年8月19日":["合并Test和Temp分支的rework内容","init_client_info_v1.20250818.0.json游戏数据更新"],"2025年8月18日":["修复贯穿技能可能对相同目标造成重复伤害的问题","修复团灭日志在黑夜模式下的显示异常","战斗等级公式更新","钟乳石魔像的荆棘数值调整","init_client_info_v1.20250626.0_0817.json游戏数据更新"],"2025年8月16日":["增加停止模拟按钮 by BKN46","增加技能顺序调整按钮 by BKN46","增加团灭日志 by TruthLight","怪物属性更新","奥术反射更名为报应","init_client_info_v1.20250626.0_0815.json游戏数据更新"],"2025年8月14日":["怪物属性更新","远程和法师装备属性调整","反伤计算上限调整","修复战斗间隔释放技能的异常","修复技能释放判断逻辑的异常","法力值耗尽比例更加准确","调整远程经验的15%和魔法经验的12%映射到攻击经验","init_client_info_v1.20250626.0_0813.json游戏数据更新"],"2025年8月11日":["怪物属性更新","近战和物理技能施法时间更新","盾击和重锤数值调整","双手盾防御经验加成调整","init_client_info_v1.20250626.0_0811.json游戏数据更新"],"2025年8月8日":["实现组队等级差过大时对掉落和经验的惩罚","实现怪物经验随狂暴进度百分比增加","暴击光环数值调整","增加战斗等级数值显示","增加等级差距惩罚数值显示","init_client_info_v1.20250626.0_0807.json游戏数据更新"],"2025年8月7日":["修复组队战斗时一些重复物品掉落数量异常的缺陷 by contr4l","init_client_info_v1.20250626.0_0806.json游戏数据更新"],"2025年8月3日":["怪物狂暴机制及对应trigger生效","精炼装备更新,护符数值调整,守护光环增加闪避率","init_client_info_v1.20250626.0_0802.json游戏数据更新","狂怒层数修正为5层","招架结算机制调整"],"2025年7月31日":["物品数据和怪物属性更新","尖刺外壳和奥术反射重做","强化数值更新","删除异常trigger","狮鹫盾的虚弱重做","君王剑招架对队友生效","狂怒特效最大层数修正为6层","涟漪特效增加10MP恢复","反伤正确显示其命中率","反伤机制调整","同步双手盾属性和反伤荆棘技能数值的调整"],"2025年7月22日":["暴击光环受远程等级加成","光环基础数值和等级加成调整"],"2025年7月17日":["批量模拟支持勾选星球","经验分配比例调整至30%+70%","光环及对应trigger,并按对应技能等级百分比加成","水火自然默认调整为元素光环","init_client_info_v1.20250626.0_0717.json游戏数据更新"],"2025年7月11日":["怪物经验和技能等级公式更新","闪避和抗性计算公式更新","力量更替为近战以及对应的兼容","init_client_info_v1.20250626.0_0711.json游戏数据更新"],"2025年7月10日":["修复贯穿技能由敌人释放时可能多次击中相同目标的缺陷"],"2025年7月9日":["掉落和掉率调整","经验调整","疫病射击和破甲之刺调整","怪物自动恢复移除","疫病射击trigger调整","获取价格使用官方API"],"2025年7月7日":["怪物属性缩放和地图多难度","法师技能调整和装备上\'技能伤害\'词缀生效","攻击等级和房屋等级对施法速度的影响生效","物品调整","精准重做以攻击等级计算","TEST 远程魔法经验的10%映射到攻击经验!","经验重做和护符装备"]}'); /***/ }), diff --git a/dist/patchNote.json b/dist/patchNote.json index 1c5d1580..c8160696 100644 --- a/dist/patchNote.json +++ b/dist/patchNote.json @@ -1,4 +1,8 @@ { + "2025年12月24日": + [ + "修复技能释放选择的缺陷,之前可能存在异常缺蓝等情况" + ], "2025年12月18日": [ "支持成就系统及对应buff效果", diff --git a/dist/src_worker_js.bundle.js b/dist/src_worker_js.bundle.js index 864af71a..b8e2c673 100644 --- a/dist/src_worker_js.bundle.js +++ b/dist/src_worker_js.bundle.js @@ -1209,9 +1209,11 @@ class CombatSimulator extends EventTarget { processRegenTickEvent(event) { let units = [...this.players]; - if (this.enemies) { - units.push(...this.enemies); - } + + // regen of emeny always set to 0, ingore the proc time + // if (this.enemies) { + // units.push(...this.enemies); + // } for (const unit of units) { if (unit.combatDetails.currentHitpoints <= 0) { @@ -1490,7 +1492,6 @@ class CombatSimulator extends EventTarget { castDuration /= (1 + source.combatDetails.combatStats.castSpeed) // console.log((this.simulationTime / 1000000000) + " Used ability " + ability.hrid + " Cast time " + (castDuration / 1e9)); }*/ - this.addNextAttackEvent(source); let todoAbilities = [ability]; @@ -1544,6 +1545,8 @@ class CombatSimulator extends EventTarget { } } + this.addNextAttackEvent(source); + // Could die from reflect damage if (source.combatDetails.currentHitpoints == 0) { this.eventQueue.clearEventsForUnit(source); diff --git a/dist/src_worker_js.bundle.js.map b/dist/src_worker_js.bundle.js.map index 522e34e3..1bc291e8 100644 --- a/dist/src_worker_js.bundle.js.map +++ b/dist/src_worker_js.bundle.js.map @@ -1 +1 @@ -{"version":3,"file":"src_worker_js.bundle.js","mappings":";;;;;;;;;;;;;;;;AAA0B;AACkC;AAC5B;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,wDAAgB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,6CAAI;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;ACxNG;AACkD;AACR;AACpE;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,gEAAwB;AAChE;AACA,0CAA0C,4DAAoB;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,WAAW;;;;;;;;;;;;;;AC1B1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,IAAI,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACd4B;AACO;AACQ;AACU;AAChB;AACM;AACF;AACF;AACd;AACgB;AACR;AACU;AACE;AACI;AACJ;AACE;AACJ;AACR;AACnB;AAC2B;AACF;AAC7B;AACA;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,0DAAU;AACxC,6BAA6B,mDAAS;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,kBAAkB;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,aAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,KAAK,MAAM,WAAW,OAAO,YAAY;AAChE,wBAAwB,YAAY,KAAK,YAAY;AACrD,yBAAyB,cAAc,IAAI,YAAY;AACvD,4BAA4B,0BAA0B,OAAO,IAAI,UAAU,GAAG,MAAM,eAAe;AACnG;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA,mCAAmC,UAAU;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA,mCAAmC,UAAU;AAC7C;AACA;AACA;AACA;AACA;AACA,gBAAgB,yBAAyB;AACzC;AACA;AACA,wBAAwB,WAAW;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,gEAAgB;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,iBAAiB;AACjB;AACA;AACA;AACA;AACA,2BAA2B,0CAA0C;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0CAA0C;AAC1E;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA,wBAAwB,yBAAyB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,mDAAS;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,gEAAgB;AACjC;AACA;AACA,iBAAiB,kEAAkB;AACnC;AACA;AACA,iBAAiB,iEAAiB;AAClC;AACA;AACA,iBAAiB,+DAAe;AAChC;AACA;AACA,iBAAiB,mEAAmB;AACpC;AACA;AACA,iBAAiB,mEAAmB;AACpC;AACA;AACA,iBAAiB,wEAAwB;AACzC;AACA;AACA,iBAAiB,+DAAc;AAC/B;AACA;AACA,iBAAiB,oEAAmB;AACpC;AACA;AACA,iBAAiB,qEAAoB;AACrC;AACA;AACA,iBAAiB,uEAAsB;AACvC;AACA;AACA,iBAAiB,qEAAoB;AACrC;AACA;AACA,iBAAiB,sEAAqB;AACtC;AACA;AACA,iBAAiB,oEAAmB;AACpC;AACA;AACA,iBAAiB,gEAAe;AAChC;AACA;AACA,iBAAiB,oEAAmB;AACpC;AACA;AACA,iBAAiB,mEAAkB;AACnC;AACA;AACA;AACA,iBAAiB,kEAAkB;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,yBAAyB;AACjD,mCAAmC;AACnC;AACA;AACA;AACA;AACA,iCAAiC,+DAAc;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,yBAAyB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,0CAA0C,gEAAe;AACzD,kCAAkC,gEAAe;AACjD;AACA;AACA;AACA,0CAA0C,oEAAmB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,yBAAyB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,wDAAe;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6FAA6F,qEAAoB;AACjH;AACA;AACA,uEAAuE,qEAAoB;AAC3F;AACA,+CAA+C,qEAAoB;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4FAA4F,oEAAmB;AAC/G,uEAAuE,oEAAmB;AAC1F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,oEAAmB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8FAA8F,sEAAqB;AACnH;AACA;AACA;AACA,uEAAuE,sEAAqB;AAC5F,gDAAgD,sEAAqB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,+DAAe;AAC7D;AACA,wCAAwC,iEAAiB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4GAA4G,kEAAkB;AAC9H;AACA,iDAAiD,kEAAkB;AACnE;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,gEAAgB;AAC3D;AACA,cAAc;AACd,kDAAkD,+DAAe;AACjE,kDAAkD,oEAAmB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,oEAAmB,uBAAuB,+DAAe;AAC3H;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,wDAAe;AACpC;AACA;AACA,UAAU;AACV,qBAAqB,wDAAe;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD,oEAAmB;AACzE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sCAAsC,+DAAe;AACrD;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,wDAAe;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,wDAAe;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,mEAAkB;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,mEAAmB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,wDAAe;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,mEAAmB;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,mEAAkB;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,+DAAc;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,kCAAkC,gEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,wDAAe;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,qCAAqC,kEAAkB;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,mEAAkB;AACnE;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV,0CAA0C,mEAAmB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+CAA+C,wEAAwB;AACvE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,mCAAmC,iDAAO;AAC1C;AACA;AACA;AACA,mCAAmC,iDAAO;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,uDAAuD,wEAAwB;AAC/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+CAA+C,wEAAwB;AACvE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC;AACpC;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,wDAAe;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,wDAAe;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2DAA2D,wEAAwB;AACnF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,mEAAmB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4EAA4E,+DAAe,uBAAuB,oEAAmB,uBAAuB,oEAAmB;AAC/K,kDAAkD,oEAAmB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA,2EAA2E,qEAAoB;AAC/F,+EAA+E,+DAAe;AAC9F;AACA;AACA;AACA,mDAAmD,qEAAoB;AACvE;AACA;AACA;AACA;AACA;AACA;AACA,2EAA2E,uEAAsB;AACjG,+EAA+E,oEAAmB;AAClG;AACA;AACA;AACA,qDAAqD,uEAAsB;AAC3E;AACA;AACA;AACA;AACA;AACA,iGAAiG,qEAAoB;AACrH;AACA;AACA,2EAA2E,qEAAoB;AAC/F;AACA,mDAAmD,qEAAoB;AACvE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kGAAkG,sEAAqB;AACvH;AACA;AACA;AACA,2EAA2E,sEAAqB;AAChG,oDAAoD,sEAAqB;AACzE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,wDAAe;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,wDAAe;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,wDAAe;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mEAAmE,kEAAkB;AACrF;AACA;AACA;AACA,+BAA+B,wDAAe;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,iDAAO;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,wDAAe;AACrC;AACA;AACA;AACA;AACA;AACA,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;;ACxjD/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wDAAwD;AACtF,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,mBAAmB;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;AC/gB1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;;;;;AC9VL;AAC4B;AACtB;AAChC;AACA;AACA;AACA;AACA;AACA,6BAA6B,qDAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;ACtF1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,KAAK,EAAC;;;;;;;;;;;;;;;;;ACXiC;AACmD;AACzG;AACA;AACA;AACA;AACA,uBAAuB,qDAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,iFAAoC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS,EAAC;;;;;;;;;;;;;;;;AC/Ce;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB;;;;;;;;;;;;;;;ACbM;AACxC;AACA,8BAA8B,oDAAW;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;;;ACZS;AACxC;AACA,iCAAiC,oDAAW;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,kBAAkB;;;;;;;;;;;;;;;ACZO;AACxC;AACA,mCAAmC,oDAAW;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,oBAAoB;;;;;;;;;;;;;;;ACZK;AACxC;AACA,uCAAuC,oDAAW;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,wBAAwB,EAAC;;;;;;;;;;;;;;;ACZxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,WAAW,EAAC;;;;;;;;;;;;;;;;ACPa;AACxC;AACA,+BAA+B,oDAAW;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,gBAAgB,EAAC;;;;;;;;;;;;;;;;ACVQ;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB,EAAC;;;;;;;;;;;;;;;;ACfK;AACxC;AACA,iCAAiC,oDAAW;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,kBAAkB,EAAC;;;;;;;;;;;;;;;;ACVM;AACxC;AACA,mCAAmC,oDAAW;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,oBAAoB;;;;;;;;;;;;;;;ACfK;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB,EAAC;;;;;;;;;;;;;;;;AClBK;AACxC;AACA,gCAAgC,oDAAW;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,iBAAiB,EAAC;;;;;;;;;;;;;;;;ACVO;AACxC;AACA,8BAA8B,oDAAW;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;;;ACbJ;AAC3B;AACA;AACA;AACA,2BAA2B,+CAAI;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,+CAAI;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;AChEc;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB;;;;;;;;;;;;;;;ACbM;AACxC;AACA,iCAAiC,oDAAW;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,kBAAkB,EAAC;;;;;;;;;;;;;;;;ACXM;AACxC;AACA,6BAA6B,oDAAW;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,cAAc,EAAC;;;;;;;;;;;;;;;;ACVU;AACxC;AACA,qCAAqC,oDAAW;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,sBAAsB;;;;;;;;;;;;;;;ACZG;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB;;;;;;;;;;;;;;;ACZM;AACxC;AACA,oCAAoC,oDAAW;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,qBAAqB;;;;;;;;;;;;;;;;AChBV;AACsC;AAChE;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,0DAAkB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS;;;;;;;;;;;;;;;;;;AC7BQ;AACM;AACkC;AAC5C;AAC5B;AACA,sBAAsB,mDAAU;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,8DAAsB;AAChD;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,kCAAkC;AAC1D;AACA;AACA;AACA,oCAAoC,gDAAO;AAC3C;AACA;AACA,wBAAwB,kCAAkC;AAC1D,oCAAoC,8CAAK;AACzC;AACA,wBAAwB,sCAAsC;AAC9D;AACA;AACA;AACA,wCAAwC,8CAAK;AAC7C;AACA;AACA;AACA;AACA,0BAA0B,8DAAsB;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;;;;AClJS;AACM;AACA;AACF;AACA;AACI;AACxC;AACA,qBAAqB,mDAAU;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,kDAAS;AACrD;AACA;AACA,qDAAqD,mDAAU;AAC/D,2DAA2D,mDAAU;AACrE,qEAAqE,gDAAO;AAC5E;AACA;AACA,2CAA2C,kDAAS;AACpD;AACA,SAAS;AACT;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,MAAM,EAAC;;;;;;;;;;;;;;;;ACvL6C;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd,2CAA2C,uEAAuE;AAClH;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,4DAAoB;AAChD;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD;AACtD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS,EAAC;;;;;;;;;;;;;;;;ACtPmE;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,wEAAgC;AAC5C;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;ACjLmC;AAC1B;AAChC;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,uDAAe;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yEAAyE,gDAAO;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,yDAAyD;AACxF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,wEAAwE;AACtH;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gDAAgD,gDAAO;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wDAAwD,gDAAO;AAC/D,UAAU;AACV;AACA;AACA;AACA;AACA,cAAc;AACd,gCAAgC,yBAAyB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,iCAAiC;AACpE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,wEAAwE;AAC1H;AACA,0BAA0B;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oDAAoD,gDAAO;AAC3D;AACA;AACA;AACA;AACA,iEAAe,IAAI,EAAC;;;;;;;;;;;;;;;ACjH4C;AAClB;AACJ;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,6DAAI;AAC/B,4BAA4B,wBAAwB;AACpD,oCAAoC,+DAAM;AAC1C;AACA;AACA;AACA;AACA;AACA,sCAAsC,wEAAe;AACrD;AACA,mCAAmC,oIAAoI;AACvK,aAAa;AACb;AACA;AACA;AACA,mCAAmC,iDAAiD;AACpF,cAAc;AACd;AACA,mCAAmC,oCAAoC;AACvE;AACA;AACA;AACA","sources":["webpack://mwicombatsimulator/./src/combatsimulator/ability.js","webpack://mwicombatsimulator/./src/combatsimulator/achievement.js","webpack://mwicombatsimulator/./src/combatsimulator/buff.js","webpack://mwicombatsimulator/./src/combatsimulator/combatSimulator.js","webpack://mwicombatsimulator/./src/combatsimulator/combatUnit.js","webpack://mwicombatsimulator/./src/combatsimulator/combatUtilities.js","webpack://mwicombatsimulator/./src/combatsimulator/consumable.js","webpack://mwicombatsimulator/./src/combatsimulator/drops.js","webpack://mwicombatsimulator/./src/combatsimulator/equipment.js","webpack://mwicombatsimulator/./src/combatsimulator/events/abilityCastEndEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/autoAttackEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/awaitCooldownEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/blindExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/checkBuffExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/combatEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/combatStartEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/consumableTickEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/cooldownReadyEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/curseExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/damageOverTimeEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/enemyRespawnEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/enrageTickEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/eventQueue.js","webpack://mwicombatsimulator/./src/combatsimulator/events/furyExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/playerRespawnEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/regenTickEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/silenceExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/stunExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/weakenExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/houseRoom.js","webpack://mwicombatsimulator/./src/combatsimulator/monster.js","webpack://mwicombatsimulator/./src/combatsimulator/player.js","webpack://mwicombatsimulator/./src/combatsimulator/simResult.js","webpack://mwicombatsimulator/./src/combatsimulator/trigger.js","webpack://mwicombatsimulator/./src/combatsimulator/zone.js","webpack://mwicombatsimulator/./src/worker.js"],"sourcesContent":["import Buff from \"./buff\";\r\nimport abilityDetailMap from \"./data/abilityDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nconst abilityFromCombatStat = {\r\n \"blaze\":\r\n {\r\n \"hrid\": \"/abilities/blaze\",\r\n \"name\": \"Blaze\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"allEnemies\",\r\n \"effectType\": \"/ability_effect_types/damage\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"/damage_types/fire\",\r\n \"baseDamageFlat\": 0,\r\n \"baseDamageFlatLevelBonus\": 0.0,\r\n \"baseDamageRatio\": 0.3,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/number_of_active_units\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n },\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/current_hp\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n }\r\n ],\r\n },\r\n \"bloom\":\r\n {\r\n \"hrid\": \"/abilities/bloom\",\r\n \"name\": \"Bloom\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"lowestHpAlly\",\r\n \"effectType\": \"/ability_effect_types/heal\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"\",\r\n \"baseDamageFlat\": 10,\r\n \"baseDamageFlatLevelBonus\": 0,\r\n \"baseDamageRatio\": 0.15,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_allies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/lowest_hp_percentage\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/less_than_equal\",\r\n \"value\": 100\r\n }\r\n ],\r\n }\r\n}\r\n\r\nclass Ability {\r\n constructor(hrid, level = 1, triggers = null) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameAbility = abilityDetailMap[hrid];\r\n if (!gameAbility) {\r\n gameAbility = abilityFromCombatStat[hrid];\r\n }\r\n if (!gameAbility) {\r\n throw new Error(\"No ability found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.manaCost = gameAbility.manaCost;\r\n this.cooldownDuration = gameAbility.cooldownDuration;\r\n this.castDuration = gameAbility.castDuration;\r\n this.isSpecialAbility = gameAbility.isSpecialAbility;\r\n\r\n this.abilityEffects = [];\r\n\r\n for (const effect of gameAbility.abilityEffects) {\r\n let abilityEffect = {\r\n targetType: effect.targetType,\r\n effectType: effect.effectType,\r\n combatStyleHrid: effect.combatStyleHrid,\r\n damageType: effect.damageType,\r\n damageFlat: effect.baseDamageFlat + (this.level - 1) * effect.baseDamageFlatLevelBonus,\r\n damageRatio: effect.baseDamageRatio + (this.level - 1) * effect.baseDamageRatioLevelBonus,\r\n bonusAccuracyRatio: effect.bonusAccuracyRatio + (this.level - 1) * effect.bonusAccuracyRatioLevelBonus,\r\n damageOverTimeRatio: effect.damageOverTimeRatio,\r\n damageOverTimeDuration: effect.damageOverTimeDuration,\r\n armorDamageRatio: effect.armorDamageRatio + (this.level - 1) * effect.armorDamageRatioLevelBonus,\r\n hpDrainRatio: effect.hpDrainRatio,\r\n pierceChance: effect.pierceChance,\r\n blindChance: effect.blindChance,\r\n blindDuration: effect.blindDuration,\r\n silenceChance: effect.silenceChance,\r\n silenceDuration: effect.silenceDuration,\r\n stunChance: effect.stunChance,\r\n stunDuration: effect.stunDuration,\r\n spendHpRatio: effect.spendHpRatio,\r\n buffs: null,\r\n };\r\n if (effect.buffs) {\r\n abilityEffect.buffs = [];\r\n for (const buff of effect.buffs) {\r\n abilityEffect.buffs.push(new Buff(buff, this.level));\r\n }\r\n }\r\n this.abilityEffects.push(abilityEffect);\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameAbility.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let ability = new Ability(dto.hrid, dto.level, triggers);\r\n\r\n return ability;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n\r\n if (source.isSilenced) {\r\n return false;\r\n }\r\n\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = this.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Ability;\r\n","import Buff from \"./buff\";\r\nimport achievementTierDetailMap from \"./data/achievementTierDetailMap.json\";\r\nimport achievementDetailMap from \"./data/achievementDetailMap.json\";\r\n\r\nclass Achievement {\r\n constructor(achievements) {\r\n this.achievements = achievements;\r\n this.buffs = [];\r\n\r\n for(const tier of Object.values(achievementTierDetailMap)) {\r\n let isGetAll = true;\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid)\r\n for(const achievement of Object.values(detailMap)) {\r\n if(!this.achievements[achievement.hrid] || this.achievements[achievement.hrid] == false) {\r\n isGetAll = false;\r\n break;\r\n }\r\n }\r\n if(isGetAll) {\r\n let buff = new Buff(tier.buff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default Achievement;","class Buff {\r\n startTime;\r\n\r\n constructor(buff, level = 1) {\r\n this.uniqueHrid = buff.uniqueHrid;\r\n this.typeHrid = buff.typeHrid;\r\n this.ratioBoost = buff.ratioBoost + (level - 1) * buff.ratioBoostLevelBonus;\r\n this.flatBoost = buff.flatBoost + (level - 1) * buff.flatBoostLevelBonus;\r\n this.duration = buff.duration;\r\n this.multiplierForSkillHrid = buff.multiplierForSkillHrid ?? \"\";\r\n this.multiplierPerSkillLevel = buff.multiplierPerSkillLevel ?? 0;\r\n }\r\n}\r\n\r\nexport default Buff;\r\n","import CombatUtilities from \"./combatUtilities\";\r\nimport AutoAttackEvent from \"./events/autoAttackEvent\";\r\nimport DamageOverTimeEvent from \"./events/damageOverTimeEvent\";\r\nimport CheckBuffExpirationEvent from \"./events/checkBuffExpirationEvent\";\r\nimport CombatStartEvent from \"./events/combatStartEvent\";\r\nimport ConsumableTickEvent from \"./events/consumableTickEvent\";\r\nimport CooldownReadyEvent from \"./events/cooldownReadyEvent\";\r\nimport EnemyRespawnEvent from \"./events/enemyRespawnEvent\";\r\nimport EventQueue from \"./events/eventQueue\";\r\nimport PlayerRespawnEvent from \"./events/playerRespawnEvent\";\r\nimport RegenTickEvent from \"./events/regenTickEvent\";\r\nimport StunExpirationEvent from \"./events/stunExpirationEvent\";\r\nimport BlindExpirationEvent from \"./events/blindExpirationEvent\";\r\nimport SilenceExpirationEvent from \"./events/silenceExpirationEvent\";\r\nimport CurseExpirationEvent from \"./events/curseExpirationEvent\";\r\nimport WeakenExpirationEvent from \"./events/weakenExpirationEvent\";\r\nimport FuryExpirationEvent from \"./events/furyExpirationEvent\";\r\nimport EnrageTickEvent from \"./events/enrageTickEvent\";\r\nimport SimResult from \"./simResult\";\r\nimport AbilityCastEndEvent from \"./events/abilityCastEndEvent\";\r\nimport AwaitCooldownEvent from \"./events/awaitCooldownEvent\";\r\nimport Monster from \"./monster\";\r\nimport Ability from \"./ability\";\r\n\r\nconst ONE_SECOND = 1e9;\r\nconst HOT_TICK_INTERVAL = 5 * ONE_SECOND;\r\nconst DOT_TICK_INTERVAL = 3 * ONE_SECOND;\r\nconst REGEN_TICK_INTERVAL = 10 * ONE_SECOND;\r\nconst ENEMY_RESPAWN_INTERVAL = 3 * ONE_SECOND;\r\nconst PLAYER_RESPAWN_INTERVAL = 150 * ONE_SECOND;\r\nconst RESTART_INTERVAL = 15 * ONE_SECOND;\r\nconst ENRAGE_TICK_INTERVAL = 60 * ONE_SECOND;\r\n\r\nclass CombatSimulator extends EventTarget {\r\n constructor(players, zone) {\r\n super();\r\n this.players = players;\r\n this.zone = zone;\r\n this.eventQueue = new EventQueue();\r\n this.simResult = new SimResult(zone, players.length);\r\n this.allPlayersDead = false;\r\n\r\n this.wipeLogs = {\r\n buffer: new Array(200),\r\n index: 0,\r\n count: 0,\r\n maxSize: 200\r\n };\r\n }\r\n\r\n addToWipeLogs(logEntry) {\r\n const { buffer, maxSize } = this.wipeLogs;\r\n\r\n buffer[this.wipeLogs.index] = logEntry;\r\n this.wipeLogs.index = (this.wipeLogs.index + 1) % maxSize;\r\n this.wipeLogs.count = Math.min(this.wipeLogs.count + 1, maxSize);\r\n }\r\n\r\n logAndResetWipeLogs() {\r\n const logs = this.getOrderedWipeLogs();\r\n \r\n // console.log(\"===== 团灭日志 =====\");\r\n // console.log(`最后 ${logs.length} 条战斗日志:`);\r\n \r\n logs.forEach(log => {\r\n if (log.error) {\r\n console.log(log.error);\r\n return;\r\n }\r\n \r\n const time = (log.time / 1e9).toFixed(2);\r\n // console.log(\r\n // `[${time}s] [${log.source}] 用 [${log.ability}] ` +\r\n // `对 ${log.target} 造成 ${log.damage} 伤害,` +\r\n // `HP ${log.beforeHp} → ${log.afterHp}。` +\r\n // `队伍生命值:${log.playersHp.map(p => `${p.hrid}: ${p.current}/${p.max}`).join(\" | \")}`\r\n // );\r\n });\r\n\r\n this.wipeLogs.index = 0;\r\n this.wipeLogs.count = 0;\r\n // console.log(\"===== 团灭日志结束 =====\");\r\n }\r\n \r\n buildCombatLog(source, ability, target, damageDone) {\r\n try {\r\n const sourceHrid = source?.hrid || \"UNKNOWN_SOURCE\";\r\n const targetHrid = target?.hrid || \"UNKNOWN_TARGET\";\r\n \r\n const afterHp = target?.combatDetails?.currentHitpoints || 0;\r\n const beforeHp = Math.max(0, afterHp + damageDone);\r\n\r\n const playersHp = this.players.map(p => ({\r\n hrid: p.hrid || \"UNKNOWN_PLAYER\",\r\n current: p.combatDetails?.currentHitpoints ?? 0,\r\n max: p.combatDetails?.maxHitpoints ?? 0\r\n }));\r\n \r\n return {\r\n time: this.simulationTime,\r\n wave: (this.zone.encountersKilled - 1),\r\n source: sourceHrid,\r\n ability: ability,\r\n target: targetHrid,\r\n damage: damageDone,\r\n beforeHp: beforeHp,\r\n afterHp: afterHp,\r\n playersHp: playersHp,\r\n // enemiesHp: enemiesHp,\r\n isCrit: false,\r\n };\r\n } catch (e) {\r\n return {\r\n error: `[日志生成错误] ${e.message}`\r\n };\r\n }\r\n }\r\n\r\n generateCombatLog(source, ability, target, attackResult) {\r\n try {\r\n const sourceHrid = source?.hrid || \"UNKNOWN_SOURCE\";\r\n const targetHrid = target?.hrid || \"UNKNOWN_TARGET\";\r\n const damage = attackResult?.damageDone || 0;\r\n \r\n const afterHp = target?.combatDetails?.currentHitpoints || 0;\r\n const beforeHp = Math.max(0, afterHp + damage);\r\n\r\n const playersHp = this.players.map(p => ({\r\n hrid: p.hrid || \"UNKNOWN_PLAYER\",\r\n current: p.combatDetails?.currentHitpoints ?? 0,\r\n max: p.combatDetails?.maxHitpoints ?? 0\r\n }));\r\n \r\n return {\r\n time: this.simulationTime,\r\n wave: (this.zone.encountersKilled - 1),\r\n source: sourceHrid,\r\n ability: ability,\r\n target: targetHrid,\r\n damage: damage,\r\n beforeHp: beforeHp,\r\n afterHp: afterHp,\r\n playersHp: playersHp,\r\n // enemiesHp: enemiesHp,\r\n isCrit: attackResult?.isCrit || false,\r\n };\r\n } catch (e) {\r\n return {\r\n error: `[日志生成错误] ${e.message}`\r\n };\r\n }\r\n }\r\n \r\n getOrderedWipeLogs() {\r\n const { buffer, maxSize, count } = this.wipeLogs;\r\n const logs = [];\r\n \r\n for (let i = 0; i < count; i++) {\r\n const idx = (this.wipeLogs.index - count + maxSize + i) % maxSize;\r\n logs.push(buffer[idx]);\r\n }\r\n \r\n return logs;\r\n }\r\n\r\n saveWipeLogsToSimResult(wave) {\r\n const logs = this.getOrderedWipeLogs();\r\n this.simResult.addWipeEvent(logs, this.simulationTime, wave);\r\n }\r\n\r\n async simulate(simulationTimeLimit) {\r\n this.reset();\r\n\r\n let ticks = 0;\r\n\r\n let combatStartEvent = new CombatStartEvent(0);\r\n this.eventQueue.addEvent(combatStartEvent);\r\n\r\n while (this.simulationTime < simulationTimeLimit) {\r\n let nextEvent = this.eventQueue.getNextEvent();\r\n await this.processEvent(nextEvent);\r\n\r\n ticks++;\r\n if (ticks == 1000) {\r\n ticks = 0;\r\n let progressEvent = new CustomEvent(\"progress\", {\r\n detail: {\r\n zone: this.zone.hrid,\r\n difficultyTier: this.zone.difficultyTier,\r\n progress: Math.min(this.simulationTime / simulationTimeLimit, 1)\r\n },\r\n });\r\n this.dispatchEvent(progressEvent);\r\n }\r\n }\r\n\r\n // for (let i = 0; i < this.simResult.timeSpentAlive.length; i++) {\r\n // if (this.simResult.timeSpentAlive[i].alive == true) {\r\n // this.simResult.updateTimeSpentAlive(this.simResult.timeSpentAlive[i].name, false, simulationTimeLimit);\r\n // }\r\n // }\r\n\r\n this.simResult.isDungeon = this.zone.isDungeon;\r\n if (this.simResult.isDungeon) {\r\n console.log(\"Timeout now at wave #\" + (this.zone.encountersKilled - 1));\r\n\r\n this.simResult.dungeonsCompleted = this.zone.dungeonsCompleted;\r\n this.simResult.dungeonsFailed = this.zone.dungeonsFailed;\r\n if (this.simResult.dungeonsCompleted < 1) {\r\n this.simResult.maxWaveReached = 0;\r\n for (let i = 0; i <= this.zone.dungeonSpawnInfo.maxWaves; i++) {\r\n let waveName = \"#\" + i.toString();\r\n const idx = this.simResult.timeSpentAlive.findIndex(e => e.name === waveName);\r\n if (idx == -1 || this.simResult.timeSpentAlive[idx].count == 0) {\r\n break;\r\n }\r\n this.simResult.maxWaveReached = i;\r\n }\r\n } else {\r\n this.simResult.maxWaveReached = this.zone.dungeonSpawnInfo.maxWaves;\r\n }\r\n }\r\n this.simResult.simulatedTime = this.simulationTime;\r\n \r\n for (let i = 0; i < this.players.length; i++) {\r\n this.simResult.setDropRateMultipliers(this.players[i]);\r\n this.simResult.setManaUsed(this.players[i]);\r\n }\r\n\r\n if (this.zone.isDungeon) {\r\n Object.entries(this.zone.dungeonSpawnInfo.fixedSpawnsMap).forEach(([wave, monsters]) => {\r\n let waveName = \"#\" + wave.toString();\r\n monsters.forEach(monster => {\r\n waveName += ',' + monster.combatMonsterHrid;\r\n });\r\n this.simResult.bossSpawns.push(waveName);\r\n });\r\n\r\n }\r\n if (this.zone.monsterSpawnInfo.bossSpawns) {\r\n for (const boss of this.zone.monsterSpawnInfo.bossSpawns) {\r\n this.simResult.bossSpawns.push(boss.combatMonsterHrid);\r\n }\r\n }\r\n\r\n return this.simResult;\r\n }\r\n\r\n reset() {\r\n this.tempDungeonCount = 0;\r\n this.simulationTime = 0;\r\n this.eventQueue.clear();\r\n this.simResult = new SimResult(this.zone, this.players.length);\r\n }\r\n\r\n async processEvent(event) {\r\n this.simulationTime = event.time;\r\n\r\n // console.log(this.simulationTime / 1e9, event.type, event);\r\n\r\n switch (event.type) {\r\n case CombatStartEvent.type:\r\n this.processCombatStartEvent(event);\r\n break;\r\n case PlayerRespawnEvent.type:\r\n this.processPlayerRespawnEvent(event);\r\n break;\r\n case EnemyRespawnEvent.type:\r\n this.processEnemyRespawnEvent(event);\r\n break;\r\n case AutoAttackEvent.type:\r\n this.processAutoAttackEvent(event);\r\n break;\r\n case ConsumableTickEvent.type:\r\n this.processConsumableTickEvent(event);\r\n break;\r\n case DamageOverTimeEvent.type:\r\n this.processDamageOverTimeTickEvent(event);\r\n break;\r\n case CheckBuffExpirationEvent.type:\r\n this.processCheckBuffExpirationEvent(event);\r\n break;\r\n case RegenTickEvent.type:\r\n this.processRegenTickEvent(event);\r\n break;\r\n case StunExpirationEvent.type:\r\n this.processStunExpirationEvent(event);\r\n break;\r\n case BlindExpirationEvent.type:\r\n this.processBlindExpirationEvent(event);\r\n break;\r\n case SilenceExpirationEvent.type:\r\n this.processSilenceExpirationEvent(event);\r\n break;\r\n case CurseExpirationEvent.type:\r\n this.processCurseExpirationEvent(event);\r\n break;\r\n case WeakenExpirationEvent.type:\r\n this.processWeakenExpirationEvent(event);\r\n break;\r\n case FuryExpirationEvent.type:\r\n this.processFuryExpirationEvent(event);\r\n break;\r\n case EnrageTickEvent.type:\r\n this.processEnrageTickEvent(event);\r\n break;\r\n case AbilityCastEndEvent.type:\r\n this.tryUseAbility(event.source, event.ability);\r\n break;\r\n case AwaitCooldownEvent.type:\r\n // console.log(\"Await CD \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(event.source);\r\n break;\r\n case CooldownReadyEvent.type:\r\n // Only used to check triggers\r\n break;\r\n }\r\n\r\n this.checkTriggers();\r\n }\r\n\r\n processCombatStartEvent(event) {\r\n // console.log(\"Combat Start \" + (this.simulationTime / 1000000000));\r\n for (let i = 0; i < this.players.length; i++) {\r\n if (event.time == 0) { // First combat start event\r\n this.players[i].generatePermanentBuffs();\r\n }\r\n this.players[i].reset(this.simulationTime);\r\n }\r\n let regenTickEvent = new RegenTickEvent(this.simulationTime + REGEN_TICK_INTERVAL);\r\n this.eventQueue.addEvent(regenTickEvent);\r\n\r\n this.startNewEncounter();\r\n }\r\n\r\n processPlayerRespawnEvent(event) {\r\n // console.log(\"Player \" + event.hrid + \" respawn at \" + + (this.simulationTime / 1000000000));\r\n let respawningPlayer = this.players.find(player => player.hrid === event.hrid);\r\n respawningPlayer.combatDetails.currentHitpoints = respawningPlayer.combatDetails.maxHitpoints;\r\n respawningPlayer.combatDetails.currentManapoints = respawningPlayer.combatDetails.maxManapoints;\r\n respawningPlayer.clearBuffs();\r\n respawningPlayer.clearCCs();\r\n if (this.allPlayersDead) {\r\n this.allPlayersDead = false;\r\n this.startAttacks();\r\n } else {\r\n this.addNextAttackEvent(respawningPlayer);\r\n }\r\n }\r\n\r\n processEnemyRespawnEvent(event) {\r\n this.startNewEncounter();\r\n }\r\n\r\n startNewEncounter() {\r\n if (this.allPlayersDead) {\r\n this.allPlayersDead = false;\r\n this.zone.failWave();\r\n }\r\n\r\n if (!this.zone.isDungeon) {\r\n this.enemies = this.zone.getRandomEncounter();\r\n } else {\r\n this.enemies = this.zone.getNextWave();\r\n this.simResult.updateTimeSpentAlive(\"#\" + (this.zone.encountersKilled - 1).toString(), true, this.simulationTime);\r\n let currentDungeonCount = this.zone.dungeonsCompleted;\r\n // console.log('wave at #' + (this.zone.encountersKilled - 1) +' completed:' + this.zone.dungeonsCompleted + ' failed:'+ this.zone.dungeonsFailed + ' temp:'+ this.tempDungeonCount);\r\n if (currentDungeonCount > this.tempDungeonCount) {\r\n this.tempDungeonCount = currentDungeonCount;\r\n for (let i = 0; i < this.players.length; i++) {\r\n this.players[i].combatDetails.currentHitpoints = this.players[i].combatDetails.maxHitpoints;\r\n this.players[i].combatDetails.currentManapoints = this.players[i].combatDetails.maxManapoints;\r\n // this.simResult.playerRanOutOfMana[this.players[i].hrid] = false;\r\n }\r\n }\r\n }\r\n\r\n this.enemies.forEach((enemy) => {\r\n enemy.reset(this.simulationTime);\r\n this.simResult.updateTimeSpentAlive(enemy.hrid, true, this.simulationTime);\r\n //console.log(enemy.hrid, \"spawned\");\r\n });\r\n\r\n this.eventQueue.clearEventsOfType(EnrageTickEvent.type);\r\n let enrageTickEvent = new EnrageTickEvent(this.simulationTime + ENRAGE_TICK_INTERVAL, ENRAGE_TICK_INTERVAL);\r\n this.eventQueue.addEvent(enrageTickEvent);\r\n this.enrageBeginTime = this.simulationTime;\r\n\r\n this.eventQueue.clearEventsOfType(AbilityCastEndEvent.type);\r\n\r\n this.startAttacks();\r\n }\r\n\r\n startAttacks() {\r\n let units = [...this.players];\r\n if (this.enemies) {\r\n units.push(...this.enemies);\r\n }\r\n\r\n for (const unit of units) {\r\n if (unit.combatDetails.currentHitpoints <= 0) {\r\n continue;\r\n }\r\n\r\n /*-if (unit.isPlayer) {\r\n // console.log(\"Start Attacks \" + (this.simulationTime / 1000000000));\r\n }*/\r\n this.addNextAttackEvent(unit);\r\n }\r\n }\r\n\r\n checkParry(targets) {\r\n let parryUnits = targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0 && unit.combatDetails.combatStats.parry > 0);\r\n if (parryUnits.length <= 0) {\r\n return undefined;\r\n }\r\n let randomIndex = Math.floor(Math.random() * parryUnits.length);\r\n if (parryUnits[randomIndex].combatDetails.combatStats.parry > Math.random()) {\r\n return parryUnits[randomIndex];\r\n }\r\n return undefined;\r\n }\r\n\r\n processAutoAttackEvent(event) {\r\n // console.log(\"source:\", event.source.hrid);\r\n // console.log(\"aa \" + (this.simulationTime / 1000000000));\r\n\r\n let targets = event.source.isPlayer ? this.enemies : this.players;\r\n\r\n if (!targets) {\r\n return;\r\n }\r\n\r\n const aliveTargets = targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0);\r\n\r\n for (let i = 0; i < aliveTargets.length; i++) {\r\n let target = aliveTargets[i];\r\n if (!event.source.isPlayer && aliveTargets.length > 1) {\r\n let cumulativeThreat = 0;\r\n let cumulativeRanges = [];\r\n aliveTargets.forEach(player => {\r\n let playerThreat = player.combatDetails.combatStats.threat;\r\n cumulativeThreat += playerThreat;\r\n cumulativeRanges.push({\r\n player: player,\r\n rangeStart: cumulativeThreat - playerThreat,\r\n rangeEnd: cumulativeThreat\r\n });\r\n });\r\n let randomValueHit = Math.random() * cumulativeThreat;\r\n target = cumulativeRanges.find(range => randomValueHit >= range.rangeStart && randomValueHit < range.rangeEnd).player;\r\n }\r\n let source = event.source;\r\n\r\n let parryTarget = this.checkParry(targets);\r\n if (parryTarget) {\r\n target = source;\r\n source = parryTarget;\r\n }\r\n\r\n let attackResult = CombatUtilities.processAttack(source, target);\r\n if (this.zone.isDungeon && target.isPlayer && attackResult.didHit && attackResult.damageDone > 0) {\r\n const log = this.generateCombatLog(source, \"autoAttack\", target, attackResult);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n let mayhem = source.combatDetails.combatStats.mayhem > Math.random();\r\n\r\n if (attackResult.didHit && source.combatDetails.combatStats.curse > 0) {\r\n const curseExpireTime = 15000000000;\r\n let currentCurseEvent = this.eventQueue.getMatching((event) => event.type == CurseExpirationEvent.type && event.source == target);\r\n let currentCurseAmount = 0;\r\n if (currentCurseEvent) currentCurseAmount = currentCurseEvent.curseAmount;\r\n this.eventQueue.clearMatching((event) => event.type == CurseExpirationEvent.type && event.source == target);\r\n\r\n let curseExpirationEvent = new CurseExpirationEvent(this.simulationTime + curseExpireTime, currentCurseAmount, target);\r\n const curseBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/curse\",\r\n \"typeHrid\": \"/buff_types/damage_taken\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": source.combatDetails.combatStats.curse * curseExpirationEvent.curseAmount,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": curseExpireTime\r\n };\r\n target.addBuff(curseBuff);\r\n this.eventQueue.addEvent(curseExpirationEvent);\r\n }\r\n\r\n if (source.combatDetails.combatStats.fury > 0) {\r\n let currentFuryEvent = this.eventQueue.getMatching((event) => event.type == FuryExpirationEvent.type && event.source == source);\r\n this.eventQueue.clearMatching((event) => event.type == FuryExpirationEvent.type && event.source == source);\r\n\r\n const furyExpireTime = 15000000000;\r\n const maxFuryStack = 5;\r\n\r\n let furyAmount = 0;\r\n if (currentFuryEvent) furyAmount = currentFuryEvent.furyAmount;\r\n\r\n if (attackResult.didHit) {\r\n furyAmount = Math.min(furyAmount + 1, maxFuryStack);\r\n } else {\r\n furyAmount = Math.floor(furyAmount / 2);\r\n }\r\n\r\n const furyAccuracyBuf = {\r\n \"uniqueHrid\": \"/buff_uniques/fury_accuracy\",\r\n \"typeHrid\": \"/buff_types/fury_accuracy\",\r\n \"ratioBoost\": furyAmount * source.combatDetails.combatStats.fury,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": furyExpireTime\r\n };\r\n const furyDamageBuf = {\r\n \"uniqueHrid\": \"/buff_uniques/fury_damage\",\r\n \"typeHrid\": \"/buff_types/fury_damage\",\r\n \"ratioBoost\": furyAmount * source.combatDetails.combatStats.fury,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": furyExpireTime\r\n };\r\n\r\n if (furyAmount > 0) {\r\n let furyExpirationEvent = new FuryExpirationEvent(this.simulationTime + furyExpireTime, furyAmount, source);\r\n this.eventQueue.addEvent(furyExpirationEvent);\r\n\r\n source.addBuff(furyAccuracyBuf, this.simulationTime);\r\n source.addBuff(furyDamageBuf, this.simulationTime);\r\n }\r\n else {\r\n source.removeBuff(furyAccuracyBuf);\r\n source.removeBuff(furyDamageBuf);\r\n }\r\n }\r\n\r\n if (target.combatDetails.combatStats.weaken > 0) {\r\n const weakenExpireTime = 15000000000;\r\n let currentWeakenEvent = this.eventQueue.getMatching((event) => event.type == WeakenExpirationEvent.type && event.source == source);\r\n let weakenAmount = 0;\r\n if (currentWeakenEvent)\r\n weakenAmount = currentWeakenEvent.weakenAmount;\r\n this.eventQueue.clearMatching((event) => event.type == WeakenExpirationEvent.type && event.source == source);\r\n let weakenExpirationEvent = new WeakenExpirationEvent(this.simulationTime + 15000000000, weakenAmount, source);\r\n const weakenBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/weaken\",\r\n \"typeHrid\": \"/buff_types/damage\",\r\n \"ratioBoost\": -1 * target.combatDetails.combatStats.weaken * weakenExpirationEvent.weakenAmount,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": weakenExpireTime\r\n };\r\n source.addBuff(weakenBuff);\r\n this.eventQueue.addEvent(weakenExpirationEvent);\r\n }\r\n\r\n if (!mayhem || (mayhem && attackResult.didHit) || (mayhem && i == (aliveTargets.length - 1))) {\r\n let attackType = \"autoAttack\";\r\n if (parryTarget) attackType = \"parry\";\r\n this.simResult.addAttack(\r\n source,\r\n target,\r\n \"autoAttack\",\r\n attackResult.didHit ? attackResult.damageDone : \"miss\"\r\n );\r\n }\r\n\r\n if (attackResult.lifeStealHeal > 0) {\r\n this.simResult.addHitpointsGained(source, \"lifesteal\", attackResult.lifeStealHeal);\r\n }\r\n\r\n if (attackResult.manaLeechMana > 0) {\r\n this.simResult.addManapointsGained(source, \"manaLeech\", attackResult.manaLeechMana);\r\n }\r\n\r\n if (attackResult.thornDamageDone > 0) {\r\n this.simResult.addAttack(target, source, attackResult.thornType, attackResult.thornDamageDone);\r\n }\r\n if (this.zone.isDungeon && attackResult.thornDamageDone > 0 && source.isPlayer) {\r\n const log = this.buildCombatLog(target, attackResult.thornType, source, attackResult.thornDamageDone);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (target.combatDetails.combatStats.retaliation > 0) {\r\n this.simResult.addAttack(target, source, \"retaliation\", attackResult.retaliationDamageDone > 0?attackResult.retaliationDamageDone:\"miss\");\r\n }\r\n if (this.zone.isDungeon && attackResult.retaliationDamageDone > 0 && source.isPlayer) {\r\n const log = this.buildCombatLog(target, \"retaliation\", source, attackResult.retaliationDamageDone);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (target.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(target);\r\n this.simResult.addDeath(target);\r\n if (!target.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(target.hrid, false, this.simulationTime);\r\n }\r\n // console.log(target.hrid, \"died\");\r\n }\r\n\r\n // Could die from reflect damage\r\n if (source.combatDetails.currentHitpoints == 0 && \r\n (attackResult.thornDamageDone != 0 || attackResult.retaliationDamageDone != 0)\r\n ) {\r\n this.eventQueue.clearEventsForUnit(source);\r\n this.simResult.addDeath(source);\r\n if (!source.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(source.hrid, false, this.simulationTime);\r\n }\r\n break;\r\n }\r\n\r\n if (mayhem && !attackResult.didHit) {\r\n continue;\r\n }\r\n\r\n if (!attackResult.didHit || parryTarget || source.combatDetails.combatStats.pierce <= Math.random()) {\r\n break;\r\n }\r\n }\r\n\r\n if (!this.checkEncounterEnd()) {\r\n // console.log(\"!EncounterEnd \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(event.source);\r\n }\r\n }\r\n\r\n checkEncounterEnd() {\r\n if (this.enemies) {\r\n let deadEnemies = this.enemies.filter((enemy) => enemy.combatDetails.currentHitpoints <= 0 && enemy.experienceRate == 0);\r\n if (deadEnemies.length > 0) {\r\n deadEnemies.forEach(enemy => {\r\n let aliveDuration = this.simulationTime - this.enrageBeginTime;\r\n if (aliveDuration > enemy.enrageTime) {\r\n aliveDuration = enemy.enrageTime;\r\n }\r\n enemy.experienceRate = 1.0 + aliveDuration / enemy.enrageTime;\r\n // console.log(enemy.hrid, \"alive duration\", aliveDuration, \"exp rate\", enemy.experienceRate);\r\n })\r\n }\r\n }\r\n\r\n let encounterEnded = false;\r\n\r\n if (this.enemies && !this.enemies.some((enemy) => enemy.combatDetails.currentHitpoints > 0)) {\r\n this.eventQueue.clearEventsOfType(AutoAttackEvent.type);\r\n // this.eventQueue.clearEventsOfType(AbilityCastEndEvent.type);\r\n let enemyRespawnEvent = new EnemyRespawnEvent(this.simulationTime + ENEMY_RESPAWN_INTERVAL);\r\n this.eventQueue.addEvent(enemyRespawnEvent);\r\n\r\n //calc exp before clear\r\n if (this.enemies.some(enemy => enemy.experienceRate <= 0)) {\r\n console.log(\"WARN: Some enemies have no experience rate\");\r\n }\r\n\r\n let totalExp = this.enemies.map(enemy => enemy.experience * enemy.experienceRate).reduce((a, b) => a + b, 0);\r\n this.players.forEach(player => {\r\n this.simResult.addExperienceGain(player, totalExp / this.players.length);\r\n });\r\n\r\n this.enemies = null;\r\n\r\n if (this.zone.isDungeon) {\r\n this.simResult.updateTimeSpentAlive(\"#\" + (this.zone.encountersKilled - 1).toString(), false, this.simulationTime);\r\n }\r\n this.simResult.addEncounterEnd();\r\n // console.log(\"All enemies died\");\r\n\r\n encounterEnded = true;\r\n // console.log(\"encounter end \" + (this.simulationTime / 1000000000))\r\n }\r\n\r\n this.players.forEach(player => {\r\n if ((player.combatDetails.currentHitpoints <= 0) && !this.eventQueue.containsEventOfTypeAndHrid(PlayerRespawnEvent.type, player.hrid)) {\r\n if (!this.zone.isDungeon) {\r\n let playerRespawnEvent = new PlayerRespawnEvent(this.simulationTime + PLAYER_RESPAWN_INTERVAL, player.hrid);\r\n this.eventQueue.addEvent(playerRespawnEvent);\r\n }\r\n this.simResult.addRanOutOfManaCount(player, false, this.simulationTime);\r\n // console.log(player.hrid + \" died at \" + (this.simulationTime / 1000000000) + 'in wave #' + (this.zone.encountersKilled - 1) + ' with ememies: ' + this.enemies?.map(enemy => (enemy.hrid+\"(\"+(enemy.combatDetails.currentHitpoints*100/enemy.combatDetails.maxHitpoints).toFixed(2)+\"%)\")).join(\", \"));\r\n }\r\n });\r\n\r\n if (\r\n !this.players.some((player) => player.combatDetails.currentHitpoints > 0)\r\n ) {\r\n if (this.zone.isDungeon) {\r\n console.log(\"All Players died at wave #\" + (this.zone.encountersKilled - 1) + \" with ememies: \" + this.enemies.map(enemy => (enemy.hrid+\"(\"+(enemy.combatDetails.currentHitpoints*100/enemy.combatDetails.maxHitpoints).toFixed(2)+\"%)\")).join(\", \"));\r\n\r\n this.saveWipeLogsToSimResult(this.zone.encountersKilled - 1);\r\n // console.log(this.simResult)\r\n this.wipeLogs.index = 0;\r\n this.wipeLogs.count = 0;\r\n\r\n this.eventQueue.clear();\r\n this.enemies = null;\r\n\r\n let combatStartEvent = new CombatStartEvent(this.simulationTime + RESTART_INTERVAL);\r\n this.eventQueue.addEvent(combatStartEvent);\r\n } else {\r\n this.eventQueue.clearEventsOfType(AutoAttackEvent.type);\r\n this.eventQueue.clearEventsOfType(AbilityCastEndEvent.type);\r\n }\r\n // console.log(\"All Players died\");\r\n encounterEnded = true;\r\n this.allPlayersDead = true;\r\n }\r\n\r\n return encounterEnded;\r\n }\r\n\r\n addNextAttackEvent(source) {\r\n if (this.eventQueue.getMatching((event) => (event.type == AbilityCastEndEvent.type || event.type == AutoAttackEvent.type)&& event.source == source)) {\r\n return;\r\n }\r\n\r\n let target;\r\n let friendlies;\r\n let enemies;\r\n if (source.isPlayer) {\r\n target = CombatUtilities.getTarget(this.enemies);\r\n friendlies = this.players;\r\n enemies = this.enemies;\r\n } else {\r\n target = CombatUtilities.getTarget(this.players);\r\n friendlies = this.enemies;\r\n enemies = this.players;\r\n }\r\n\r\n let usedAbility = false;\r\n let skipNextAbility = false; \r\n\r\n source.abilities\r\n .filter((ability) => ability != null)\r\n .forEach((ability) => {\r\n if (!usedAbility && !skipNextAbility && ability.shouldTrigger(this.simulationTime, source, target, friendlies, enemies)) {\r\n if (!this.canUseAbility(source, ability, true)) {\r\n skipNextAbility = true;\r\n }\r\n\r\n if (!skipNextAbility) {\r\n let castDuration = ability.castDuration;\r\n castDuration /= (1 + source.combatDetails.combatStats.castSpeed)\r\n let abilityCastEndEvent = new AbilityCastEndEvent(this.simulationTime + castDuration, source, ability);\r\n this.eventQueue.addEvent(abilityCastEndEvent);\r\n /*-if (source.isPlayer) {\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n // console.log((this.simulationTime / 1000000000) + \" Casting \" + ability.hrid + \" Cast time \" + (castDuration / 1e9) + \" Off CD at \" + ((this.simulationTime + cooldownDuration + castDuration) / 1e9) + \" CD \" + ((cooldownDuration) / 1e9));\r\n }*/\r\n usedAbility = true;\r\n }\r\n }\r\n });\r\n\r\n if (usedAbility) {\r\n source.isOutOfMana = false;\r\n return;\r\n }\r\n\r\n if (!enemies) {\r\n return;\r\n }\r\n\r\n if (!source.isBlinded) {\r\n let autoAttackEvent = new AutoAttackEvent(\r\n this.simulationTime + source.combatDetails.combatStats.attackInterval,\r\n source\r\n );\r\n /*-if (source.isPlayer) {\r\n // console.log(\"next attack \" + ((this.simulationTime + source.combatDetails.combatStats.attackInterval) / 1e9))\r\n }*/\r\n this.eventQueue.addEvent(autoAttackEvent);\r\n } else {\r\n source.isOutOfMana = true;\r\n }\r\n }\r\n\r\n processConsumableTickEvent(event) {\r\n if (event.consumable.hitpointRestore > 0) {\r\n let tickValue = CombatUtilities.calculateTickValue(\r\n event.consumable.hitpointRestore,\r\n event.totalTicks,\r\n event.currentTick\r\n );\r\n let hitpointsAdded = event.source.addHitpoints(tickValue);\r\n this.simResult.addHitpointsGained(event.source, event.consumable.hrid, hitpointsAdded);\r\n // console.log(\"Added hitpoints:\", hitpointsAdded);\r\n }\r\n\r\n if (event.consumable.manapointRestore > 0) {\r\n let tickValue = CombatUtilities.calculateTickValue(\r\n event.consumable.manapointRestore,\r\n event.totalTicks,\r\n event.currentTick\r\n );\r\n let manapointsAdded = event.source.addManapoints(tickValue);\r\n this.simResult.addManapointsGained(event.source, event.consumable.hrid, manapointsAdded);\r\n // console.log(\"Added manapoints:\", manapointsAdded);\r\n\r\n // when oom check ability trigger\r\n if (event.source.isOutOfMana) {\r\n let awaitCooldownEvent = new AwaitCooldownEvent(\r\n this.simulationTime,\r\n event.source\r\n );\r\n this.eventQueue.addEvent(awaitCooldownEvent);\r\n }\r\n }\r\n\r\n if (event.currentTick < event.totalTicks) {\r\n let consumableTickEvent = new ConsumableTickEvent(\r\n this.simulationTime + HOT_TICK_INTERVAL,\r\n event.source,\r\n event.consumable,\r\n event.totalTicks,\r\n event.currentTick + 1\r\n );\r\n this.eventQueue.addEvent(consumableTickEvent);\r\n }\r\n }\r\n\r\n processDamageOverTimeTickEvent(event) {\r\n let tickDamage = CombatUtilities.calculateTickValue(event.damage, event.totalTicks, event.currentTick);\r\n let damage = Math.min(tickDamage, event.target.combatDetails.currentHitpoints);\r\n\r\n event.target.combatDetails.currentHitpoints -= damage;\r\n this.simResult.addAttack(event.sourceRef, event.target, \"damageOverTime\", damage);\r\n\r\n const log = this.buildCombatLog(\"\", \"damageOverTime\", event.target, damage);\r\n this.addToWipeLogs(log);\r\n\r\n // console.log(event.target.hrid, \"bleed for\", damage);\r\n\r\n if (event.currentTick < event.totalTicks) {\r\n let damageOverTimeTickEvent = new DamageOverTimeEvent(\r\n this.simulationTime + DOT_TICK_INTERVAL,\r\n event.sourceRef,\r\n event.target,\r\n event.damage,\r\n event.totalTicks,\r\n event.currentTick + 1,\r\n event.combatStyleHrid\r\n );\r\n this.eventQueue.addEvent(damageOverTimeTickEvent);\r\n }\r\n\r\n if (event.target.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(event.target);\r\n this.simResult.addDeath(event.target);\r\n if (!event.target.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(event.target.hrid, false, this.simulationTime);\r\n }\r\n }\r\n\r\n this.checkEncounterEnd();\r\n }\r\n\r\n processRegenTickEvent(event) {\r\n let units = [...this.players];\r\n if (this.enemies) {\r\n units.push(...this.enemies);\r\n }\r\n\r\n for (const unit of units) {\r\n if (unit.combatDetails.currentHitpoints <= 0) {\r\n continue;\r\n }\r\n\r\n let hitpointRegen = Math.floor(unit.combatDetails.maxHitpoints * unit.combatDetails.combatStats.hpRegenPer10);\r\n let hitpointsAdded = unit.addHitpoints(hitpointRegen);\r\n this.simResult.addHitpointsGained(unit, \"regen\", hitpointsAdded);\r\n // console.log(\"Added hitpoints:\", hitpointsAdded);\r\n\r\n let manapointRegen = Math.floor(unit.combatDetails.maxManapoints * unit.combatDetails.combatStats.mpRegenPer10);\r\n let manapointsAdded = unit.addManapoints(manapointRegen);\r\n this.simResult.addManapointsGained(unit, \"regen\", manapointsAdded);\r\n // console.log(\"Added manapoints:\", manapointsAdded);\r\n\r\n // when oom check ability trigger\r\n if (unit.isOutOfMana) {\r\n let awaitCooldownEvent = new AwaitCooldownEvent(\r\n this.simulationTime,\r\n unit\r\n );\r\n this.eventQueue.addEvent(awaitCooldownEvent);\r\n }\r\n }\r\n\r\n let regenTickEvent = new RegenTickEvent(this.simulationTime + REGEN_TICK_INTERVAL);\r\n this.eventQueue.addEvent(regenTickEvent);\r\n }\r\n\r\n processCheckBuffExpirationEvent(event) {\r\n event.source.removeExpiredBuffs(this.simulationTime);\r\n }\r\n\r\n processStunExpirationEvent(event) {\r\n event.source.isStunned = false;\r\n // console.log(\"Stun \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(event.source);\r\n }\r\n\r\n processBlindExpirationEvent(event) {\r\n event.source.isBlinded = false;\r\n this.addNextAttackEvent(event.source);\r\n }\r\n\r\n processSilenceExpirationEvent(event) {\r\n event.source.isSilenced = false;\r\n }\r\n\r\n processCurseExpirationEvent(event) {\r\n event.source.removeExpiredBuffs(this.simulationTime);\r\n }\r\n\r\n processWeakenExpirationEvent(event) {\r\n event.source.removeExpiredBuffs(this.simulationTime);\r\n }\r\n\r\n processFuryExpirationEvent(event) {\r\n event.source.removeExpiredBuffs(this.simulationTime);\r\n console.log(\"Fury Timeout\");\r\n }\r\n\r\n processEnrageTickEvent(event) {\r\n if (!this.enemies) return;\r\n const maxEnrageStack = 10;\r\n this.enemies.filter((enemy) => enemy.combatDetails.currentHitpoints > 0).forEach((enemy) => {\r\n let nowStack = Math.min(maxEnrageStack, Math.floor(event.encounterTime / enemy.enrageTime));\r\n\r\n if (nowStack <= 0) {\r\n return;\r\n }\r\n\r\n console.log(enemy.hrid, nowStack, \" stack Enrage at \", (event.encounterTime / ONE_SECOND));\r\n\r\n const enrageDamageBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/enrage_damage\",\r\n \"typeHrid\": \"/buff_types/damage\",\r\n \"ratioBoost\": nowStack * 0.1,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": ENRAGE_TICK_INTERVAL\r\n };\r\n const enrageAccuracyBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/enrage_accuracy\",\r\n \"typeHrid\": \"/buff_types/accuracy\",\r\n \"ratioBoost\": nowStack * 0.1,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": ENRAGE_TICK_INTERVAL\r\n };\r\n enemy.addBuff(enrageDamageBuff);\r\n enemy.addBuff(enrageAccuracyBuff);\r\n \r\n this.simResult.maxEnrageStack = Math.max(this.simResult.maxEnrageStack, nowStack);\r\n });\r\n\r\n let enrageTickEvent = new EnrageTickEvent(this.simulationTime + ENRAGE_TICK_INTERVAL, event.encounterTime + ENRAGE_TICK_INTERVAL);\r\n this.eventQueue.addEvent(enrageTickEvent);\r\n }\r\n\r\n checkTriggers() {\r\n let triggeredSomething;\r\n\r\n do {\r\n triggeredSomething = false;\r\n\r\n this.players\r\n .filter((player) => player.combatDetails.currentHitpoints > 0)\r\n .forEach((player) => {\r\n if (this.checkTriggersForUnit(player, this.players, this.enemies)) {\r\n triggeredSomething = true;\r\n }\r\n });\r\n\r\n if (this.enemies) {\r\n this.enemies\r\n .filter((enemy) => enemy.combatDetails.currentHitpoints > 0)\r\n .forEach((enemy) => {\r\n if (this.checkTriggersForUnit(enemy, this.enemies, this.players)) {\r\n triggeredSomething = true;\r\n }\r\n });\r\n }\r\n } while (triggeredSomething);\r\n }\r\n\r\n checkTriggersForUnit(unit, friendlies, enemies) {\r\n if (unit.combatDetails.currentHitpoints <= 0) {\r\n throw new Error(\"Checking triggers for a dead unit\");\r\n }\r\n\r\n let triggeredSomething = false;\r\n let target = CombatUtilities.getTarget(enemies);\r\n\r\n for (const food of unit.food) {\r\n if (food && food.shouldTrigger(this.simulationTime, unit, target, friendlies, enemies)) {\r\n let result = this.tryUseConsumable(unit, food);\r\n if (result) {\r\n triggeredSomething = true;\r\n }\r\n }\r\n }\r\n\r\n for (const drink of unit.drinks) {\r\n if (drink && drink.shouldTrigger(this.simulationTime, unit, target, friendlies, enemies)) {\r\n let result = this.tryUseConsumable(unit, drink);\r\n if (result) {\r\n triggeredSomething = true;\r\n }\r\n }\r\n }\r\n\r\n return triggeredSomething;\r\n }\r\n\r\n tryUseConsumable(source, consumable) {\r\n // console.log(\"Consuming:\", consumable);\r\n\r\n if (source.combatDetails.currentHitpoints <= 0) {\r\n return false;\r\n }\r\n\r\n consumable.lastUsed = this.simulationTime;\r\n let consumeCooldown = consumable.cooldownDuration;\r\n if (source.combatDetails.combatStats.drinkConcentration > 0 && consumable.catagoryHrid.includes(\"drink\")) {\r\n consumeCooldown = consumeCooldown / (1 + source.combatDetails.combatStats.drinkConcentration);\r\n } else if (source.combatDetails.combatStats.foodHaste > 0 && consumable.catagoryHrid.includes(\"food\")) {\r\n consumeCooldown = consumeCooldown / (1 + source.combatDetails.combatStats.foodHaste);\r\n }\r\n let cooldownReadyEvent = new CooldownReadyEvent(this.simulationTime + consumeCooldown);\r\n this.eventQueue.addEvent(cooldownReadyEvent);\r\n\r\n this.simResult.addConsumableUse(source, consumable);\r\n\r\n if (consumable.recoveryDuration == 0) {\r\n if (consumable.hitpointRestore > 0) {\r\n let hitpointsAdded = source.addHitpoints(consumable.hitpointRestore);\r\n this.simResult.addHitpointsGained(source, consumable.hrid, hitpointsAdded);\r\n // console.log(\"Added hitpoints:\", hitpointsAdded);\r\n }\r\n\r\n if (consumable.manapointRestore > 0) {\r\n let manapointsAdded = source.addManapoints(consumable.manapointRestore);\r\n this.simResult.addManapointsGained(source, consumable.hrid, manapointsAdded);\r\n // console.log(\"Added manapoints:\", manapointsAdded);\r\n\r\n // when oom check ability trigger\r\n if (source.isOutOfMana) {\r\n let awaitCooldownEvent = new AwaitCooldownEvent(\r\n this.simulationTime,\r\n source\r\n );\r\n this.eventQueue.addEvent(awaitCooldownEvent);\r\n }\r\n }\r\n } else {\r\n let consumableTickEvent = new ConsumableTickEvent(\r\n this.simulationTime + HOT_TICK_INTERVAL,\r\n source,\r\n consumable,\r\n consumable.recoveryDuration / HOT_TICK_INTERVAL,\r\n 1\r\n );\r\n this.eventQueue.addEvent(consumableTickEvent);\r\n }\r\n\r\n for (const buff of consumable.buffs) {\r\n let currentBuff = structuredClone(buff);\r\n if (source.combatDetails.combatStats.drinkConcentration > 0 && consumable.catagoryHrid.includes(\"drink\")) {\r\n currentBuff.ratioBoost *= (1 + source.combatDetails.combatStats.drinkConcentration);\r\n currentBuff.flatBoost *= (1 + source.combatDetails.combatStats.drinkConcentration);\r\n currentBuff.duration = currentBuff.duration / (1 + source.combatDetails.combatStats.drinkConcentration);\r\n }\r\n source.addBuff(currentBuff, this.simulationTime);\r\n // console.log(\"Added buff:\", currentBuff);\r\n let checkBuffExpirationEvent = new CheckBuffExpirationEvent(this.simulationTime + currentBuff.duration, source);\r\n this.eventQueue.addEvent(checkBuffExpirationEvent);\r\n }\r\n\r\n return true;\r\n }\r\n\r\n canUseAbility(source, ability, oomCheck) {\r\n if (source.combatDetails.currentHitpoints <= 0) {\r\n return false;\r\n }\r\n\r\n if (source.combatDetails.currentManapoints < ability.manaCost) {\r\n if (source.isPlayer && oomCheck) {\r\n // if (this.simResult.playerRanOutOfMana[source.hrid] == false) {\r\n // console.log(source.hrid + \" ran out of mana\" + ' at wave #' + (this.zone.encountersKilled - 1) + ' at time ' + this.simulationTime / 1000000000 + 's');\r\n // }\r\n this.simResult.addRanOutOfManaCount(source, true, this.simulationTime);\r\n }\r\n return false;\r\n }\r\n if (source.isPlayer && oomCheck) {\r\n this.simResult.addRanOutOfManaCount(source, false, this.simulationTime);\r\n }\r\n return true;\r\n }\r\n\r\n tryUseAbility(source, ability) {\r\n\r\n if (!this.canUseAbility(source, ability, true)) {\r\n // console.log(\"Falseeeeeee\");\r\n return false;\r\n }\r\n\r\n // console.log(\"Casting:\", ability);\r\n\r\n if (source.isPlayer) {\r\n if (source.abilityManaCosts.has(ability.hrid)) {\r\n source.abilityManaCosts.set(ability.hrid, source.abilityManaCosts.get(ability.hrid) + ability.manaCost);\r\n } else {\r\n source.abilityManaCosts.set(ability.hrid, ability.manaCost);\r\n }\r\n }\r\n\r\n source.combatDetails.currentManapoints -= ability.manaCost;\r\n\r\n ability.lastUsed = this.simulationTime;\r\n\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n\r\n /*-if (source.isPlayer) {\r\n let castDuration = ability.castDuration;\r\n castDuration /= (1 + source.combatDetails.combatStats.castSpeed)\r\n // console.log((this.simulationTime / 1000000000) + \" Used ability \" + ability.hrid + \" Cast time \" + (castDuration / 1e9));\r\n }*/\r\n this.addNextAttackEvent(source);\r\n\r\n let todoAbilities = [ability];\r\n\r\n if (source.combatDetails.combatStats.blaze > 0 && Math.random() < source.combatDetails.combatStats.blaze) {\r\n todoAbilities.push(new Ability(\"blaze\"));\r\n }\r\n\r\n if (source.combatDetails.combatStats.bloom > 0 && Math.random() < source.combatDetails.combatStats.bloom) {\r\n todoAbilities.push(new Ability(\"bloom\"));\r\n }\r\n\r\n for (const todoAbility of todoAbilities) {\r\n for (const abilityEffect of todoAbility.abilityEffects) {\r\n switch (abilityEffect.effectType) {\r\n case \"/ability_effect_types/buff\":\r\n this.processAbilityBuffEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/damage\":\r\n this.processAbilityDamageEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/heal\":\r\n this.processAbilityHealEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/spend_hp\":\r\n this.processAbilitySpendHpEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/revive\":\r\n this.processAbilityReviveEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/promote\":\r\n this.eventQueue.clearEventsForUnit(source);\r\n source = this.processAbilityPromoteEffect(source, todoAbility, abilityEffect);\r\n this.addNextAttackEvent(source);\r\n break;\r\n default:\r\n throw new Error(\"Unsupported effect type for ability: \" + todoAbility.hrid + \" effectType: \" + abilityEffect.effectType);\r\n }\r\n }\r\n }\r\n\r\n if (source.combatDetails.combatStats.ripple > 0 && Math.random() < source.combatDetails.combatStats.ripple) {\r\n let manapointsAdded = source.addManapoints(10);\r\n this.simResult.addManapointsGained(source, \"ripple\", manapointsAdded);\r\n for (const ability of source.abilities) {\r\n if (ability && ability.lastUsed) {\r\n const remainingCooldown = ability.lastUsed + ability.cooldownDuration - this.simulationTime;\r\n if (remainingCooldown > 0) {\r\n ability.lastUsed = Math.max(ability.lastUsed - ONE_SECOND * 2, this.simulationTime - ability.cooldownDuration);\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Could die from reflect damage\r\n if (source.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(source);\r\n this.simResult.addDeath(source);\r\n if (!source.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(source.hrid, false, this.simulationTime);\r\n }\r\n }\r\n\r\n this.checkEncounterEnd();\r\n\r\n return true;\r\n }\r\n\r\n processAbilityBuffEffect(source, ability, abilityEffect) {\r\n if (abilityEffect.targetType == \"allAllies\") {\r\n let targets = source.isPlayer ? this.players : this.enemies;\r\n for (const target of targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0)) {\r\n for (const buff of abilityEffect.buffs) {\r\n if (ability.isSpecialAbility && buff.multiplierForSkillHrid && buff.multiplierPerSkillLevel > 0) {\r\n let multiplier = 1.0 + source.combatDetails[buff.multiplierForSkillHrid.split('/')[2] + 'Level'] * buff.multiplierPerSkillLevel;\r\n let currentBuff = structuredClone(buff);\r\n currentBuff.flatBoost *= multiplier;\r\n target.addBuff(currentBuff, this.simulationTime);\r\n } else {\r\n target.addBuff(buff, this.simulationTime);\r\n }\r\n let checkBuffExpirationEvent = new CheckBuffExpirationEvent(this.simulationTime + buff.duration, target);\r\n this.eventQueue.addEvent(checkBuffExpirationEvent);\r\n }\r\n }\r\n return;\r\n }\r\n\r\n if (abilityEffect.targetType != \"self\") {\r\n throw new Error(\"Unsupported target type for buff ability effect: \" + ability.hrid);\r\n }\r\n\r\n for (const buff of abilityEffect.buffs) {\r\n source.addBuff(buff, this.simulationTime);\r\n // console.log(\"Added buff:\", abilityEffect.buff);\r\n let checkBuffExpirationEvent = new CheckBuffExpirationEvent(this.simulationTime + buff.duration, source);\r\n this.eventQueue.addEvent(checkBuffExpirationEvent);\r\n }\r\n }\r\n\r\n processAbilityDamageEffect(source, ability, abilityEffect) {\r\n let targets;\r\n switch (abilityEffect.targetType) {\r\n case \"enemy\":\r\n case \"allEnemies\":\r\n targets = source.isPlayer ? this.enemies : this.players;\r\n break;\r\n default:\r\n throw new Error(\"Unsupported target type for damage ability effect: \" + ability.hrid);\r\n }\r\n\r\n if (!targets) {\r\n return;\r\n }\r\n\r\n let avoidTarget = [];\r\n\r\n let isSkipParry = false;\r\n\r\n for (let target of targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0)) {\r\n let parryTarget = undefined;\r\n if (!isSkipParry) {\r\n parryTarget = this.checkParry(targets);\r\n isSkipParry = true; // parry check only once on first target\r\n }\r\n \r\n if (parryTarget) {\r\n let tempTarget = source;\r\n let tempSource = parryTarget;\r\n\r\n let attackResult = CombatUtilities.processAttack(tempSource, tempTarget);\r\n\r\n this.simResult.addAttack(\r\n tempSource,\r\n tempTarget,\r\n \"parry\",\r\n attackResult.didHit ? attackResult.damageDone : \"miss\"\r\n );\r\n\r\n if (attackResult.lifeStealHeal > 0) {\r\n this.simResult.addHitpointsGained(tempSource, \"lifesteal\", attackResult.lifeStealHeal);\r\n }\r\n\r\n if (attackResult.manaLeechMana > 0) {\r\n this.simResult.addManapointsGained(tempSource, \"manaLeech\", attackResult.manaLeechMana);\r\n }\r\n\r\n if (attackResult.thornDamageDone > 0) {\r\n this.simResult.addAttack(tempTarget, tempSource, attackResult.thornType, attackResult.thornDamageDone);\r\n }\r\n if (tempTarget.combatDetails.combatStats.retaliation > 0) {\r\n this.simResult.addAttack(tempTarget, tempSource, \"retaliation\", attackResult.retaliationDamageDone > 0 ? attackResult.retaliationDamageDone : \"miss\");\r\n }\r\n\r\n if (tempTarget.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(tempTarget);\r\n this.simResult.addDeath(tempTarget);\r\n if (!tempTarget.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(tempTarget.hrid, false, this.simulationTime);\r\n }\r\n // console.log(tempTarget.hrid, \"died\");\r\n }\r\n\r\n // Could die from reflect damage\r\n if (tempSource.combatDetails.currentHitpoints == 0 && \r\n (attackResult.thornDamageDone != 0 || attackResult.retaliationDamageDone != 0)\r\n ) {\r\n this.eventQueue.clearEventsForUnit(tempSource);\r\n this.simResult.addDeath(tempSource);\r\n if (!tempSource.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(tempSource.hrid, false, this.simulationTime);\r\n }\r\n }\r\n } else {\r\n targets = targets.filter((unit) => unit && !avoidTarget.includes(unit.hrid) && unit.combatDetails.currentHitpoints > 0);\r\n if (!source.isPlayer && targets.length > 0 && abilityEffect.targetType == \"enemy\") {\r\n let cumulativeThreat = 0;\r\n let cumulativeRanges = [];\r\n targets.forEach(player => {\r\n let playerThreat = player.combatDetails.combatStats.threat;\r\n cumulativeThreat += playerThreat;\r\n cumulativeRanges.push({\r\n player: player,\r\n rangeStart: cumulativeThreat - playerThreat,\r\n rangeEnd: cumulativeThreat\r\n });\r\n });\r\n let randomValueHit = Math.random() * cumulativeThreat;\r\n target = cumulativeRanges.find(range => randomValueHit >= range.rangeStart && randomValueHit < range.rangeEnd).player;\r\n avoidTarget.push(target.hrid);\r\n }\r\n if (targets.length <= 0) {\r\n break;\r\n }\r\n\r\n let attackResult = CombatUtilities.processAttack(source, target, abilityEffect);\r\n\r\n if (this.zone.isDungeon && target.isPlayer && attackResult.didHit && attackResult.damageDone > 0) {\r\n const log = this.generateCombatLog(source, ability.hrid, target, attackResult);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (attackResult.hpDrain > 0) {\r\n this.simResult.addHitpointsGained(source, ability.hrid, attackResult.hpDrain);\r\n }\r\n\r\n if (attackResult.didHit && abilityEffect.buffs) {\r\n for (const buff of abilityEffect.buffs) {\r\n target.addBuff(buff, this.simulationTime);\r\n let checkBuffExpirationEvent = new CheckBuffExpirationEvent(\r\n this.simulationTime + buff.duration,\r\n target\r\n );\r\n this.eventQueue.addEvent(checkBuffExpirationEvent);\r\n }\r\n }\r\n\r\n if (abilityEffect.damageOverTimeRatio > 0 && attackResult.damageDone > 0) {\r\n let damageOverTimeEvent = new DamageOverTimeEvent(\r\n this.simulationTime + DOT_TICK_INTERVAL,\r\n source,\r\n target,\r\n attackResult.damageDone * abilityEffect.damageOverTimeRatio,\r\n abilityEffect.damageOverTimeDuration / DOT_TICK_INTERVAL,\r\n 1, abilityEffect.combatStyleHrid\r\n );\r\n this.eventQueue.addEvent(damageOverTimeEvent);\r\n }\r\n\r\n if (attackResult.didHit && abilityEffect.stunChance > 0 && Math.random() < (abilityEffect.stunChance * 100 / (100 + target.combatDetails.combatStats.tenacity))) {\r\n target.isStunned = true;\r\n target.stunExpireTime = this.simulationTime + abilityEffect.stunDuration;\r\n this.eventQueue.clearMatching((event) => (event.type == AutoAttackEvent.type || event.type == AbilityCastEndEvent.type || event.type == StunExpirationEvent.type) && event.source == target);\r\n let stunExpirationEvent = new StunExpirationEvent(target.stunExpireTime, target);\r\n this.eventQueue.addEvent(stunExpirationEvent);\r\n }\r\n\r\n if (attackResult.didHit && abilityEffect.blindChance > 0 && Math.random() < (abilityEffect.blindChance * 100 / (100 + target.combatDetails.combatStats.tenacity))) {\r\n target.isBlinded = true;\r\n target.blindExpireTime = this.simulationTime + abilityEffect.blindDuration;\r\n this.eventQueue.clearMatching((event) => event.type == BlindExpirationEvent.type && event.source == target)\r\n if (this.eventQueue.clearMatching((event) => event.type == AutoAttackEvent.type && event.source == target)) {\r\n // console.log(\"Blind \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(target);\r\n }\r\n let blindExpirationEvent = new BlindExpirationEvent(target.blindExpireTime, target);\r\n this.eventQueue.addEvent(blindExpirationEvent);\r\n }\r\n\r\n if (attackResult.didHit && abilityEffect.silenceChance > 0 && Math.random() < (abilityEffect.silenceChance * 100 / (100 + target.combatDetails.combatStats.tenacity))) {\r\n target.isSilenced = true;\r\n target.silenceExpireTime = this.simulationTime + abilityEffect.silenceDuration;\r\n this.eventQueue.clearMatching((event) => event.type == SilenceExpirationEvent.type && event.source == target)\r\n if (this.eventQueue.clearMatching((event) => event.type == AbilityCastEndEvent.type && event.source == target)) {\r\n // console.log(\"Silence \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(target);\r\n }\r\n let silenceExpirationEvent = new SilenceExpirationEvent(target.silenceExpireTime, target);\r\n this.eventQueue.addEvent(silenceExpirationEvent);\r\n }\r\n\r\n if (attackResult.didHit && source.combatDetails.combatStats.curse > 0 && Math.random() < (100 / (100 + target.combatDetails.combatStats.tenacity))) {\r\n const curseExpireTime = 15000000000;\r\n let currentCurseEvent = this.eventQueue.getMatching((event) => event.type == CurseExpirationEvent.type && event.source == target);\r\n let currentCurseAmount = 0;\r\n if (currentCurseEvent) currentCurseAmount = currentCurseEvent.curseAmount;\r\n this.eventQueue.clearMatching((event) => event.type == CurseExpirationEvent.type && event.source == target);\r\n\r\n let curseExpirationEvent = new CurseExpirationEvent(this.simulationTime + curseExpireTime, currentCurseAmount, target);\r\n const curseBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/curse\",\r\n \"typeHrid\": \"/buff_types/damage_taken\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": source.combatDetails.combatStats.curse * curseExpirationEvent.curseAmount,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": curseExpireTime\r\n };\r\n target.addBuff(curseBuff);\r\n this.eventQueue.addEvent(curseExpirationEvent);\r\n }\r\n\r\n if (target.combatDetails.combatStats.weaken > 0) {\r\n const weakenExpireTime = 15000000000;\r\n source.weakenExpireTime = this.simulationTime + weakenExpireTime;\r\n let currentWeakenEvent = this.eventQueue.getMatching((event) => event.type == WeakenExpirationEvent.type && event.source == source);\r\n let weakenAmount = 0;\r\n if (currentWeakenEvent)\r\n weakenAmount = currentWeakenEvent.weakenAmount;\r\n this.eventQueue.clearMatching((event) => event.type == WeakenExpirationEvent.type && event.source == source);\r\n let weakenExpirationEvent = new WeakenExpirationEvent(this.simulationTime + weakenExpireTime, weakenAmount, source);\r\n const weakenBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/weaken\",\r\n \"typeHrid\": \"/buff_types/damage\",\r\n \"ratioBoost\": -1 * target.combatDetails.combatStats.weaken * weakenExpirationEvent.weakenAmount,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": weakenExpireTime\r\n };\r\n source.addBuff(weakenBuff);\r\n this.eventQueue.addEvent(weakenExpirationEvent);\r\n }\r\n\r\n this.simResult.addAttack(\r\n source,\r\n target,\r\n ability.hrid,\r\n attackResult.didHit ? attackResult.damageDone : \"miss\"\r\n );\r\n\r\n if (attackResult.thornDamageDone > 0) {\r\n this.simResult.addAttack(target, source, attackResult.thornType, attackResult.thornDamageDone);\r\n }\r\n if (this.zone.isDungeon && attackResult.thornDamageDone > 0 && source.isPlayer) {\r\n const log = this.buildCombatLog(target, attackResult.thornType, source, attackResult.thornDamageDone);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (target.combatDetails.combatStats.retaliation > 0) {\r\n this.simResult.addAttack(target, source, \"retaliation\", attackResult.retaliationDamageDone > 0 ? attackResult.retaliationDamageDone : \"miss\");\r\n }\r\n if (this.zone.isDungeon && attackResult.retaliationDamageDone > 0 && source.isPlayer) {\r\n const log = this.buildCombatLog(target, \"retaliation\", source, attackResult.retaliationDamageDone);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (target.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(target);\r\n this.simResult.addDeath(target);\r\n if (!target.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(target.hrid, false, this.simulationTime);\r\n }\r\n // console.log(target.hrid, \"died\");\r\n }\r\n\r\n\r\n if (attackResult.didHit && abilityEffect.pierceChance > Math.random()) {\r\n continue;\r\n }\r\n }\r\n \r\n if (parryTarget)\r\n {\r\n break;\r\n }\r\n\r\n if (abilityEffect.targetType == \"enemy\") {\r\n break;\r\n }\r\n }\r\n }\r\n\r\n processAbilityHealEffect(source, ability, abilityEffect) {\r\n\r\n if (abilityEffect.targetType == \"allAllies\") {\r\n let targets = source.isPlayer ? this.players : this.enemies;\r\n for (const target of targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0)) {\r\n let amountHealed = CombatUtilities.processHeal(source, abilityEffect, target);\r\n\r\n this.simResult.addHitpointsGained(target, ability.hrid, amountHealed);\r\n }\r\n return;\r\n }\r\n\r\n if (abilityEffect.targetType == \"lowestHpAlly\") {\r\n let targets = source.isPlayer ? this.players : this.enemies;\r\n let healTarget;\r\n for (const target of targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0)) {\r\n if (!healTarget) {\r\n healTarget = target;\r\n continue;\r\n }\r\n if (target.combatDetails.currentHitpoints < healTarget.combatDetails.currentHitpoints) {\r\n healTarget = target;\r\n }\r\n }\r\n\r\n if (healTarget) {\r\n let amountHealed = CombatUtilities.processHeal(source, abilityEffect, healTarget);\r\n\r\n this.simResult.addHitpointsGained(healTarget, ability.hrid, amountHealed);\r\n }\r\n return;\r\n }\r\n\r\n if (abilityEffect.targetType != \"self\") {\r\n throw new Error(\"Unsupported target type for heal ability effect: \" + ability.hrid);\r\n }\r\n\r\n let amountHealed = CombatUtilities.processHeal(source, abilityEffect, source);\r\n\r\n this.simResult.addHitpointsGained(source, ability.hrid, amountHealed);\r\n }\r\n\r\n processAbilityReviveEffect(source, ability, abilityEffect) {\r\n if (abilityEffect.targetType != \"deadAlly\") {\r\n throw new Error(\"Unsupported target type for revive ability effect: \" + ability.hrid);\r\n }\r\n\r\n let targets = source.isPlayer ? this.players : this.enemies;\r\n let reviveTarget = targets.find((unit) => unit && unit.combatDetails.currentHitpoints <= 0);\r\n\r\n if (reviveTarget) {\r\n this.eventQueue.clearMatching((event) => event.type == PlayerRespawnEvent.type && event.hrid == reviveTarget.hrid);\r\n\r\n reviveTarget.removeExpiredBuffs(this.simulationTime);\r\n\r\n let amountHealed = CombatUtilities.processRevive(source, abilityEffect, reviveTarget);\r\n\r\n this.simResult.addHitpointsGained(reviveTarget, ability.hrid, amountHealed);\r\n\r\n this.addNextAttackEvent(reviveTarget);\r\n\r\n if (!source.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(reviveTarget.hrid, true, this.simulationTime);\r\n }\r\n\r\n // console.log(source.hrid + \" revived \" + reviveTarget.hrid + \" with \" + amountHealed + \" HP.\" + ' at wave #' + (this.zone.encountersKilled - 1) + ' at time ' + this.simulationTime / 1000000000 + 's');\r\n }\r\n return;\r\n }\r\n\r\n processAbilityPromoteEffect(source, ability, abilityEffect) {\r\n const promotionHrids = [\"/monsters/enchanted_rook\", \"/monsters/enchanted_knight\", \"/monsters/enchanted_bishop\"];\r\n let randomPromotionIndex = Math.floor(Math.random() * promotionHrids.length);\r\n return new Monster(promotionHrids[randomPromotionIndex], source.difficultyTier);\r\n }\r\n\r\n processAbilitySpendHpEffect(source, ability, abilityEffect) {\r\n if (abilityEffect.targetType != \"self\") {\r\n throw new Error(\"Unsupported target type for spend hp ability effect: \" + ability.hrid);\r\n }\r\n\r\n let hpSpent = CombatUtilities.processSpendHp(source, abilityEffect);\r\n\r\n this.simResult.addHitpointsSpent(source, ability.hrid, hpSpent);\r\n }\r\n}\r\n\r\nexport default CombatSimulator;\r\n","class CombatUnit {\r\n isPlayer;\r\n isStunned = false;\r\n stunExpireTime = null;\r\n isBlinded = false;\r\n blindExpireTime = null;\r\n isSilenced = false;\r\n silenceExpireTime = null;\r\n\r\n isOutOfMana = false;\r\n\r\n // Base levels which don't change after initialization\r\n staminaLevel = 1;\r\n intelligenceLevel = 1;\r\n attackLevel = 1;\r\n meleeLevel = 1;\r\n defenseLevel = 1;\r\n rangedLevel = 1;\r\n magicLevel = 1;\r\n\r\n experience = 0;\r\n experienceRate = 0;\r\n enrageTime = 0;\r\n\r\n abilities = [null, null, null, null];\r\n food = [null, null, null];\r\n drinks = [null, null, null];\r\n houseRooms = [];\r\n achievements = null;\r\n dropTable = [];\r\n rareDropTable = [];\r\n abilityManaCosts = new Map();\r\n\r\n // Calculated combat stats including temporary buffs\r\n combatDetails = {\r\n staminaLevel: 1,\r\n intelligenceLevel: 1,\r\n attackLevel: 1,\r\n meleeLevel: 1,\r\n defenseLevel: 1,\r\n rangedLevel: 1,\r\n magicLevel: 1,\r\n maxHitpoints: 110,\r\n currentHitpoints: 110,\r\n maxManapoints: 110,\r\n currentManapoints: 110,\r\n stabAccuracyRating: 11,\r\n slashAccuracyRating: 11,\r\n smashAccuracyRating: 11,\r\n rangedAccuracyRating: 11,\r\n magicAccuracyRating: 11,\r\n stabMaxDamage: 11,\r\n slashMaxDamage: 11,\r\n smashMaxDamage: 11,\r\n rangedMaxDamage: 11,\r\n magicMaxDamage: 11,\r\n stabEvasionRating: 11,\r\n slashEvasionRating: 11,\r\n smashEvasionRating: 11,\r\n rangedEvasionRating: 11,\r\n magicEvasionRating: 11,\r\n defensiveMaxDamage: 0,\r\n totalArmor: 0.2,\r\n totalWaterResistance: 0.4,\r\n totalNatureResistance: 0.4,\r\n totalFireResistance: 0.4,\r\n abilityHaste: 0,\r\n tenacity: 0,\r\n totalThreat: 100,\r\n combatStats: {\r\n combatStyleHrid: \"/combat_styles/smash\",\r\n damageType: \"/damage_types/physical\",\r\n attackInterval: 3000000000,\r\n autoAttackDamage: 0,\r\n abilityDamage: 0,\r\n criticalRate: 0,\r\n criticalDamage: 0,\r\n stabAccuracy: 0,\r\n slashAccuracy: 0,\r\n smashAccuracy: 0,\r\n rangedAccuracy: 0,\r\n magicAccuracy: 0,\r\n stabDamage: 0,\r\n slashDamage: 0,\r\n smashDamage: 0,\r\n rangedDamage: 0,\r\n magicDamage: 0,\r\n defensiveDamage: 0,\r\n taskDamage: 0,\r\n physicalAmplify: 0,\r\n waterAmplify: 0,\r\n natureAmplify: 0,\r\n fireAmplify: 0,\r\n healingAmplify: 0,\r\n physicalThorns: 0,\r\n elementalThorns: 0,\r\n maxHitpoints: 0,\r\n maxManapoints: 0,\r\n stabEvasion: 0,\r\n slashEvasion: 0,\r\n smashEvasion: 0,\r\n rangedEvasion: 0,\r\n magicEvasion: 0,\r\n armor: 0,\r\n waterResistance: 0,\r\n natureResistance: 0,\r\n fireResistance: 0,\r\n lifeSteal: 0,\r\n hpRegenPer10: 0.01,\r\n mpRegenPer10: 0.01,\r\n combatDropRate: 0,\r\n combatDropQuantity: 0,\r\n combatRareFind: 0,\r\n combatExperience: 0,\r\n foodSlots: 1,\r\n drinkSlots: 1,\r\n armorPenetration: 0,\r\n waterPenetration: 0,\r\n naturePenetration: 0,\r\n firePenetration: 0,\r\n manaLeech: 0,\r\n castSpeed: 0,\r\n threat: 100,\r\n parry: 0,\r\n mayhem: 0,\r\n pierce: 0,\r\n curse: 0,\r\n ripple: 0,\r\n bloom: 0,\r\n blaze: 0,\r\n weaken: 0,\r\n fury: 0,\r\n foodHaste: 0,\r\n drinkConcentration: 0,\r\n damageTaken: 0,\r\n attackSpeed: 0,\r\n armorDamageRatio: 0,\r\n hpDrainRatio: 0,\r\n primaryTraining: \"\",\r\n focusTraining: \"\",\r\n staminaExperience: 0,\r\n intelligenceExperience: 0,\r\n attackExperience: 0,\r\n defenseExperience: 0,\r\n meleeExperience: 0,\r\n rangedExperience: 0,\r\n magicExperience: 0,\r\n retaliation: 0,\r\n },\r\n };\r\n combatBuffs = {};\r\n permanentBuffs = {};\r\n zoneBuffs = {};\r\n extraBuffs = {};\r\n\r\n constructor() { }\r\n\r\n updateCombatDetails() {\r\n if (this.isPlayer) {\r\n if (this.combatDetails.combatStats.hpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01 + this.combatDetails.combatStats.hpRegenPer10;\r\n }\r\n if (this.combatDetails.combatStats.mpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01 + this.combatDetails.combatStats.mpRegenPer10;\r\n }\r\n }\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((stat) => {\r\n this.combatDetails[stat + \"Level\"] = this[stat + \"Level\"];\r\n let boosts = this.getBuffBoosts(\"/buff_types/\" + stat + \"_level\");\r\n boosts.forEach((buff) => {\r\n this.combatDetails[stat + \"Level\"] += (this[stat + \"Level\"] * buff.ratioBoost);\r\n this.combatDetails[stat + \"Level\"] += buff.flatBoost;\r\n });\r\n });\r\n\r\n this.combatDetails.maxHitpoints = Math.floor\r\n (10 * (10 + this.combatDetails.staminaLevel) + this.combatDetails.combatStats.maxHitpoints);\r\n this.combatDetails.maxManapoints = Math.floor\r\n (10 * (10 + this.combatDetails.intelligenceLevel) + this.combatDetails.combatStats.maxManapoints);\r\n\r\n let accuracyRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_accuracy\").ratioBoost;\r\n let damageRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_damage\").ratioBoost;\r\n // if (accuracyRatioBoostFromFury > 0) {\r\n // console.log(\"Fury Boost: \" + accuracyRatioBoostFromFury);\r\n // }\r\n\r\n let accuracyRatioBoost = this.getBuffBoost(\"/buff_types/accuracy\").ratioBoost;\r\n let damageRatioBoost = this.getBuffBoost(\"/buff_types/damage\").ratioBoost;\r\n\r\n [\"stab\", \"slash\", \"smash\"].forEach((style) => {\r\n this.combatDetails[style + \"AccuracyRating\"] =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Accuracy\"]) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails[style + \"MaxDamage\"] =\r\n (10 + this.combatDetails.meleeLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Damage\"]) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n let baseEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats[style + \"Evasion\"]);\r\n this.combatDetails[style + \"EvasionRating\"] = baseEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails[style + \"EvasionRating\"] += boost.flatBoost;\r\n this.combatDetails[style + \"EvasionRating\"] += baseEvasion * boost.ratioBoost;\r\n }\r\n });\r\n\r\n this.combatDetails.defensiveMaxDamage = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.defensiveDamage);\r\n\r\n // when equiped bulwark\r\n if (this.equipment?.['/equipment_types/two_hand']?.hrid.includes(\"bulwark\")) {\r\n this.combatDetails.smashMaxDamage += this.combatDetails.defensiveMaxDamage;\r\n }\r\n\r\n this.combatDetails.rangedAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.rangedAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.rangedMaxDamage =\r\n (10 + this.combatDetails.rangedLevel) *\r\n (1 + this.combatDetails.combatStats.rangedDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseRangedEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.rangedEvasion);\r\n this.combatDetails.rangedEvasionRating = baseRangedEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.rangedEvasionRating += boost.flatBoost;\r\n this.combatDetails.rangedEvasionRating += baseRangedEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.damageTaken = this.getBuffBoost(\"/buff_types/damage_taken\").flatBoost;\r\n // if (this.combatDetails.combatStats.damageTaken > 0) {\r\n // console.log(\"Damage taken: \" + this.combatDetails.combatStats.damageTaken);\r\n // }\r\n\r\n this.combatDetails.magicAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.magicAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.magicMaxDamage =\r\n (10 + this.combatDetails.magicLevel) *\r\n (1 + this.combatDetails.combatStats.magicDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseMagicEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.magicEvasion);\r\n this.combatDetails.magicEvasionRating = baseMagicEvasion;\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.magicEvasionRating += boost.flatBoost;\r\n this.combatDetails.magicEvasionRating += baseMagicEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.physicalAmplify += this.getBuffBoost(\"/buff_types/physical_amplify\").flatBoost;\r\n this.combatDetails.combatStats.waterAmplify += this.getBuffBoost(\"/buff_types/water_amplify\").flatBoost;\r\n this.combatDetails.combatStats.natureAmplify += this.getBuffBoost(\"/buff_types/nature_amplify\").flatBoost;\r\n this.combatDetails.combatStats.fireAmplify += this.getBuffBoost(\"/buff_types/fire_amplify\").flatBoost;\r\n\r\n this.combatDetails.combatStats.attackInterval /= (1 + (this.combatDetails.attackLevel / 2000));\r\n\r\n let baseAttackSpeed = this.combatDetails.combatStats.attackSpeed;\r\n this.combatDetails.combatStats.attackInterval /= (1 + baseAttackSpeed);\r\n let attackIntervalBoosts = this.getBuffBoosts(\"/buff_types/attack_speed\");\r\n let attackIntervalRatioBoost = attackIntervalBoosts\r\n .map((boost) => boost.ratioBoost)\r\n .reduce((prev, cur) => prev + cur, 0);\r\n this.combatDetails.combatStats.attackInterval /= (1 + attackIntervalRatioBoost);\r\n\r\n let baseArmor = 0.2 * this.combatDetails.defenseLevel + this.combatDetails.combatStats.armor;\r\n this.combatDetails.totalArmor = baseArmor;\r\n let armorBoosts = this.getBuffBoosts(\"/buff_types/armor\");\r\n for (const boost of armorBoosts) {\r\n this.combatDetails.totalArmor += boost.flatBoost;\r\n this.combatDetails.totalArmor += baseArmor * boost.ratioBoost;\r\n }\r\n\r\n let baseWaterResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.waterResistance;\r\n this.combatDetails.totalWaterResistance = baseWaterResistance;\r\n let waterResistanceBoosts = this.getBuffBoosts(\"/buff_types/water_resistance\");\r\n for (const boost of waterResistanceBoosts) {\r\n this.combatDetails.totalWaterResistance += boost.flatBoost;\r\n this.combatDetails.totalWaterResistance += baseWaterResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseNatureResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.natureResistance;\r\n this.combatDetails.totalNatureResistance = baseNatureResistance;\r\n let natureResistanceBoosts = this.getBuffBoosts(\"/buff_types/nature_resistance\");\r\n for (const boost of natureResistanceBoosts) {\r\n this.combatDetails.totalNatureResistance += boost.flatBoost;\r\n this.combatDetails.totalNatureResistance += baseNatureResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseFireResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.fireResistance;\r\n this.combatDetails.totalFireResistance = baseFireResistance;\r\n let fireResistanceBoosts = this.getBuffBoosts(\"/buff_types/fire_resistance\");\r\n for (const boost of fireResistanceBoosts) {\r\n this.combatDetails.totalFireResistance += boost.flatBoost;\r\n this.combatDetails.totalFireResistance += baseFireResistance * boost.ratioBoost;\r\n }\r\n\r\n let hpRegenBoosts = this.getBuffBoost(\"/buff_types/hp_regen\");\r\n this.combatDetails.combatStats.hpRegenPer10 += this.combatDetails.combatStats.hpRegenPer10 * hpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.hpRegenPer10 += hpRegenBoosts.flatBoost;\r\n\r\n let mpRegenBoosts = this.getBuffBoost(\"/buff_types/mp_regen\");\r\n this.combatDetails.combatStats.mpRegenPer10 += this.combatDetails.combatStats.mpRegenPer10 * mpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.mpRegenPer10 += mpRegenBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.lifeSteal += this.getBuffBoost(\"/buff_types/life_steal\").flatBoost;\r\n this.combatDetails.combatStats.physicalThorns += this.getBuffBoost(\r\n \"/buff_types/physical_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.elementalThorns += this.getBuffBoost(\r\n \"/buff_types/elemental_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.combatExperience += this.getBuffBoost(\"/buff_types/wisdom\").flatBoost;\r\n this.combatDetails.combatStats.criticalRate += this.getBuffBoost(\"/buff_types/critical_rate\").flatBoost;\r\n this.combatDetails.combatStats.criticalDamage += this.getBuffBoost(\"/buff_types/critical_damage\").flatBoost;\r\n\r\n this.combatDetails.combatStats.castSpeed += this.getBuffBoost(\"/buff_types/cast_speed\").flatBoost;\r\n this.combatDetails.combatStats.castSpeed += this.combatDetails[\"attackLevel\"] / 2000;\r\n\r\n let combatDropRateBoosts = this.getBuffBoost(\"/buff_types/combat_drop_rate\");\r\n this.combatDetails.combatStats.combatDropRate += (1 + this.combatDetails.combatStats.combatDropRate) * combatDropRateBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropRate += combatDropRateBoosts.flatBoost;\r\n let combatRareFindBoosts = this.getBuffBoost(\"/buff_types/rare_find\");\r\n this.combatDetails.combatStats.combatRareFind += (1 + this.combatDetails.combatStats.combatRareFind) * combatRareFindBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatRareFind += combatRareFindBoosts.flatBoost;\r\n let combatDropQuantityBoosts = this.getBuffBoost(\"/buff_types/combat_drop_quantity\");\r\n this.combatDetails.combatStats.combatDropQuantity += (1 + this.combatDetails.combatStats.combatDropQuantity) * combatDropQuantityBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropQuantity += combatDropQuantityBoosts.flatBoost;\r\n\r\n let baseThreat = 100 + this.combatDetails.combatStats.threat;\r\n this.combatDetails.totalThreat = baseThreat;\r\n let threatBoosts = this.getBuffBoost(\"/buff_types/threat\");\r\n if (threatBoosts.ratioBoost !== 0) {\r\n this.combatDetails.combatStats.threat += baseThreat * threatBoosts.ratioBoost;\r\n } else {\r\n this.combatDetails.combatStats.threat = baseThreat;\r\n }\r\n this.combatDetails.combatStats.threat += threatBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.retaliation += this.getBuffBoost(\"/buff_types/retaliation\").flatBoost;\r\n }\r\n\r\n addBuff(buff, currentTime) {\r\n buff.startTime = currentTime;\r\n this.combatBuffs[buff.uniqueHrid] = buff;\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n removeBuff(buff) {\r\n if (!this.combatBuffs[buff.uniqueHrid]) {\r\n return;\r\n }\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n addPermanentBuff(buff) {\r\n if (this.permanentBuffs[buff.typeHrid]) {\r\n this.permanentBuffs[buff.typeHrid].flatBoost += buff.flatBoost;\r\n this.permanentBuffs[buff.typeHrid].ratioBoost += buff.ratioBoost;\r\n } else {\r\n this.permanentBuffs[buff.typeHrid] = buff;\r\n }\r\n }\r\n\r\n generatePermanentBuffs() {\r\n for (let i = 0; i < this.houseRooms.length; i++) {\r\n const houseRoom = this.houseRooms[i];\r\n houseRoom.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n\r\n if (this.achievements) {\r\n this.achievements.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.zoneBuffs) {\r\n this.zoneBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.extraBuffs) {\r\n this.extraBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n }\r\n\r\n removeExpiredBuffs(currentTime) {\r\n let expiredBuffs = Object.values(this.combatBuffs).filter(\r\n (buff) => buff.startTime + buff.duration <= currentTime\r\n );\r\n expiredBuffs.forEach((buff) => {\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n });\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearBuffs() {\r\n this.combatBuffs = structuredClone(this.permanentBuffs);\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearCCs() {\r\n this.isStunned = false;\r\n this.stunExpireTime = null;\r\n this.isSilenced = false;\r\n this.silenceExpireTime = null;\r\n this.isBlinded = false;\r\n this.blindExpireTime = null;\r\n this.combatDetails.combatStats.damageTaken = 0;\r\n }\r\n\r\n getBuffBoosts(type) {\r\n let boosts = [];\r\n Object.values(this.combatBuffs)\r\n .filter((buff) => buff.typeHrid == type)\r\n .forEach((buff) => {\r\n boosts.push({ ratioBoost: buff.ratioBoost, flatBoost: buff.flatBoost });\r\n });\r\n\r\n return boosts;\r\n }\r\n\r\n getBuffBoost(type) {\r\n let boosts = this.getBuffBoosts(type);\r\n\r\n let boost = {\r\n ratioBoost: 0,\r\n flatBoost: 0,\r\n };\r\n\r\n for (let i = 0; i < boosts.length; i++) {\r\n boost.ratioBoost += boosts[i]?.ratioBoost ?? 0;\r\n boost.flatBoost += boosts[i]?.flatBoost ?? 0;\r\n }\r\n\r\n return boost;\r\n }\r\n\r\n reset(currentTime = 0) {\r\n this.clearCCs();\r\n this.clearBuffs();\r\n this.updateCombatDetails();\r\n this.resetCooldowns(currentTime);\r\n\r\n this.combatDetails.currentHitpoints = this.combatDetails.maxHitpoints;\r\n this.combatDetails.currentManapoints = this.combatDetails.maxManapoints;\r\n }\r\n\r\n resetCooldowns(currentTime = 0) {\r\n this.food.filter((food) => food != null).forEach((food) => (food.lastUsed = Number.MIN_SAFE_INTEGER));\r\n this.drinks.filter((drink) => drink != null).forEach((drink) => (drink.lastUsed = Number.MIN_SAFE_INTEGER));\r\n\r\n let haste = this.combatDetails.combatStats.abilityHaste;\r\n\r\n this.abilities\r\n .filter((ability) => ability != null)\r\n .forEach((ability) => {\r\n if (this.isPlayer) {\r\n ability.lastUsed = Number.MIN_SAFE_INTEGER;\r\n } else {\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n ability.lastUsed = currentTime - Math.floor(cooldownDuration * 0.5) + Math.floor(Math.random() * cooldownDuration * 0.5);\r\n }\r\n });\r\n }\r\n\r\n addHitpoints(hitpoints) {\r\n let hitpointsAdded = 0;\r\n\r\n if (this.combatDetails.currentHitpoints >= this.combatDetails.maxHitpoints) {\r\n return hitpointsAdded;\r\n }\r\n\r\n let newHitpoints = Math.min(this.combatDetails.currentHitpoints + hitpoints, this.combatDetails.maxHitpoints);\r\n hitpointsAdded = newHitpoints - this.combatDetails.currentHitpoints;\r\n this.combatDetails.currentHitpoints = newHitpoints;\r\n\r\n return hitpointsAdded;\r\n }\r\n\r\n addManapoints(manapoints) {\r\n let manapointsAdded = 0;\r\n\r\n if (this.combatDetails.currentManapoints >= this.combatDetails.maxManapoints) {\r\n return manapointsAdded;\r\n }\r\n\r\n let newManapoints = Math.min(\r\n this.combatDetails.currentManapoints + manapoints,\r\n this.combatDetails.maxManapoints\r\n );\r\n manapointsAdded = newManapoints - this.combatDetails.currentManapoints;\r\n this.combatDetails.currentManapoints = newManapoints;\r\n\r\n return manapointsAdded;\r\n }\r\n}\r\n\r\nexport default CombatUnit;\r\n","class CombatUtilities {\r\n static getTarget(enemies) {\r\n if (!enemies) {\r\n return null;\r\n }\r\n let target = enemies.find((enemy) => enemy.combatDetails.currentHitpoints > 0);\r\n\r\n return target ?? null;\r\n }\r\n\r\n static randomInt(min, max) {\r\n if (max < min) {\r\n let temp = min;\r\n min = max;\r\n max = temp;\r\n }\r\n\r\n let minCeil = Math.ceil(min);\r\n let maxFloor = Math.floor(max);\r\n\r\n if (Math.floor(min) == maxFloor) {\r\n return Math.floor((min + max) / 2 + Math.random());\r\n }\r\n\r\n let minTail = -1 * (min - minCeil);\r\n let maxTail = max - maxFloor;\r\n\r\n let balancedWeight = 2 * minTail + (maxFloor - minCeil);\r\n let balancedAverage = (maxFloor + minCeil) / 2;\r\n let average = (max + min) / 2;\r\n let extraTailWeight = (balancedWeight * (average - balancedAverage)) / (maxFloor + 1 - average);\r\n let extraTailChance = Math.abs(extraTailWeight / (extraTailWeight + balancedWeight));\r\n\r\n if (Math.random() < extraTailChance) {\r\n if (maxTail > minTail) {\r\n return Math.floor(maxFloor + 1);\r\n } else {\r\n return Math.floor(minCeil - 1);\r\n }\r\n }\r\n\r\n if (maxTail > minTail) {\r\n return Math.floor(min + Math.random() * (maxFloor + minTail - min + 1));\r\n } else {\r\n return Math.floor(minCeil - maxTail + Math.random() * (max - (minCeil - maxTail) + 1));\r\n }\r\n }\r\n\r\n static processAttack(source, target, abilityEffect = null) {\r\n let combatStyle = abilityEffect\r\n ? abilityEffect.combatStyleHrid\r\n : source.combatDetails.combatStats.combatStyleHrid;\r\n let damageType = abilityEffect ? abilityEffect.damageType : source.combatDetails.combatStats.damageType;\r\n\r\n let sourceAccuracyRating = 1;\r\n let sourceAutoAttackMaxDamage = 1;\r\n let targetEvasionRating = 1;\r\n\r\n switch (combatStyle) {\r\n case \"/combat_styles/stab\":\r\n sourceAccuracyRating = source.combatDetails.stabAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.stabMaxDamage;\r\n targetEvasionRating = target.combatDetails.stabEvasionRating;\r\n break;\r\n case \"/combat_styles/slash\":\r\n sourceAccuracyRating = source.combatDetails.slashAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.slashMaxDamage;\r\n targetEvasionRating = target.combatDetails.slashEvasionRating;\r\n break;\r\n case \"/combat_styles/smash\":\r\n sourceAccuracyRating = source.combatDetails.smashAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.smashMaxDamage;\r\n targetEvasionRating = target.combatDetails.smashEvasionRating;\r\n break;\r\n case \"/combat_styles/ranged\":\r\n sourceAccuracyRating = source.combatDetails.rangedAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.rangedMaxDamage;\r\n targetEvasionRating = target.combatDetails.rangedEvasionRating;\r\n break;\r\n case \"/combat_styles/magic\":\r\n sourceAccuracyRating = source.combatDetails.magicAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.magicMaxDamage;\r\n targetEvasionRating = target.combatDetails.magicEvasionRating;\r\n break;\r\n default:\r\n throw new Error(\"Unknown combat style: \" + combatStyle);\r\n }\r\n\r\n let sourceDamageMultiplier = 1;\r\n let sourceResistance = 0;\r\n let sourcePenetration = 0;\r\n let targetResistance = 0;\r\n let targetThornPower = 0;\r\n let targetPenetration = 0;\r\n let thornType;\r\n\r\n switch (damageType) {\r\n case \"/damage_types/physical\":\r\n sourceDamageMultiplier = 1 + source.combatDetails.combatStats.physicalAmplify;\r\n sourceResistance = source.combatDetails.totalArmor;\r\n sourcePenetration = source.combatDetails.combatStats.armorPenetration;\r\n targetResistance = target.combatDetails.totalArmor;\r\n targetThornPower = target.combatDetails.combatStats.physicalThorns;\r\n targetPenetration = target.combatDetails.combatStats.armorPenetration;\r\n thornType = \"physicalThorns\";\r\n break;\r\n case \"/damage_types/water\":\r\n sourceDamageMultiplier = 1 + source.combatDetails.combatStats.waterAmplify;\r\n sourceResistance = source.combatDetails.totalWaterResistance;\r\n sourcePenetration = source.combatDetails.combatStats.waterPenetration;\r\n targetResistance = target.combatDetails.totalWaterResistance;\r\n targetThornPower = target.combatDetails.combatStats.elementalThorns;\r\n targetPenetration = target.combatDetails.combatStats.waterPenetration;\r\n thornType = \"elementalThorns\";\r\n break;\r\n case \"/damage_types/nature\":\r\n sourceDamageMultiplier = 1 + source.combatDetails.combatStats.natureAmplify;\r\n sourceResistance = source.combatDetails.totalNatureResistance;\r\n sourcePenetration = source.combatDetails.combatStats.naturePenetration;\r\n targetResistance = target.combatDetails.totalNatureResistance;\r\n targetThornPower = target.combatDetails.combatStats.elementalThorns;\r\n targetPenetration = target.combatDetails.combatStats.naturePenetration;\r\n thornType = \"elementalThorns\";\r\n break;\r\n case \"/damage_types/fire\":\r\n sourceDamageMultiplier = 1 + source.combatDetails.combatStats.fireAmplify;\r\n sourceResistance = source.combatDetails.totalFireResistance;\r\n sourcePenetration = source.combatDetails.combatStats.firePenetration;\r\n targetResistance = target.combatDetails.totalFireResistance;\r\n targetThornPower = target.combatDetails.combatStats.elementalThorns;\r\n targetPenetration = target.combatDetails.combatStats.firePenetration;\r\n thornType = \"elementalThorns\";\r\n break;\r\n default:\r\n throw new Error(\"Unknown damage type: \" + damageType);\r\n }\r\n\r\n let hitChance = 1;\r\n let critChance = 0;\r\n let isCrit = false;\r\n let bonusCritChance = source.combatDetails.combatStats.criticalRate;\r\n let bonusCritDamage = source.combatDetails.combatStats.criticalDamage;\r\n\r\n if (abilityEffect) {\r\n sourceAccuracyRating *= (1 + abilityEffect.bonusAccuracyRatio);\r\n }\r\n\r\n if (source.isWeakened) {\r\n sourceAccuracyRating = sourceAccuracyRating - (source.weakenPercentage * sourceAccuracyRating);\r\n }\r\n\r\n hitChance =\r\n Math.pow(sourceAccuracyRating, 1.4) /\r\n (Math.pow(sourceAccuracyRating, 1.4) + Math.pow(targetEvasionRating, 1.4));\r\n\r\n if (combatStyle == \"/combat_styles/ranged\") {\r\n critChance = 0.3 * hitChance;\r\n }\r\n\r\n critChance = critChance + bonusCritChance;\r\n\r\n let baseDamageFlat = abilityEffect ? abilityEffect.damageFlat : 0;\r\n let baseDamageRatio = abilityEffect ? abilityEffect.damageRatio : 1;\r\n\r\n let armorDamageRatioFlat = abilityEffect ? abilityEffect.armorDamageRatio * source.combatDetails.totalArmor : 0;\r\n\r\n let sourceMinDamage = sourceDamageMultiplier * (1 + baseDamageFlat + armorDamageRatioFlat);\r\n let sourceMaxDamage = sourceDamageMultiplier * (baseDamageRatio * sourceAutoAttackMaxDamage + baseDamageFlat + armorDamageRatioFlat);\r\n\r\n if (Math.random() < critChance) {\r\n sourceMaxDamage = sourceMaxDamage * (1 + bonusCritDamage);\r\n sourceMinDamage = sourceMaxDamage;\r\n isCrit = true;\r\n }\r\n\r\n let damageRoll = CombatUtilities.randomInt(sourceMinDamage, sourceMaxDamage);\r\n damageRoll *= (1 + source.combatDetails.combatStats.taskDamage);\r\n damageRoll *= (1 + target.combatDetails.combatStats.damageTaken);\r\n if (!abilityEffect) {\r\n damageRoll += damageRoll * source.combatDetails.combatStats.autoAttackDamage;\r\n } else {\r\n damageRoll *= (1 + source.combatDetails.combatStats.abilityDamage);\r\n }\r\n\r\n let damageDone = 0;\r\n let thornDamageDone = 0;\r\n\r\n let didHit = false;\r\n if (Math.random() < hitChance) {\r\n didHit = true;\r\n let penetratedTargetResistance = targetResistance;\r\n\r\n if (sourcePenetration > 0 && targetResistance > 0) {\r\n penetratedTargetResistance = targetResistance / (1 + sourcePenetration);\r\n }\r\n\r\n let targetDamageTakenRatio = 100 / (100 + penetratedTargetResistance);\r\n if (penetratedTargetResistance < 0) {\r\n targetDamageTakenRatio = (100 - penetratedTargetResistance) / 100;\r\n }\r\n\r\n let mitigatedDamage = Math.ceil(targetDamageTakenRatio * damageRoll);\r\n damageDone = Math.min(mitigatedDamage, target.combatDetails.currentHitpoints);\r\n target.combatDetails.currentHitpoints -= damageDone;\r\n }\r\n\r\n if (targetThornPower > 0.0 && targetResistance > -99.0) {\r\n let penetratedSourceResistance = sourceResistance\r\n\r\n if (sourceResistance > 0) {\r\n penetratedSourceResistance = sourceResistance / (1 + targetPenetration);\r\n }\r\n\r\n let sourceDamageTakenRatio = 100.0 / (100 + penetratedSourceResistance);\r\n if (penetratedSourceResistance < 0) {\r\n sourceDamageTakenRatio = (100 - penetratedSourceResistance) / 100;\r\n }\r\n\r\n let targetTaskDamageMultiplier = 1.0 + target.combatDetails.combatStats.taskDamage;\r\n let sourceDamageTakenMultiplier = 1.0 + source.combatDetails.combatStats.damageTaken;\r\n let targetDamageMultiplier = targetTaskDamageMultiplier * sourceDamageTakenMultiplier;\r\n\r\n let thornsDamageRoll = CombatUtilities.randomInt(1,\r\n targetDamageMultiplier\r\n * target.combatDetails.defensiveMaxDamage\r\n * (1.0 + targetResistance / 100.0)\r\n * targetThornPower);\r\n\r\n let mitigatedThornsDamage = Math.ceil(sourceDamageTakenRatio * thornsDamageRoll);\r\n\r\n thornDamageDone = Math.min(mitigatedThornsDamage, source.combatDetails.currentHitpoints);\r\n source.combatDetails.currentHitpoints -= thornDamageDone;\r\n }\r\n\r\n let retaliationDamageDone = 0;\r\n if (target.combatDetails.combatStats.retaliation > 0) {\r\n let retaliationHitChance = \r\n Math.pow(target.combatDetails.smashAccuracyRating, 1.4) /\r\n (Math.pow(target.combatDetails.smashAccuracyRating, 1.4) + Math.pow(source.combatDetails.smashEvasionRating, 1.4));\r\n\r\n if (retaliationHitChance > Math.random()) {\r\n let sourceEffectiveArmor = source.combatDetails.totalArmor;\r\n if (sourceEffectiveArmor > 0) {\r\n sourceEffectiveArmor = sourceEffectiveArmor / (1.0 + target.combatDetails.combatStats.armorPenetration);\r\n }\r\n\r\n let sourceDamageTakenRatio = 100.0 / (100.0 + sourceEffectiveArmor);\r\n if (sourceEffectiveArmor < 0) {\r\n sourceDamageTakenRatio = (100.0 - sourceEffectiveArmor) / 100.0;\r\n }\r\n\r\n let targetTaskDamageMultiplier = 1.0 + target.combatDetails.combatStats.taskDamage;\r\n let sourceDamageTakenMultiplier = 1.0 + source.combatDetails.combatStats.damageTaken;\r\n let retaliationDamageMultiplier = targetTaskDamageMultiplier * sourceDamageTakenMultiplier;\r\n\r\n let premitigatedDamage = damageRoll;\r\n premitigatedDamage = Math.min(premitigatedDamage, target.combatDetails.defensiveMaxDamage * 5);\r\n\r\n let retaliationMinDamage = retaliationDamageMultiplier * target.combatDetails.combatStats.retaliation * premitigatedDamage;\r\n let retaliationMaxDamage = retaliationDamageMultiplier * target.combatDetails.combatStats.retaliation * (target.combatDetails.defensiveMaxDamage + premitigatedDamage);\r\n\r\n let retaliationDamageRoll = CombatUtilities.randomInt(retaliationMinDamage, retaliationMaxDamage);\r\n let mitigatedRetaliationDamage = Math.ceil(sourceDamageTakenRatio * retaliationDamageRoll);\r\n retaliationDamageDone = Math.min(mitigatedRetaliationDamage, source.combatDetails.currentHitpoints);\r\n source.combatDetails.currentHitpoints -= retaliationDamageDone;\r\n }\r\n }\r\n\r\n let lifeStealHeal = 0;\r\n if (!abilityEffect && didHit && source.combatDetails.combatStats.lifeSteal > 0) {\r\n lifeStealHeal = source.addHitpoints(Math.floor(source.combatDetails.combatStats.lifeSteal * damageDone));\r\n }\r\n\r\n let hpDrain = 0;\r\n if (abilityEffect && didHit && abilityEffect.hpDrainRatio > 0) {\r\n let healingAmplify = 1 + source.combatDetails.combatStats.healingAmplify;\r\n hpDrain = source.addHitpoints(Math.floor(abilityEffect.hpDrainRatio * damageDone * healingAmplify));\r\n }\r\n\r\n let manaLeechMana = 0;\r\n if (!abilityEffect && didHit && source.combatDetails.combatStats.manaLeech > 0) {\r\n manaLeechMana = source.addManapoints(Math.floor(source.combatDetails.combatStats.manaLeech * damageDone));\r\n }\r\n\r\n return { damageDone, didHit, thornDamageDone, thornType, retaliationDamageDone, lifeStealHeal, hpDrain, manaLeechMana, isCrit};\r\n }\r\n\r\n static processHeal(source, abilityEffect, target) {\r\n if (abilityEffect.combatStyleHrid != \"/combat_styles/magic\") {\r\n throw new Error(\"Heal ability effect not supported for combat style: \" + abilityEffect.combatStyleHrid);\r\n }\r\n\r\n let healingAmplify = 1 + source.combatDetails.combatStats.healingAmplify;\r\n let magicMaxDamage = source.combatDetails.magicMaxDamage;\r\n\r\n let baseHealFlat = abilityEffect.damageFlat;\r\n let baseHealRatio = abilityEffect.damageRatio;\r\n\r\n let minHeal = healingAmplify * (1 + baseHealFlat);\r\n let maxHeal = healingAmplify * (baseHealRatio * magicMaxDamage + baseHealFlat);\r\n\r\n let heal = this.randomInt(minHeal, maxHeal);\r\n let amountHealed = target.addHitpoints(heal);\r\n\r\n return amountHealed;\r\n }\r\n\r\n static processRevive(source, abilityEffect, target) {\r\n if (abilityEffect.combatStyleHrid != \"/combat_styles/magic\") {\r\n throw new Error(\"Heal ability effect not supported for combat style: \" + abilityEffect.combatStyleHrid);\r\n }\r\n\r\n let healingAmplify = 1 + source.combatDetails.combatStats.healingAmplify;\r\n let magicMaxDamage = source.combatDetails.magicMaxDamage;\r\n\r\n let baseHealFlat = abilityEffect.damageFlat;\r\n let baseHealRatio = abilityEffect.damageRatio;\r\n\r\n let minHeal = healingAmplify * (1 + baseHealFlat);\r\n let maxHeal = healingAmplify * (baseHealRatio * magicMaxDamage + baseHealFlat);\r\n\r\n let heal = this.randomInt(minHeal, maxHeal);\r\n let amountHealed = target.addHitpoints(heal);\r\n target.combatDetails.currentManapoints = target.combatDetails.maxManapoints;\r\n target.clearCCs();\r\n\r\n // target.clearBuffs();\r\n\r\n return amountHealed;\r\n }\r\n\r\n static processSpendHp(source, abilityEffect) {\r\n let currentHp = source.combatDetails.currentHitpoints;\r\n let spendHpRatio = abilityEffect.spendHpRatio;\r\n\r\n let spentHp = Math.floor(currentHp * spendHpRatio);\r\n\r\n source.combatDetails.currentHitpoints -= spentHp;\r\n\r\n return spentHp;\r\n }\r\n\r\n static calculateTickValue(totalValue, totalTicks, currentTick) {\r\n let currentSum = Math.floor((currentTick * totalValue) / totalTicks);\r\n let previousSum = Math.floor(((currentTick - 1) * totalValue) / totalTicks);\r\n\r\n return currentSum - previousSum;\r\n }\r\n}\r\n\r\nexport default CombatUtilities;\r\n","import Buff from \"./buff\";\r\nimport itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nclass Consumable {\r\n constructor(hrid, triggers = null) {\r\n this.hrid = hrid;\r\n\r\n let gameConsumable = itemDetailMap[this.hrid];\r\n if (!gameConsumable) {\r\n throw new Error(\"No consumable found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.cooldownDuration = gameConsumable.consumableDetail.cooldownDuration;\r\n this.hitpointRestore = gameConsumable.consumableDetail.hitpointRestore;\r\n this.manapointRestore = gameConsumable.consumableDetail.manapointRestore;\r\n this.recoveryDuration = gameConsumable.consumableDetail.recoveryDuration;\r\n this.catagoryHrid = gameConsumable.categoryHrid;\r\n\r\n this.buffs = [];\r\n if (gameConsumable.consumableDetail.buffs) {\r\n for (const consumableBuff of gameConsumable.consumableDetail.buffs) {\r\n let buff = new Buff(consumableBuff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameConsumable.consumableDetail.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let consumable = new Consumable(dto.hrid, triggers);\r\n\r\n return consumable;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n let consumableHaste;\r\n if (this.catagoryHrid.includes(\"food\")) {\r\n consumableHaste = source.combatDetails.combatStats.foodHaste\r\n } else {\r\n consumableHaste = source.combatDetails.combatStats.drinkConcentration;\r\n }\r\n let cooldownDuration = this.cooldownDuration;\r\n if (consumableHaste > 0) {\r\n cooldownDuration = cooldownDuration / (1 + consumableHaste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Consumable;\r\n","class Drops {\r\n\r\n constructor(itemHrid, dropRate, minCount, maxCount, difficultyTier) {\r\n this.itemHrid = itemHrid;\r\n this.dropRate = dropRate;\r\n this.minCount = minCount;\r\n this.maxCount = maxCount;\r\n this.difficultyTier = difficultyTier;\r\n }\r\n}\r\n\r\nexport default Drops;\r\n","import itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport enhancementLevelTotalMultiplierTable from \"./data/enhancementLevelTotalBonusMultiplierTable.json\";\r\n\r\nclass Equipment {\r\n constructor(hrid, enhancementLevel) {\r\n this.hrid = hrid;\r\n let gameItem = itemDetailMap[this.hrid];\r\n if (!gameItem) {\r\n throw new Error(\"No equipment found for hrid: \" + this.hrid);\r\n }\r\n this.gameItem = gameItem;\r\n this.enhancementLevel = enhancementLevel;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let equipment = new Equipment(dto.hrid, dto.enhancementLevel);\r\n\r\n return equipment;\r\n }\r\n\r\n getCombatStat(combatStat) {\r\n let multiplier = enhancementLevelTotalMultiplierTable[this.enhancementLevel];\r\n if(this.gameItem.equipmentDetail.combatStats[combatStat]) {\r\n let enhancementBonus = this.gameItem.equipmentDetail.combatEnhancementBonuses[combatStat] || 0;\r\n let stat = this.gameItem.equipmentDetail.combatStats[combatStat] + multiplier * enhancementBonus;\r\n return stat;\r\n }\r\n return 0;\r\n }\r\n\r\n getCombatStyle() {\r\n return this.gameItem.equipmentDetail.combatStats.combatStyleHrids[0];\r\n }\r\n\r\n getDamageType() {\r\n return this.gameItem.equipmentDetail.combatStats.damageType;\r\n }\r\n\r\n getPrimaryTraining() {\r\n return this.gameItem.equipmentDetail.combatStats.primaryTraining;\r\n }\r\n\r\n getFocusTraining(){\r\n return this.gameItem.equipmentDetail.combatStats.focusTraining;\r\n }\r\n}\r\n\r\nexport default Equipment;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass AbilityCastEndEvent extends CombatEvent {\r\n static type = \"abilityCastEndEvent\";\r\n\r\n constructor(time, source, ability) {\r\n super(AbilityCastEndEvent.type, time);\r\n\r\n this.source = source;\r\n this.ability = ability;\r\n }\r\n}\r\n\r\nexport default AbilityCastEndEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass AutoAttackEvent extends CombatEvent {\r\n static type = \"autoAttack\";\r\n\r\n constructor(time, source) {\r\n super(AutoAttackEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default AutoAttackEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass AwaitCooldownEvent extends CombatEvent {\r\n static type = \"awaitCooldownEvent\";\r\n\r\n constructor(time, source) {\r\n super(AwaitCooldownEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default AwaitCooldownEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass BlindExpirationEvent extends CombatEvent {\r\n static type = \"blindExpiration\";\r\n\r\n constructor(time, source) {\r\n super(BlindExpirationEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default BlindExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass CheckBuffExpirationEvent extends CombatEvent {\r\n static type = \"checkBuffExpiration\";\r\n\r\n constructor(time, source) {\r\n super(CheckBuffExpirationEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default CheckBuffExpirationEvent;\r\n","class CombatEvent {\r\n constructor(type, time) {\r\n this.type = type;\r\n this.time = time;\r\n }\r\n}\r\n\r\nexport default CombatEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass CombatStartEvent extends CombatEvent {\r\n static type = \"combatStart\";\r\n\r\n constructor(time) {\r\n super(CombatStartEvent.type, time);\r\n }\r\n}\r\n\r\nexport default CombatStartEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass ConsumableTickEvent extends CombatEvent {\r\n static type = \"consumableTick\";\r\n\r\n constructor(time, source, consumable, totalTicks, currentTick) {\r\n super(ConsumableTickEvent.type, time);\r\n\r\n this.source = source;\r\n this.consumable = consumable;\r\n this.totalTicks = totalTicks;\r\n this.currentTick = currentTick;\r\n }\r\n}\r\n\r\nexport default ConsumableTickEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass CooldownReadyEvent extends CombatEvent {\r\n static type = \"cooldownReady\";\r\n\r\n constructor(time) {\r\n super(CooldownReadyEvent.type, time);\r\n }\r\n}\r\n\r\nexport default CooldownReadyEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass CurseExpirationEvent extends CombatEvent {\r\n static type = \"curseExpiration\";\r\n static maxCurseStacks = 5;\r\n\r\n constructor(time, curseAmount, source) {\r\n super(CurseExpirationEvent.type, time);\r\n\r\n this.curseAmount = Math.min(curseAmount + 1, CurseExpirationEvent.maxCurseStacks);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default CurseExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass DamageOverTimeEvent extends CombatEvent {\r\n static type = \"damageOverTime\";\r\n\r\n constructor(time, sourceRef, target, damage, totalTicks, currentTick, combatStyleHrid) {\r\n super(DamageOverTimeEvent.type, time);\r\n\r\n // Calling it 'source' would wrongly clear Damage Over Time when the source dies\r\n this.sourceRef = sourceRef;\r\n this.target = target;\r\n this.damage = damage;\r\n this.totalTicks = totalTicks;\r\n this.currentTick = currentTick;\r\n this.combatStyleHrid = combatStyleHrid;\r\n }\r\n}\r\n\r\nexport default DamageOverTimeEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass EnemyRespawnEvent extends CombatEvent {\r\n static type = \"enemyRespawn\";\r\n\r\n constructor(time) {\r\n super(EnemyRespawnEvent.type, time);\r\n }\r\n}\r\n\r\nexport default EnemyRespawnEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass EnrageTickEvent extends CombatEvent {\r\n static type = \"enrageTick\";\r\n\r\n constructor(time, encounterTime) {\r\n\r\n super(EnrageTickEvent.type, time);\r\n\r\n this.encounterTime = encounterTime;\r\n }\r\n}\r\n\r\nexport default EnrageTickEvent;\r\n","import Heap from \"heap-js\";\r\n\r\nclass EventQueue {\r\n constructor() {\r\n this.minHeap = new Heap((a, b) => a.time - b.time);\r\n }\r\n\r\n addEvent(event) {\r\n this.minHeap.push(event);\r\n }\r\n\r\n getNextEvent() {\r\n return this.minHeap.pop();\r\n }\r\n\r\n containsEventOfType(type) {\r\n let heapEvents = this.minHeap.toArray();\r\n\r\n return heapEvents.some((event) => event.type == type);\r\n }\r\n\r\n containsEventOfTypeAndHrid(type, hrid) {\r\n let heapEvents = this.minHeap.toArray();\r\n return heapEvents.some((event) => event.type == type && event.hrid == hrid);\r\n }\r\n\r\n clear() {\r\n this.minHeap = new Heap((a, b) => a.time - b.time);\r\n }\r\n\r\n clearEventsForUnit(unit) {\r\n this.clearMatching((event) => event.source == unit || event.target == unit);\r\n }\r\n\r\n clearEventsOfType(type) {\r\n this.clearMatching((event) => event.type == type);\r\n }\r\n\r\n clearMatching(fn) {\r\n let cleared = false;\r\n let heapEvents = this.minHeap.toArray();\r\n\r\n for (const event of heapEvents) {\r\n if (fn(event)) {\r\n this.minHeap.remove(event);\r\n cleared = true;\r\n }\r\n }\r\n return cleared;\r\n }\r\n\r\n getMatching(fn) {\r\n let heapEvents = this.minHeap.toArray(); \r\n \r\n for (const event of heapEvents) {\r\n if (fn(event)) {\r\n return event; \r\n }\r\n }\r\n \r\n return null; \r\n }\r\n}\r\n\r\nexport default EventQueue;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass FuryExpirationEvent extends CombatEvent {\r\n static type = \"furyExpiration\";\r\n\r\n constructor(time, furyAmount, source) {\r\n super(FuryExpirationEvent.type, time);\r\n \r\n this.furyAmount = furyAmount;\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default FuryExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass PlayerRespawnEvent extends CombatEvent {\r\n static type = \"playerRespawn\";\r\n\r\n constructor(time, hrid) {\r\n super(PlayerRespawnEvent.type, time);\r\n this.hrid = hrid;\r\n }\r\n}\r\n\r\nexport default PlayerRespawnEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass RegenTickEvent extends CombatEvent {\r\n static type = \"regenTick\";\r\n\r\n constructor(time) {\r\n super(RegenTickEvent.type, time);\r\n }\r\n}\r\n\r\nexport default RegenTickEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass SilenceExpirationEvent extends CombatEvent {\r\n static type = \"silenceExpiration\";\r\n\r\n constructor(time, source) {\r\n super(SilenceExpirationEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default SilenceExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass StunExpirationEvent extends CombatEvent {\r\n static type = \"stunExpiration\";\r\n\r\n constructor(time, source) {\r\n super(StunExpirationEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default StunExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass WeakenExpirationEvent extends CombatEvent {\r\n static type = \"weakenExpiration\";\r\n static maxWeakenStacks = 5;\r\n\r\n constructor(time, weakenAmount, source) {\r\n super(WeakenExpirationEvent.type, time);\r\n this.weakenAmount = Math.min(\r\n weakenAmount + 1,\r\n WeakenExpirationEvent.maxWeakenStacks\r\n );\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default WeakenExpirationEvent;","import Buff from \"./buff\";\r\nimport houseRoomDetailMap from \"./data/houseRoomDetailMap.json\";\r\n\r\nclass HouseRoom {\r\n constructor(hrid, level) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameHouseRoom = houseRoomDetailMap[this.hrid];\r\n if (!gameHouseRoom) {\r\n throw new Error(\"No house room found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.buffs = [];\r\n if (gameHouseRoom.actionBuffs) {\r\n for (const actionBuff of gameHouseRoom.actionBuffs) {\r\n let buff = new Buff(actionBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n if (gameHouseRoom.globalBuffs) {\r\n for (const globalBuff of gameHouseRoom.globalBuffs) {\r\n let buff = new Buff(globalBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default HouseRoom;","import Ability from \"./ability\";\r\nimport CombatUnit from \"./combatUnit\";\r\nimport combatMonsterDetailMap from \"./data/combatMonsterDetailMap.json\";\r\nimport Drops from \"./drops\";\r\n\r\nclass Monster extends CombatUnit {\r\n\r\n difficultyTier = 0;\r\n\r\n constructor(hrid, difficultyTier = 0) {\r\n super();\r\n\r\n this.isPlayer = false;\r\n this.hrid = hrid;\r\n this.difficultyTier = difficultyTier;\r\n\r\n let gameMonster = combatMonsterDetailMap[this.hrid];\r\n if (!gameMonster) {\r\n throw new Error(\"No monster found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.enrageTime = gameMonster.enrageTime;\r\n\r\n for (let i = 0; i < gameMonster.abilities.length; i++) {\r\n if (gameMonster.abilities[i].minDifficultyTier > this.difficultyTier) {\r\n continue;\r\n }\r\n this.abilities[i] = new Ability(gameMonster.abilities[i].abilityHrid, gameMonster.abilities[i].level);\r\n }\r\n if(gameMonster.dropTable)\r\n for (let i = 0; i < gameMonster.dropTable.length; i++) {\r\n this.dropTable[i] = new Drops(gameMonster.dropTable[i].itemHrid, gameMonster.dropTable[i].dropRate, gameMonster.dropTable[i].minCount, gameMonster.dropTable[i].maxCount, gameMonster.dropTable[i].difficultyTier);\r\n }\r\n for (let i = 0; i < gameMonster.rareDropTable.length; i++) {\r\n let dropTableItem = (gameMonster.dropTable && i < gameMonster.dropTable.length) ? gameMonster.dropTable[i] : null;\r\n let difficultyTier = dropTableItem?.difficultyTier ?? gameMonster.rareDropTable[i].minDifficultyTier;\r\n\r\n this.rareDropTable[i] = new Drops(gameMonster.rareDropTable[i].itemHrid, gameMonster.rareDropTable[i].dropRate, gameMonster.rareDropTable[i].minCount, difficultyTier);\r\n }\r\n }\r\n\r\n updateCombatDetails() {\r\n let gameMonster = combatMonsterDetailMap[this.hrid];\r\n\r\n let levelMultiplier = 1.0 + 0.25 * this.difficultyTier;\r\n let defLevelMultiplier = 1.0 + 0.15 * this.difficultyTier;\r\n let levelBonus = 20.0 * this.difficultyTier;\r\n\r\n this.staminaLevel = levelMultiplier * (gameMonster.combatDetails.staminaLevel + levelBonus);\r\n this.intelligenceLevel = levelMultiplier * (gameMonster.combatDetails.intelligenceLevel + levelBonus);\r\n this.attackLevel = levelMultiplier * (gameMonster.combatDetails.attackLevel + levelBonus);\r\n this.meleeLevel = levelMultiplier * (gameMonster.combatDetails.meleeLevel + levelBonus);\r\n this.defenseLevel = defLevelMultiplier * (gameMonster.combatDetails.defenseLevel + levelBonus);\r\n this.rangedLevel = levelMultiplier * (gameMonster.combatDetails.rangedLevel + levelBonus);\r\n this.magicLevel = levelMultiplier * (gameMonster.combatDetails.magicLevel + levelBonus);\r\n\r\n \r\n let expMultiplier = 1.0 + 0.5 * this.difficultyTier;\r\n let expBonus = 5.0 * this.difficultyTier;\r\n\r\n this.experience = expMultiplier * (gameMonster.experience + expBonus);\r\n\r\n this.combatDetails.combatStats.combatStyleHrid = gameMonster.combatDetails.combatStats.combatStyleHrids[0];\r\n\r\n for (const [key, value] of Object.entries(gameMonster.combatDetails.combatStats)) {\r\n this.combatDetails.combatStats[key] = value;\r\n }\r\n\r\n [\r\n \"stabAccuracy\",\r\n \"slashAccuracy\",\r\n \"smashAccuracy\",\r\n \"rangedAccuracy\",\r\n \"magicAccuracy\",\r\n \"stabDamage\",\r\n \"slashDamage\",\r\n \"smashDamage\",\r\n \"rangedDamage\",\r\n \"magicDamage\",\r\n \"defensiveDamage\",\r\n \"taskDamage\",\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"stabEvasion\",\r\n \"slashEvasion\",\r\n \"smashEvasion\",\r\n \"rangedEvasion\",\r\n \"magicEvasion\",\r\n \"armor\",\r\n \"waterResistance\",\r\n \"natureResistance\",\r\n \"fireResistance\",\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"combatDropRate\",\r\n \"combatRareFind\",\r\n \"combatDropQuantity\",\r\n \"combatExperience\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"abilityHaste\",\r\n \"tenacity\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"threat\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"foodHaste\",\r\n \"drinkConcentration\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"retaliation\"\r\n ].forEach((stat) => {\r\n if (gameMonster.combatDetails.combatStats[stat] == null) {\r\n this.combatDetails.combatStats[stat] = 0;\r\n }\r\n });\r\n\r\n if (this.combatDetails.combatStats.attackInterval == 0) {\r\n this.combatDetails.combatStats.attackInterval = gameMonster.combatDetails.attackInterval;\r\n }\r\n\r\n super.updateCombatDetails();\r\n }\r\n}\r\n\r\nexport default Monster;\r\n","import Ability from \"./ability\";\r\nimport CombatUnit from \"./combatUnit\";\r\nimport Consumable from \"./consumable\";\r\nimport Equipment from \"./equipment\";\r\nimport HouseRoom from \"./houseRoom\";\r\nimport Achievement from \"./achievement\";\r\n\r\nclass Player extends CombatUnit {\r\n equipment = {\r\n \"/equipment_types/head\": null,\r\n \"/equipment_types/body\": null,\r\n \"/equipment_types/legs\": null,\r\n \"/equipment_types/feet\": null,\r\n \"/equipment_types/hands\": null,\r\n \"/equipment_types/main_hand\": null,\r\n \"/equipment_types/two_hand\": null,\r\n \"/equipment_types/off_hand\": null,\r\n \"/equipment_types/pouch\": null,\r\n \"/equipment_types/back\": null,\r\n };\r\n\r\n constructor() {\r\n super();\r\n\r\n this.isPlayer = true;\r\n this.hrid = \"player\";\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let player = new Player();\r\n\r\n player.staminaLevel = dto.staminaLevel;\r\n player.intelligenceLevel = dto.intelligenceLevel;\r\n player.attackLevel = dto.attackLevel;\r\n player.meleeLevel = dto.meleeLevel;\r\n player.defenseLevel = dto.defenseLevel;\r\n player.rangedLevel = dto.rangedLevel;\r\n player.magicLevel = dto.magicLevel;\r\n\r\n player.hrid = dto.hrid;\r\n\r\n for (const [key, value] of Object.entries(dto.equipment)) {\r\n player.equipment[key] = value ? Equipment.createFromDTO(value) : null;\r\n }\r\n\r\n player.food = dto.food.map((food) => (food ? Consumable.createFromDTO(food) : null));\r\n player.drinks = dto.drinks.map((drink) => (drink ? Consumable.createFromDTO(drink) : null));\r\n player.abilities = dto.abilities.map((ability) => (ability ? Ability.createFromDTO(ability) : null));\r\n Object.entries(dto.houseRooms).forEach(houseRoom => {\r\n if (houseRoom[1] > 0) {\r\n player.houseRooms.push(new HouseRoom(houseRoom[0], houseRoom[1]))\r\n }\r\n });\r\n\r\n player.achievements = new Achievement(dto.achievements);\r\n\r\n player.debuffOnLevelGap = dto.debuffOnLevelGap;\r\n\r\n return player;\r\n }\r\n\r\n updateCombatDetails() {\r\n if (this.equipment[\"/equipment_types/main_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/main_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/main_hand\"].getPrimaryTraining();\r\n } else if (this.equipment[\"/equipment_types/two_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/two_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/two_hand\"].getPrimaryTraining();\r\n } else {\r\n this.combatDetails.combatStats.combatStyleHrid = \"/combat_styles/smash\";\r\n this.combatDetails.combatStats.damageType = \"/damage_types/physical\";\r\n this.combatDetails.combatStats.attackInterval = 3000000000;\r\n this.combatDetails.combatStats.primaryTraining = \"/skills/melee\";\r\n }\r\n\r\n if (this.equipment[\"/equipment_types/charm\"]) {\r\n this.combatDetails.combatStats.focusTraining = this.equipment[\"/equipment_types/charm\"].getFocusTraining();\r\n } else {\r\n this.combatDetails.combatStats.focusTraining = \"\";\r\n }\r\n\r\n [\r\n \"stabAccuracy\",\r\n \"slashAccuracy\",\r\n \"smashAccuracy\",\r\n \"rangedAccuracy\",\r\n \"magicAccuracy\",\r\n \"stabDamage\",\r\n \"slashDamage\",\r\n \"smashDamage\",\r\n \"rangedDamage\",\r\n \"magicDamage\",\r\n \"defensiveDamage\",\r\n \"taskDamage\",\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"stabEvasion\",\r\n \"slashEvasion\",\r\n \"smashEvasion\",\r\n \"rangedEvasion\",\r\n \"magicEvasion\",\r\n \"armor\",\r\n \"waterResistance\",\r\n \"natureResistance\",\r\n \"fireResistance\",\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"combatDropRate\",\r\n \"combatRareFind\",\r\n \"combatDropQuantity\",\r\n \"combatExperience\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"abilityHaste\",\r\n \"tenacity\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"threat\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"foodHaste\",\r\n \"drinkConcentration\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\",\r\n \"retaliation\"\r\n ].forEach((stat) => {\r\n this.combatDetails.combatStats[stat] = Object.values(this.equipment)\r\n .filter((equipment) => equipment != null)\r\n .map((equipment) => equipment.getCombatStat(stat))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n });\r\n\r\n if (this.equipment[\"/equipment_types/pouch\"]) {\r\n this.combatDetails.combatStats.foodSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"foodSlots\");\r\n this.combatDetails.combatStats.drinkSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"drinkSlots\");\r\n } else {\r\n this.combatDetails.combatStats.foodSlots = 1;\r\n this.combatDetails.combatStats.drinkSlots = 1;\r\n }\r\n\r\n super.updateCombatDetails();\r\n }\r\n}\r\n\r\nexport default Player;\r\n","import combatStyleDetailMap from \"./data/combatStyleDetailMap.json\"\r\n\r\nclass SimResult {\r\n constructor(zone, numberOfPlayers) {\r\n this.deaths = {};\r\n this.experienceGained = {};\r\n this.encounters = 0;\r\n this.attacks = {};\r\n this.consumablesUsed = {};\r\n this.hitpointsGained = {};\r\n this.manapointsGained = {};\r\n this.debuffOnLevelGap = {};\r\n this.dropRateMultiplier = {};\r\n this.rareFindMultiplier = {};\r\n this.combatDropQuantity = {};\r\n this.playerRanOutOfMana = {\r\n \"player1\": false,\r\n \"player2\": false,\r\n \"player3\": false,\r\n \"player4\": false,\r\n \"player5\": false\r\n };\r\n this.playerRanOutOfManaTime = {};\r\n this.manaUsed = {};\r\n this.timeSpentAlive = [];\r\n this.bossSpawns = [];\r\n this.hitpointsSpent = {};\r\n this.zoneName = zone.hrid;\r\n this.difficultyTier = zone.difficultyTier;\r\n this.isDungeon = false;\r\n this.dungeonsCompleted = 0;\r\n this.dungeonsFailed = 0;\r\n this.maxWaveReached = 0;\r\n this.numberOfPlayers = numberOfPlayers;\r\n this.maxEnrageStack = 0;\r\n\r\n this.wipeEvents = [];\r\n }\r\n\r\n addWipeEvent(logs, simulationTime, wave) {\r\n this.wipeEvents.push({\r\n simulationTime: simulationTime,\r\n logs: logs,\r\n wave: wave,\r\n timestamp: new Date().toISOString()\r\n });\r\n }\r\n \r\n addDeath(unit) {\r\n if (!this.deaths[unit.hrid]) {\r\n this.deaths[unit.hrid] = 0;\r\n }\r\n\r\n this.deaths[unit.hrid] += 1;\r\n }\r\n\r\n updateTimeSpentAlive(name, alive, time) {\r\n const i = this.timeSpentAlive.findIndex(e => e.name === name);\r\n if (alive) {\r\n if (i !== -1) {\r\n this.timeSpentAlive[i].alive = true;\r\n this.timeSpentAlive[i].spawnedAt = time;\r\n } else {\r\n this.timeSpentAlive.push({ name: name, timeSpentAlive: 0, spawnedAt: time, alive: true, count: 0 });\r\n }\r\n } else {\r\n const timeAlive = time - this.timeSpentAlive[i].spawnedAt;\r\n this.timeSpentAlive[i].alive = false;\r\n this.timeSpentAlive[i].timeSpentAlive += timeAlive;\r\n this.timeSpentAlive[i].count += 1;\r\n }\r\n }\r\n\r\n addExperienceGain(unit, experience) {\r\n if (!unit.isPlayer) {\r\n return;\r\n }\r\n\r\n if (!this.experienceGained[unit.hrid]) {\r\n this.experienceGained[unit.hrid] = {\r\n stamina: 0,\r\n intelligence: 0,\r\n attack: 0,\r\n melee: 0,\r\n defense: 0,\r\n ranged: 0,\r\n magic: 0,\r\n };\r\n }\r\n\r\n let experienceGainedRate = {\r\n \"stamina\": 0,\r\n \"intelligence\": 0,\r\n \"attack\": 0,\r\n \"melee\": 0,\r\n \"defense\": 0,\r\n \"ranged\": 0,\r\n \"magic\": 0,\r\n };\r\n\r\n const primaryTraining = unit.combatDetails.combatStats.primaryTraining;\r\n experienceGainedRate[primaryTraining.split(\"/\")[2]] = .3;\r\n\r\n const skillExpMap = combatStyleDetailMap[unit.combatDetails.combatStats.combatStyleHrid].skillExpMap;\r\n const skillExpMapLength = Object.keys(skillExpMap).length;\r\n\r\n const focusTraining = unit.combatDetails.combatStats.focusTraining;\r\n if (focusTraining && skillExpMap[focusTraining]) {\r\n experienceGainedRate[focusTraining.split(\"/\")[2]] += .7;\r\n } else {\r\n Object.keys(skillExpMap).forEach(skillHrid => {\r\n experienceGainedRate[skillHrid.split(\"/\")[2]] += .7 / skillExpMapLength;\r\n });\r\n }\r\n\r\n for (const [type, rate] of Object.entries(experienceGainedRate)) {\r\n if (rate <= 0) continue;\r\n\r\n const skillExperience = rate * (1 + unit.combatDetails.combatStats[type + \"Experience\"]);\r\n\r\n this.experienceGained[unit.hrid][type] += (\r\n experience\r\n * (1 + unit.combatDetails.combatStats.combatExperience)\r\n * skillExperience\r\n * (1 + unit.debuffOnLevelGap)\r\n\r\n );\r\n }\r\n }\r\n\r\n addEncounterEnd() {\r\n this.encounters++;\r\n }\r\n\r\n addAttack(source, target, ability, hit) {\r\n if (!this.attacks[source.hrid]) {\r\n this.attacks[source.hrid] = {};\r\n }\r\n if (!this.attacks[source.hrid][target.hrid]) {\r\n this.attacks[source.hrid][target.hrid] = {};\r\n }\r\n if (!this.attacks[source.hrid][target.hrid][ability]) {\r\n this.attacks[source.hrid][target.hrid][ability] = {};\r\n }\r\n\r\n if (!this.attacks[source.hrid][target.hrid][ability][hit]) {\r\n this.attacks[source.hrid][target.hrid][ability][hit] = 0;\r\n }\r\n\r\n this.attacks[source.hrid][target.hrid][ability][hit] += 1;\r\n }\r\n\r\n addConsumableUse(unit, consumable) {\r\n if (!this.consumablesUsed[unit.hrid]) {\r\n this.consumablesUsed[unit.hrid] = {};\r\n }\r\n if (!this.consumablesUsed[unit.hrid][consumable.hrid]) {\r\n this.consumablesUsed[unit.hrid][consumable.hrid] = 0;\r\n }\r\n\r\n this.consumablesUsed[unit.hrid][consumable.hrid] += 1;\r\n }\r\n\r\n addHitpointsGained(unit, source, amount) {\r\n if (!this.hitpointsGained[unit.hrid]) {\r\n this.hitpointsGained[unit.hrid] = {};\r\n }\r\n if (!this.hitpointsGained[unit.hrid][source]) {\r\n this.hitpointsGained[unit.hrid][source] = 0;\r\n }\r\n\r\n this.hitpointsGained[unit.hrid][source] += amount;\r\n }\r\n\r\n addManapointsGained(unit, source, amount) {\r\n if (!this.manapointsGained[unit.hrid]) {\r\n this.manapointsGained[unit.hrid] = {};\r\n }\r\n if (!this.manapointsGained[unit.hrid][source]) {\r\n this.manapointsGained[unit.hrid][source] = 0;\r\n }\r\n\r\n this.manapointsGained[unit.hrid][source] += amount;\r\n }\r\n\r\n setDropRateMultipliers(unit) {\r\n if (!this.dropRateMultiplier[unit.hrid]) {\r\n this.dropRateMultiplier[unit.hrid] = {};\r\n }\r\n this.dropRateMultiplier[unit.hrid] = 1 + unit.combatDetails.combatStats.combatDropRate;\r\n\r\n if (!this.rareFindMultiplier[unit.hrid]) {\r\n this.rareFindMultiplier[unit.hrid] = {};\r\n }\r\n this.rareFindMultiplier[unit.hrid] = 1 + unit.combatDetails.combatStats.combatRareFind;\r\n\r\n if (!this.combatDropQuantity[unit.hrid]) {\r\n this.combatDropQuantity[unit.hrid] = {};\r\n }\r\n this.combatDropQuantity[unit.hrid] = unit.combatDetails.combatStats.combatDropQuantity;\r\n\r\n if (!this.debuffOnLevelGap[unit.hrid]) {\r\n this.debuffOnLevelGap[unit.hrid] = {};\r\n }\r\n this.debuffOnLevelGap[unit.hrid] = unit.debuffOnLevelGap;\r\n }\r\n\r\n setManaUsed(unit) {\r\n this.manaUsed[unit.hrid] = {};\r\n for (let [key, value] of unit.abilityManaCosts.entries()) {\r\n this.manaUsed[unit.hrid][key] = value;\r\n }\r\n }\r\n\r\n addHitpointsSpent(unit, source, amount) {\r\n if (!this.hitpointsSpent[unit.hrid]) {\r\n this.hitpointsSpent[unit.hrid] = {};\r\n }\r\n if (!this.hitpointsSpent[unit.hrid][source]) {\r\n this.hitpointsSpent[unit.hrid][source] = 0;\r\n }\r\n\r\n this.hitpointsSpent[unit.hrid][source] += amount;\r\n }\r\n\r\n addRanOutOfManaCount(unit, isOutOfMana, time) {\r\n if (isOutOfMana) this.playerRanOutOfMana[unit.hrid] = true;\r\n\r\n if (!this.playerRanOutOfManaTime[unit.hrid]) {\r\n this.playerRanOutOfManaTime[unit.hrid] = {isOutOfMana: false, startTimeForOutOfMana:0, totalTimeForOutOfMana:0};\r\n }\r\n\r\n if (isOutOfMana) {\r\n if (!this.playerRanOutOfManaTime[unit.hrid].isOutOfMana) {\r\n this.playerRanOutOfManaTime[unit.hrid].isOutOfMana = true;\r\n this.playerRanOutOfManaTime[unit.hrid].startTimeForOutOfMana = time;\r\n }\r\n } else {\r\n if (this.playerRanOutOfManaTime[unit.hrid].isOutOfMana) {\r\n this.playerRanOutOfManaTime[unit.hrid].isOutOfMana = false;\r\n this.playerRanOutOfManaTime[unit.hrid].totalTimeForOutOfMana += time - this.playerRanOutOfManaTime[unit.hrid].startTimeForOutOfMana;\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default SimResult;\r\n","import combatTriggerDependencyDetailMap from \"./data/combatTriggerDependencyDetailMap.json\";\r\n\r\nclass Trigger {\r\n constructor(dependencyHrid, conditionHrid, comparatorHrid, value = 0) {\r\n this.dependencyHrid = dependencyHrid;\r\n this.conditionHrid = conditionHrid;\r\n this.comparatorHrid = comparatorHrid;\r\n this.value = value;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let trigger = new Trigger(dto.dependencyHrid, dto.conditionHrid, dto.comparatorHrid, dto.value);\r\n\r\n return trigger;\r\n }\r\n\r\n isActive(source, target, friendlies, enemies, currentTime) {\r\n if (combatTriggerDependencyDetailMap[this.dependencyHrid].isSingleTarget) {\r\n return this.isActiveSingleTarget(source, target, currentTime);\r\n } else {\r\n return this.isActiveMultiTarget(friendlies, enemies, currentTime);\r\n }\r\n }\r\n\r\n isActiveSingleTarget(source, target, currentTime) {\r\n let dependencyValue;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/self\":\r\n dependencyValue = this.getDependencyValue(source, currentTime);\r\n break;\r\n case \"/combat_trigger_dependencies/targeted_enemy\":\r\n if (!target) {\r\n return false;\r\n }\r\n dependencyValue = this.getDependencyValue(target, currentTime);\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n isActiveMultiTarget(friendlies, enemies, currentTime) {\r\n let dependency;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/all_allies\":\r\n dependency = friendlies;\r\n break;\r\n case \"/combat_trigger_dependencies/all_enemies\":\r\n if (!enemies) {\r\n return false;\r\n }\r\n dependency = enemies;\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n let dependencyValue;\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/number_of_active_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints > 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/number_of_dead_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints <= 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/lowest_hp_percentage\":\r\n dependencyValue = dependency.reduce((prev, curr) => {\r\n let currentHpPercentage = curr.combatDetails.currentHitpoints / curr.combatDetails.maxHitpoints;\r\n return currentHpPercentage < prev ? currentHpPercentage : prev;\r\n }, 2) * 100;\r\n break;\r\n default:\r\n dependencyValue = dependency\r\n .map((unit) => this.getDependencyValue(unit, currentTime))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n break;\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n getDependencyValue(source, currentTime) {\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/berserk\":\r\n case \"/combat_trigger_conditions/frenzy\":\r\n case \"/combat_trigger_conditions/precision\":\r\n case \"/combat_trigger_conditions/vampirism\":\r\n case \"/combat_trigger_conditions/attack_coffee\":\r\n case \"/combat_trigger_conditions/defense_coffee\":\r\n case \"/combat_trigger_conditions/lucky_coffee\":\r\n case \"/combat_trigger_conditions/magic_coffee\":\r\n case \"/combat_trigger_conditions/melee_coffee\":\r\n case \"/combat_trigger_conditions/ranged_coffee\":\r\n case \"/combat_trigger_conditions/swiftness_coffee\":\r\n case \"/combat_trigger_conditions/wisdom_coffee\":\r\n case \"/combat_trigger_conditions/ice_spear\":\r\n case \"/combat_trigger_conditions/puncture\":\r\n case \"/combat_trigger_conditions/frost_surge\":\r\n case \"/combat_trigger_conditions/elusiveness\":\r\n case \"/combat_trigger_conditions/channeling_coffee\":\r\n case \"/combat_trigger_conditions/fierce_aura\":\r\n case \"/combat_trigger_conditions/invincible_armor\":\r\n case \"/combat_trigger_conditions/invincible_fire_resistance\":\r\n case \"/combat_trigger_conditions/invincible_nature_resistance\":\r\n case \"/combat_trigger_conditions/invincible_water_resistance\":\r\n case \"/combat_trigger_conditions/provoke\":\r\n case \"/combat_trigger_conditions/taunt\":\r\n case \"/combat_trigger_conditions/crippling_slash\":\r\n case \"/combat_trigger_conditions/mana_spring\":\r\n case \"/combat_trigger_conditions/retribution\":\r\n case \"/combat_trigger_conditions/fracturing_impact\":\r\n case \"/combat_trigger_conditions/maim\":\r\n case \"/combat_trigger_conditions/curse\":\r\n case \"/combat_trigger_conditions/weaken\":\r\n let buffHrid = \"/buff_uniques\";\r\n buffHrid += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n return source.combatBuffs[buffHrid];\r\n case \"/combat_trigger_conditions/critical_aura\":\r\n case \"/combat_trigger_conditions/critical_coffee\":\r\n case \"/combat_trigger_conditions/intelligence_coffee\":\r\n case \"/combat_trigger_conditions/stamina_coffee\":\r\n case \"/combat_trigger_conditions/elemental_affinity\":\r\n case \"/combat_trigger_conditions/fury\":\r\n case \"/combat_trigger_conditions/guardian_aura\":\r\n case \"/combat_trigger_conditions/insanity\":\r\n case \"/combat_trigger_conditions/spike_shell\":\r\n case \"/combat_trigger_conditions/toxic_pollen\":\r\n case \"/combat_trigger_conditions/invincible\":\r\n case \"/combat_trigger_conditions/mystic_aura\":\r\n case \"/combat_trigger_conditions/pestilent_shot\":\r\n case \"/combat_trigger_conditions/smoke_burst\":\r\n case \"/combat_trigger_conditions/speed_aura\":\r\n case \"/combat_trigger_conditions/toughness\":\r\n case \"/combat_trigger_conditions/enrage\":\r\n let buffPrefix = \"/buff_uniques\";\r\n buffPrefix += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n let buffs = Object.keys(source.combatBuffs).filter(buff => buff.startsWith(buffPrefix));\r\n return source.combatBuffs[buffs?.[0]];\r\n case \"/combat_trigger_conditions/current_hp\":\r\n return source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/current_mp\":\r\n return source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/missing_hp\":\r\n return source.combatDetails.maxHitpoints - source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/missing_mp\":\r\n return source.combatDetails.maxManapoints - source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/stun_status\":\r\n // Replicate the game's behaviour of \"stun status active\" triggers activating\r\n // immediately after the stun has worn off\r\n return source.isStunned || source.stunExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/blind_status\":\r\n return source.isBlinded || source.blindExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/silence_status\":\r\n return source.isSilenced || source.silenceExpireTime == currentTime;\r\n default:\r\n throw new Error(\"Unknown conditionHrid in trigger: \" + this.conditionHrid);\r\n }\r\n }\r\n\r\n compareValue(dependencyValue) {\r\n switch (this.comparatorHrid) {\r\n case \"/combat_trigger_comparators/greater_than_equal\":\r\n return dependencyValue >= this.value;\r\n case \"/combat_trigger_comparators/less_than_equal\":\r\n return dependencyValue <= this.value;\r\n case \"/combat_trigger_comparators/is_active\":\r\n return !!dependencyValue;\r\n case \"/combat_trigger_comparators/is_inactive\":\r\n return !dependencyValue;\r\n default:\r\n throw new Error(\"Unknown comparatorHrid in trigger: \" + this.comparatorHrid);\r\n }\r\n }\r\n}\r\n\r\nexport default Trigger;\r\n","import actionDetailMap from \"./data/actionDetailMap.json\";\r\nimport Monster from \"./monster\";\r\n\r\nclass Zone {\r\n constructor(hrid, difficultyTier) {\r\n this.hrid = hrid;\r\n this.difficultyTier = difficultyTier;\r\n\r\n let gameZone = actionDetailMap[this.hrid];\r\n this.monsterSpawnInfo = gameZone.combatZoneInfo.fightInfo;\r\n this.dungeonSpawnInfo = gameZone.combatZoneInfo.dungeonInfo;\r\n this.encountersKilled = 1;\r\n this.monsterSpawnInfo.battlesPerBoss = 10;\r\n this.buffs = gameZone.buffs;\r\n this.isDungeon = gameZone.combatZoneInfo.isDungeon;\r\n this.dungeonsCompleted = 0;\r\n this.dungeonsFailed = 0;\r\n this.finalWave = false;\r\n }\r\n\r\n getRandomEncounter() {\r\n\r\n if (this.monsterSpawnInfo.bossSpawns && this.encountersKilled == this.monsterSpawnInfo.battlesPerBoss) {\r\n this.encountersKilled = 1;\r\n return this.monsterSpawnInfo.bossSpawns.map((monster) => new Monster(monster.combatMonsterHrid, monster.difficultyTier + this.difficultyTier));\r\n }\r\n\r\n let totalWeight = this.monsterSpawnInfo.randomSpawnInfo.spawns.reduce((prev, cur) => prev + cur.rate, 0);\r\n\r\n let encounterHrids = [];\r\n let totalStrength = 0;\r\n\r\n outer: for (let i = 0; i < this.monsterSpawnInfo.randomSpawnInfo.maxSpawnCount; i++) {\r\n let randomWeight = totalWeight * Math.random();\r\n let cumulativeWeight = 0;\r\n\r\n for (const spawn of this.monsterSpawnInfo.randomSpawnInfo.spawns) {\r\n cumulativeWeight += spawn.rate;\r\n if (randomWeight <= cumulativeWeight) {\r\n totalStrength += spawn.strength;\r\n\r\n if (totalStrength <= this.monsterSpawnInfo.randomSpawnInfo.maxTotalStrength) {\r\n encounterHrids.push({ 'hrid': spawn.combatMonsterHrid, 'difficultyTier': spawn.difficultyTier});\r\n\r\n } else {\r\n break outer;\r\n }\r\n break;\r\n }\r\n }\r\n }\r\n this.encountersKilled++;\r\n return encounterHrids.map((hrid) => new Monster(hrid.hrid, hrid.difficultyTier + this.difficultyTier));\r\n }\r\n\r\n failWave() {\r\n this.dungeonsFailed++;\r\n this.encountersKilled = 1;\r\n }\r\n\r\n getNextWave() {\r\n if (this.encountersKilled > this.dungeonSpawnInfo.maxWaves) {\r\n this.dungeonsCompleted++;\r\n this.encountersKilled = 1;\r\n }\r\n // console.log(\"Wave #\" + this.encountersKilled);\r\n if (this.dungeonSpawnInfo.fixedSpawnsMap.hasOwnProperty(this.encountersKilled.toString())) {\r\n let currentMonsters = this.dungeonSpawnInfo.fixedSpawnsMap[(this.encountersKilled).toString()];\r\n this.encountersKilled++;\r\n return currentMonsters.map((monster) => new Monster(monster.combatMonsterHrid, monster.difficultyTier + this.difficultyTier));\r\n } else {\r\n let monsterSpawns = {};\r\n const waveKeys = Object.keys(this.dungeonSpawnInfo.randomSpawnInfoMap).map(Number).sort((a, b) => a - b);\r\n if (this.encountersKilled > waveKeys[waveKeys.length - 1]) {\r\n monsterSpawns = this.dungeonSpawnInfo.randomSpawnInfoMap[waveKeys[waveKeys.length - 1]];\r\n } else {\r\n for (let i = 0; i < waveKeys.length - 1; i++) {\r\n if (this.encountersKilled >= waveKeys[i] && this.encountersKilled <= waveKeys[i + 1]) {\r\n monsterSpawns = this.dungeonSpawnInfo.randomSpawnInfoMap[waveKeys[i]];\r\n break;\r\n }\r\n }\r\n }\r\n let totalWeight = monsterSpawns.spawns.reduce((prev, cur) => prev + cur.rate, 0);\r\n\r\n let encounterHrids = [];\r\n let totalStrength = 0;\r\n\r\n outer: for (let i = 0; i < monsterSpawns.maxSpawnCount; i++) {\r\n let randomWeight = totalWeight * Math.random();\r\n let cumulativeWeight = 0;\r\n\r\n for (const spawn of monsterSpawns.spawns) {\r\n cumulativeWeight += spawn.rate;\r\n if (randomWeight <= cumulativeWeight) {\r\n totalStrength += spawn.strength;\r\n\r\n if (totalStrength <= monsterSpawns.maxTotalStrength) {\r\n encounterHrids.push({ 'hrid': spawn.combatMonsterHrid, 'difficultyTier': spawn.difficultyTier});\r\n\r\n } else {\r\n break outer;\r\n }\r\n break;\r\n }\r\n }\r\n }\r\n this.encountersKilled++;\r\n return encounterHrids.map((hrid) => new Monster(hrid.hrid, hrid.difficultyTier + this.difficultyTier));\r\n }\r\n }\r\n}\r\n\r\nexport default Zone;\r\n","import CombatSimulator from \"./combatsimulator/combatSimulator\";\r\nimport Player from \"./combatsimulator/player\";\r\nimport Zone from \"./combatsimulator/zone\";\r\n\r\n\r\nonmessage = async function (event) {\r\n switch (event.data.type) {\r\n case \"start_simulation\":\r\n let extraBuffs = [];\r\n if (event.data.extra.mooPass) {\r\n const mooPassBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/experience_moo_pass_buff\",\r\n \"typeHrid\": \"/buff_types/wisdom\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0.05,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": 0\r\n };\r\n extraBuffs.push(mooPassBuff);\r\n }\r\n if (event.data.extra.comExp > 0) {\r\n const comExpBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/experience_community_buff\",\r\n \"typeHrid\": \"/buff_types/wisdom\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0.005 * (event.data.extra.comExp - 1) + 0.2,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": 0\r\n };\r\n extraBuffs.push(comExpBuff);\r\n }\r\n if (event.data.extra.comDrop > 0) {\r\n const comDropBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/combat_community_buff\",\r\n \"typeHrid\": \"/buff_types/combat_drop_quantity\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0.005 * (event.data.extra.comDrop - 1) + 0.2,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": 0\r\n };\r\n extraBuffs.push(comDropBuff);\r\n }\r\n\r\n let playersData = event.data.players;\r\n let players = [];\r\n let zone = new Zone(event.data.zone.zoneHrid, event.data.zone.difficultyTier);\r\n for (let i = 0; i < playersData.length; i++) {\r\n let currentPlayer = Player.createFromDTO(structuredClone(playersData[i]));\r\n currentPlayer.zoneBuffs = zone.buffs;\r\n currentPlayer.extraBuffs = extraBuffs;\r\n players.push(currentPlayer);\r\n }\r\n let simulationTimeLimit = event.data.simulationTimeLimit;\r\n let combatSimulator = new CombatSimulator(players, zone);\r\n combatSimulator.addEventListener(\"progress\", (event) => {\r\n this.postMessage({ type: \"simulation_progress\", progress: event.detail.progress, zone: event.detail.zone, difficultyTier: event.detail.difficultyTier });\r\n });\r\n\r\n try {\r\n let simResult = await combatSimulator.simulate(simulationTimeLimit);\r\n this.postMessage({ type: \"simulation_result\", simResult: simResult });\r\n } catch (e) {\r\n console.log(e);\r\n this.postMessage({ type: \"simulation_error\", error: e });\r\n }\r\n break;\r\n }\r\n};\r\n"],"names":[],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"src_worker_js.bundle.js","mappings":";;;;;;;;;;;;;;;;AAA0B;AACkC;AAC5B;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,wDAAgB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,6CAAI;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;ACxNG;AACkD;AACR;AACpE;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,gEAAwB;AAChE;AACA,0CAA0C,4DAAoB;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,WAAW;;;;;;;;;;;;;;AC1B1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,IAAI,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACd4B;AACO;AACQ;AACU;AAChB;AACM;AACF;AACF;AACd;AACgB;AACR;AACU;AACE;AACI;AACJ;AACE;AACJ;AACR;AACnB;AAC2B;AACF;AAC7B;AACA;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,0DAAU;AACxC,6BAA6B,mDAAS;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,kBAAkB;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,aAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,KAAK,MAAM,WAAW,OAAO,YAAY;AAChE,wBAAwB,YAAY,KAAK,YAAY;AACrD,yBAAyB,cAAc,IAAI,YAAY;AACvD,4BAA4B,0BAA0B,OAAO,IAAI,UAAU,GAAG,MAAM,eAAe;AACnG;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA,mCAAmC,UAAU;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA,mCAAmC,UAAU;AAC7C;AACA;AACA;AACA;AACA;AACA,gBAAgB,yBAAyB;AACzC;AACA;AACA,wBAAwB,WAAW;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,gEAAgB;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,iBAAiB;AACjB;AACA;AACA;AACA;AACA,2BAA2B,0CAA0C;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0CAA0C;AAC1E;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA,wBAAwB,yBAAyB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,mDAAS;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,gEAAgB;AACjC;AACA;AACA,iBAAiB,kEAAkB;AACnC;AACA;AACA,iBAAiB,iEAAiB;AAClC;AACA;AACA,iBAAiB,+DAAe;AAChC;AACA;AACA,iBAAiB,mEAAmB;AACpC;AACA;AACA,iBAAiB,mEAAmB;AACpC;AACA;AACA,iBAAiB,wEAAwB;AACzC;AACA;AACA,iBAAiB,+DAAc;AAC/B;AACA;AACA,iBAAiB,oEAAmB;AACpC;AACA;AACA,iBAAiB,qEAAoB;AACrC;AACA;AACA,iBAAiB,uEAAsB;AACvC;AACA;AACA,iBAAiB,qEAAoB;AACrC;AACA;AACA,iBAAiB,sEAAqB;AACtC;AACA;AACA,iBAAiB,oEAAmB;AACpC;AACA;AACA,iBAAiB,gEAAe;AAChC;AACA;AACA,iBAAiB,oEAAmB;AACpC;AACA;AACA,iBAAiB,mEAAkB;AACnC;AACA;AACA;AACA,iBAAiB,kEAAkB;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,yBAAyB;AACjD,mCAAmC;AACnC;AACA;AACA;AACA;AACA,iCAAiC,+DAAc;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,yBAAyB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,0CAA0C,gEAAe;AACzD,kCAAkC,gEAAe;AACjD;AACA;AACA;AACA,0CAA0C,oEAAmB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,yBAAyB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,wDAAe;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6FAA6F,qEAAoB;AACjH;AACA;AACA,uEAAuE,qEAAoB;AAC3F;AACA,+CAA+C,qEAAoB;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4FAA4F,oEAAmB;AAC/G,uEAAuE,oEAAmB;AAC1F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,oEAAmB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8FAA8F,sEAAqB;AACnH;AACA;AACA;AACA,uEAAuE,sEAAqB;AAC5F,gDAAgD,sEAAqB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,+DAAe;AAC7D;AACA,wCAAwC,iEAAiB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4GAA4G,kEAAkB;AAC9H;AACA,iDAAiD,kEAAkB;AACnE;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,gEAAgB;AAC3D;AACA,cAAc;AACd,kDAAkD,+DAAe;AACjE,kDAAkD,oEAAmB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,oEAAmB,uBAAuB,+DAAe;AAC3H;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,wDAAe;AACpC;AACA;AACA,UAAU;AACV,qBAAqB,wDAAe;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD,oEAAmB;AACzE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sCAAsC,+DAAe;AACrD;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,wDAAe;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,wDAAe;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,mEAAkB;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,mEAAmB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,wDAAe;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,mEAAmB;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,mEAAkB;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,+DAAc;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,kCAAkC,gEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,wDAAe;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,qCAAqC,kEAAkB;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,mEAAkB;AACnE;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV,0CAA0C,mEAAmB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+CAA+C,wEAAwB;AACvE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,mCAAmC,iDAAO;AAC1C;AACA;AACA;AACA,mCAAmC,iDAAO;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,uDAAuD,wEAAwB;AAC/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+CAA+C,wEAAwB;AACvE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC;AACpC;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,wDAAe;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,wDAAe;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2DAA2D,wEAAwB;AACnF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,mEAAmB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4EAA4E,+DAAe,uBAAuB,oEAAmB,uBAAuB,oEAAmB;AAC/K,kDAAkD,oEAAmB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA,2EAA2E,qEAAoB;AAC/F,+EAA+E,+DAAe;AAC9F;AACA;AACA;AACA,mDAAmD,qEAAoB;AACvE;AACA;AACA;AACA;AACA;AACA;AACA,2EAA2E,uEAAsB;AACjG,+EAA+E,oEAAmB;AAClG;AACA;AACA;AACA,qDAAqD,uEAAsB;AAC3E;AACA;AACA;AACA;AACA;AACA,iGAAiG,qEAAoB;AACrH;AACA;AACA,2EAA2E,qEAAoB;AAC/F;AACA,mDAAmD,qEAAoB;AACvE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kGAAkG,sEAAqB;AACvH;AACA;AACA;AACA,2EAA2E,sEAAqB;AAChG,oDAAoD,sEAAqB;AACzE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,wDAAe;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,wDAAe;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,wDAAe;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mEAAmE,kEAAkB;AACrF;AACA;AACA;AACA,+BAA+B,wDAAe;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,iDAAO;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,wDAAe;AACrC;AACA;AACA;AACA;AACA;AACA,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;;AC3jD/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wDAAwD;AACtF,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,mBAAmB;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;AC/gB1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;;;;;AC9VL;AAC4B;AACtB;AAChC;AACA;AACA;AACA;AACA;AACA,6BAA6B,qDAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;ACtF1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,KAAK,EAAC;;;;;;;;;;;;;;;;;ACXiC;AACmD;AACzG;AACA;AACA;AACA;AACA,uBAAuB,qDAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,iFAAoC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS,EAAC;;;;;;;;;;;;;;;;AC/Ce;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB;;;;;;;;;;;;;;;ACbM;AACxC;AACA,8BAA8B,oDAAW;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;;;ACZS;AACxC;AACA,iCAAiC,oDAAW;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,kBAAkB;;;;;;;;;;;;;;;ACZO;AACxC;AACA,mCAAmC,oDAAW;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,oBAAoB;;;;;;;;;;;;;;;ACZK;AACxC;AACA,uCAAuC,oDAAW;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,wBAAwB,EAAC;;;;;;;;;;;;;;;ACZxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,WAAW,EAAC;;;;;;;;;;;;;;;;ACPa;AACxC;AACA,+BAA+B,oDAAW;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,gBAAgB,EAAC;;;;;;;;;;;;;;;;ACVQ;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB,EAAC;;;;;;;;;;;;;;;;ACfK;AACxC;AACA,iCAAiC,oDAAW;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,kBAAkB,EAAC;;;;;;;;;;;;;;;;ACVM;AACxC;AACA,mCAAmC,oDAAW;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,oBAAoB;;;;;;;;;;;;;;;ACfK;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB,EAAC;;;;;;;;;;;;;;;;AClBK;AACxC;AACA,gCAAgC,oDAAW;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,iBAAiB,EAAC;;;;;;;;;;;;;;;;ACVO;AACxC;AACA,8BAA8B,oDAAW;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;;;ACbJ;AAC3B;AACA;AACA;AACA,2BAA2B,+CAAI;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,+CAAI;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;AChEc;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB;;;;;;;;;;;;;;;ACbM;AACxC;AACA,iCAAiC,oDAAW;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,kBAAkB,EAAC;;;;;;;;;;;;;;;;ACXM;AACxC;AACA,6BAA6B,oDAAW;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,cAAc,EAAC;;;;;;;;;;;;;;;;ACVU;AACxC;AACA,qCAAqC,oDAAW;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,sBAAsB;;;;;;;;;;;;;;;ACZG;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB;;;;;;;;;;;;;;;ACZM;AACxC;AACA,oCAAoC,oDAAW;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,qBAAqB;;;;;;;;;;;;;;;;AChBV;AACsC;AAChE;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,0DAAkB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS;;;;;;;;;;;;;;;;;;AC7BQ;AACM;AACkC;AAC5C;AAC5B;AACA,sBAAsB,mDAAU;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,8DAAsB;AAChD;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,kCAAkC;AAC1D;AACA;AACA;AACA,oCAAoC,gDAAO;AAC3C;AACA;AACA,wBAAwB,kCAAkC;AAC1D,oCAAoC,8CAAK;AACzC;AACA,wBAAwB,sCAAsC;AAC9D;AACA;AACA;AACA,wCAAwC,8CAAK;AAC7C;AACA;AACA;AACA;AACA,0BAA0B,8DAAsB;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;;;;AClJS;AACM;AACA;AACF;AACA;AACI;AACxC;AACA,qBAAqB,mDAAU;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,kDAAS;AACrD;AACA;AACA,qDAAqD,mDAAU;AAC/D,2DAA2D,mDAAU;AACrE,qEAAqE,gDAAO;AAC5E;AACA;AACA,2CAA2C,kDAAS;AACpD;AACA,SAAS;AACT;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,MAAM,EAAC;;;;;;;;;;;;;;;;ACvL6C;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd,2CAA2C,uEAAuE;AAClH;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,4DAAoB;AAChD;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD;AACtD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS,EAAC;;;;;;;;;;;;;;;;ACtPmE;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,wEAAgC;AAC5C;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;ACjLmC;AAC1B;AAChC;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,uDAAe;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yEAAyE,gDAAO;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,yDAAyD;AACxF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,wEAAwE;AACtH;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gDAAgD,gDAAO;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wDAAwD,gDAAO;AAC/D,UAAU;AACV;AACA;AACA;AACA;AACA,cAAc;AACd,gCAAgC,yBAAyB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,iCAAiC;AACpE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,wEAAwE;AAC1H;AACA,0BAA0B;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oDAAoD,gDAAO;AAC3D;AACA;AACA;AACA;AACA,iEAAe,IAAI,EAAC;;;;;;;;;;;;;;;ACjH4C;AAClB;AACJ;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,6DAAI;AAC/B,4BAA4B,wBAAwB;AACpD,oCAAoC,+DAAM;AAC1C;AACA;AACA;AACA;AACA;AACA,sCAAsC,wEAAe;AACrD;AACA,mCAAmC,oIAAoI;AACvK,aAAa;AACb;AACA;AACA;AACA,mCAAmC,iDAAiD;AACpF,cAAc;AACd;AACA,mCAAmC,oCAAoC;AACvE;AACA;AACA;AACA","sources":["webpack://mwicombatsimulator/./src/combatsimulator/ability.js","webpack://mwicombatsimulator/./src/combatsimulator/achievement.js","webpack://mwicombatsimulator/./src/combatsimulator/buff.js","webpack://mwicombatsimulator/./src/combatsimulator/combatSimulator.js","webpack://mwicombatsimulator/./src/combatsimulator/combatUnit.js","webpack://mwicombatsimulator/./src/combatsimulator/combatUtilities.js","webpack://mwicombatsimulator/./src/combatsimulator/consumable.js","webpack://mwicombatsimulator/./src/combatsimulator/drops.js","webpack://mwicombatsimulator/./src/combatsimulator/equipment.js","webpack://mwicombatsimulator/./src/combatsimulator/events/abilityCastEndEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/autoAttackEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/awaitCooldownEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/blindExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/checkBuffExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/combatEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/combatStartEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/consumableTickEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/cooldownReadyEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/curseExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/damageOverTimeEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/enemyRespawnEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/enrageTickEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/eventQueue.js","webpack://mwicombatsimulator/./src/combatsimulator/events/furyExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/playerRespawnEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/regenTickEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/silenceExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/stunExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/weakenExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/houseRoom.js","webpack://mwicombatsimulator/./src/combatsimulator/monster.js","webpack://mwicombatsimulator/./src/combatsimulator/player.js","webpack://mwicombatsimulator/./src/combatsimulator/simResult.js","webpack://mwicombatsimulator/./src/combatsimulator/trigger.js","webpack://mwicombatsimulator/./src/combatsimulator/zone.js","webpack://mwicombatsimulator/./src/worker.js"],"sourcesContent":["import Buff from \"./buff\";\r\nimport abilityDetailMap from \"./data/abilityDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nconst abilityFromCombatStat = {\r\n \"blaze\":\r\n {\r\n \"hrid\": \"/abilities/blaze\",\r\n \"name\": \"Blaze\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"allEnemies\",\r\n \"effectType\": \"/ability_effect_types/damage\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"/damage_types/fire\",\r\n \"baseDamageFlat\": 0,\r\n \"baseDamageFlatLevelBonus\": 0.0,\r\n \"baseDamageRatio\": 0.3,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/number_of_active_units\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n },\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/current_hp\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n }\r\n ],\r\n },\r\n \"bloom\":\r\n {\r\n \"hrid\": \"/abilities/bloom\",\r\n \"name\": \"Bloom\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"lowestHpAlly\",\r\n \"effectType\": \"/ability_effect_types/heal\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"\",\r\n \"baseDamageFlat\": 10,\r\n \"baseDamageFlatLevelBonus\": 0,\r\n \"baseDamageRatio\": 0.15,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_allies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/lowest_hp_percentage\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/less_than_equal\",\r\n \"value\": 100\r\n }\r\n ],\r\n }\r\n}\r\n\r\nclass Ability {\r\n constructor(hrid, level = 1, triggers = null) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameAbility = abilityDetailMap[hrid];\r\n if (!gameAbility) {\r\n gameAbility = abilityFromCombatStat[hrid];\r\n }\r\n if (!gameAbility) {\r\n throw new Error(\"No ability found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.manaCost = gameAbility.manaCost;\r\n this.cooldownDuration = gameAbility.cooldownDuration;\r\n this.castDuration = gameAbility.castDuration;\r\n this.isSpecialAbility = gameAbility.isSpecialAbility;\r\n\r\n this.abilityEffects = [];\r\n\r\n for (const effect of gameAbility.abilityEffects) {\r\n let abilityEffect = {\r\n targetType: effect.targetType,\r\n effectType: effect.effectType,\r\n combatStyleHrid: effect.combatStyleHrid,\r\n damageType: effect.damageType,\r\n damageFlat: effect.baseDamageFlat + (this.level - 1) * effect.baseDamageFlatLevelBonus,\r\n damageRatio: effect.baseDamageRatio + (this.level - 1) * effect.baseDamageRatioLevelBonus,\r\n bonusAccuracyRatio: effect.bonusAccuracyRatio + (this.level - 1) * effect.bonusAccuracyRatioLevelBonus,\r\n damageOverTimeRatio: effect.damageOverTimeRatio,\r\n damageOverTimeDuration: effect.damageOverTimeDuration,\r\n armorDamageRatio: effect.armorDamageRatio + (this.level - 1) * effect.armorDamageRatioLevelBonus,\r\n hpDrainRatio: effect.hpDrainRatio,\r\n pierceChance: effect.pierceChance,\r\n blindChance: effect.blindChance,\r\n blindDuration: effect.blindDuration,\r\n silenceChance: effect.silenceChance,\r\n silenceDuration: effect.silenceDuration,\r\n stunChance: effect.stunChance,\r\n stunDuration: effect.stunDuration,\r\n spendHpRatio: effect.spendHpRatio,\r\n buffs: null,\r\n };\r\n if (effect.buffs) {\r\n abilityEffect.buffs = [];\r\n for (const buff of effect.buffs) {\r\n abilityEffect.buffs.push(new Buff(buff, this.level));\r\n }\r\n }\r\n this.abilityEffects.push(abilityEffect);\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameAbility.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let ability = new Ability(dto.hrid, dto.level, triggers);\r\n\r\n return ability;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n\r\n if (source.isSilenced) {\r\n return false;\r\n }\r\n\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = this.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Ability;\r\n","import Buff from \"./buff\";\r\nimport achievementTierDetailMap from \"./data/achievementTierDetailMap.json\";\r\nimport achievementDetailMap from \"./data/achievementDetailMap.json\";\r\n\r\nclass Achievement {\r\n constructor(achievements) {\r\n this.achievements = achievements;\r\n this.buffs = [];\r\n\r\n for(const tier of Object.values(achievementTierDetailMap)) {\r\n let isGetAll = true;\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid)\r\n for(const achievement of Object.values(detailMap)) {\r\n if(!this.achievements[achievement.hrid] || this.achievements[achievement.hrid] == false) {\r\n isGetAll = false;\r\n break;\r\n }\r\n }\r\n if(isGetAll) {\r\n let buff = new Buff(tier.buff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default Achievement;","class Buff {\r\n startTime;\r\n\r\n constructor(buff, level = 1) {\r\n this.uniqueHrid = buff.uniqueHrid;\r\n this.typeHrid = buff.typeHrid;\r\n this.ratioBoost = buff.ratioBoost + (level - 1) * buff.ratioBoostLevelBonus;\r\n this.flatBoost = buff.flatBoost + (level - 1) * buff.flatBoostLevelBonus;\r\n this.duration = buff.duration;\r\n this.multiplierForSkillHrid = buff.multiplierForSkillHrid ?? \"\";\r\n this.multiplierPerSkillLevel = buff.multiplierPerSkillLevel ?? 0;\r\n }\r\n}\r\n\r\nexport default Buff;\r\n","import CombatUtilities from \"./combatUtilities\";\r\nimport AutoAttackEvent from \"./events/autoAttackEvent\";\r\nimport DamageOverTimeEvent from \"./events/damageOverTimeEvent\";\r\nimport CheckBuffExpirationEvent from \"./events/checkBuffExpirationEvent\";\r\nimport CombatStartEvent from \"./events/combatStartEvent\";\r\nimport ConsumableTickEvent from \"./events/consumableTickEvent\";\r\nimport CooldownReadyEvent from \"./events/cooldownReadyEvent\";\r\nimport EnemyRespawnEvent from \"./events/enemyRespawnEvent\";\r\nimport EventQueue from \"./events/eventQueue\";\r\nimport PlayerRespawnEvent from \"./events/playerRespawnEvent\";\r\nimport RegenTickEvent from \"./events/regenTickEvent\";\r\nimport StunExpirationEvent from \"./events/stunExpirationEvent\";\r\nimport BlindExpirationEvent from \"./events/blindExpirationEvent\";\r\nimport SilenceExpirationEvent from \"./events/silenceExpirationEvent\";\r\nimport CurseExpirationEvent from \"./events/curseExpirationEvent\";\r\nimport WeakenExpirationEvent from \"./events/weakenExpirationEvent\";\r\nimport FuryExpirationEvent from \"./events/furyExpirationEvent\";\r\nimport EnrageTickEvent from \"./events/enrageTickEvent\";\r\nimport SimResult from \"./simResult\";\r\nimport AbilityCastEndEvent from \"./events/abilityCastEndEvent\";\r\nimport AwaitCooldownEvent from \"./events/awaitCooldownEvent\";\r\nimport Monster from \"./monster\";\r\nimport Ability from \"./ability\";\r\n\r\nconst ONE_SECOND = 1e9;\r\nconst HOT_TICK_INTERVAL = 5 * ONE_SECOND;\r\nconst DOT_TICK_INTERVAL = 3 * ONE_SECOND;\r\nconst REGEN_TICK_INTERVAL = 10 * ONE_SECOND;\r\nconst ENEMY_RESPAWN_INTERVAL = 3 * ONE_SECOND;\r\nconst PLAYER_RESPAWN_INTERVAL = 150 * ONE_SECOND;\r\nconst RESTART_INTERVAL = 15 * ONE_SECOND;\r\nconst ENRAGE_TICK_INTERVAL = 60 * ONE_SECOND;\r\n\r\nclass CombatSimulator extends EventTarget {\r\n constructor(players, zone) {\r\n super();\r\n this.players = players;\r\n this.zone = zone;\r\n this.eventQueue = new EventQueue();\r\n this.simResult = new SimResult(zone, players.length);\r\n this.allPlayersDead = false;\r\n\r\n this.wipeLogs = {\r\n buffer: new Array(200),\r\n index: 0,\r\n count: 0,\r\n maxSize: 200\r\n };\r\n }\r\n\r\n addToWipeLogs(logEntry) {\r\n const { buffer, maxSize } = this.wipeLogs;\r\n\r\n buffer[this.wipeLogs.index] = logEntry;\r\n this.wipeLogs.index = (this.wipeLogs.index + 1) % maxSize;\r\n this.wipeLogs.count = Math.min(this.wipeLogs.count + 1, maxSize);\r\n }\r\n\r\n logAndResetWipeLogs() {\r\n const logs = this.getOrderedWipeLogs();\r\n \r\n // console.log(\"===== 团灭日志 =====\");\r\n // console.log(`最后 ${logs.length} 条战斗日志:`);\r\n \r\n logs.forEach(log => {\r\n if (log.error) {\r\n console.log(log.error);\r\n return;\r\n }\r\n \r\n const time = (log.time / 1e9).toFixed(2);\r\n // console.log(\r\n // `[${time}s] [${log.source}] 用 [${log.ability}] ` +\r\n // `对 ${log.target} 造成 ${log.damage} 伤害,` +\r\n // `HP ${log.beforeHp} → ${log.afterHp}。` +\r\n // `队伍生命值:${log.playersHp.map(p => `${p.hrid}: ${p.current}/${p.max}`).join(\" | \")}`\r\n // );\r\n });\r\n\r\n this.wipeLogs.index = 0;\r\n this.wipeLogs.count = 0;\r\n // console.log(\"===== 团灭日志结束 =====\");\r\n }\r\n \r\n buildCombatLog(source, ability, target, damageDone) {\r\n try {\r\n const sourceHrid = source?.hrid || \"UNKNOWN_SOURCE\";\r\n const targetHrid = target?.hrid || \"UNKNOWN_TARGET\";\r\n \r\n const afterHp = target?.combatDetails?.currentHitpoints || 0;\r\n const beforeHp = Math.max(0, afterHp + damageDone);\r\n\r\n const playersHp = this.players.map(p => ({\r\n hrid: p.hrid || \"UNKNOWN_PLAYER\",\r\n current: p.combatDetails?.currentHitpoints ?? 0,\r\n max: p.combatDetails?.maxHitpoints ?? 0\r\n }));\r\n \r\n return {\r\n time: this.simulationTime,\r\n wave: (this.zone.encountersKilled - 1),\r\n source: sourceHrid,\r\n ability: ability,\r\n target: targetHrid,\r\n damage: damageDone,\r\n beforeHp: beforeHp,\r\n afterHp: afterHp,\r\n playersHp: playersHp,\r\n // enemiesHp: enemiesHp,\r\n isCrit: false,\r\n };\r\n } catch (e) {\r\n return {\r\n error: `[日志生成错误] ${e.message}`\r\n };\r\n }\r\n }\r\n\r\n generateCombatLog(source, ability, target, attackResult) {\r\n try {\r\n const sourceHrid = source?.hrid || \"UNKNOWN_SOURCE\";\r\n const targetHrid = target?.hrid || \"UNKNOWN_TARGET\";\r\n const damage = attackResult?.damageDone || 0;\r\n \r\n const afterHp = target?.combatDetails?.currentHitpoints || 0;\r\n const beforeHp = Math.max(0, afterHp + damage);\r\n\r\n const playersHp = this.players.map(p => ({\r\n hrid: p.hrid || \"UNKNOWN_PLAYER\",\r\n current: p.combatDetails?.currentHitpoints ?? 0,\r\n max: p.combatDetails?.maxHitpoints ?? 0\r\n }));\r\n \r\n return {\r\n time: this.simulationTime,\r\n wave: (this.zone.encountersKilled - 1),\r\n source: sourceHrid,\r\n ability: ability,\r\n target: targetHrid,\r\n damage: damage,\r\n beforeHp: beforeHp,\r\n afterHp: afterHp,\r\n playersHp: playersHp,\r\n // enemiesHp: enemiesHp,\r\n isCrit: attackResult?.isCrit || false,\r\n };\r\n } catch (e) {\r\n return {\r\n error: `[日志生成错误] ${e.message}`\r\n };\r\n }\r\n }\r\n \r\n getOrderedWipeLogs() {\r\n const { buffer, maxSize, count } = this.wipeLogs;\r\n const logs = [];\r\n \r\n for (let i = 0; i < count; i++) {\r\n const idx = (this.wipeLogs.index - count + maxSize + i) % maxSize;\r\n logs.push(buffer[idx]);\r\n }\r\n \r\n return logs;\r\n }\r\n\r\n saveWipeLogsToSimResult(wave) {\r\n const logs = this.getOrderedWipeLogs();\r\n this.simResult.addWipeEvent(logs, this.simulationTime, wave);\r\n }\r\n\r\n async simulate(simulationTimeLimit) {\r\n this.reset();\r\n\r\n let ticks = 0;\r\n\r\n let combatStartEvent = new CombatStartEvent(0);\r\n this.eventQueue.addEvent(combatStartEvent);\r\n\r\n while (this.simulationTime < simulationTimeLimit) {\r\n let nextEvent = this.eventQueue.getNextEvent();\r\n await this.processEvent(nextEvent);\r\n\r\n ticks++;\r\n if (ticks == 1000) {\r\n ticks = 0;\r\n let progressEvent = new CustomEvent(\"progress\", {\r\n detail: {\r\n zone: this.zone.hrid,\r\n difficultyTier: this.zone.difficultyTier,\r\n progress: Math.min(this.simulationTime / simulationTimeLimit, 1)\r\n },\r\n });\r\n this.dispatchEvent(progressEvent);\r\n }\r\n }\r\n\r\n // for (let i = 0; i < this.simResult.timeSpentAlive.length; i++) {\r\n // if (this.simResult.timeSpentAlive[i].alive == true) {\r\n // this.simResult.updateTimeSpentAlive(this.simResult.timeSpentAlive[i].name, false, simulationTimeLimit);\r\n // }\r\n // }\r\n\r\n this.simResult.isDungeon = this.zone.isDungeon;\r\n if (this.simResult.isDungeon) {\r\n console.log(\"Timeout now at wave #\" + (this.zone.encountersKilled - 1));\r\n\r\n this.simResult.dungeonsCompleted = this.zone.dungeonsCompleted;\r\n this.simResult.dungeonsFailed = this.zone.dungeonsFailed;\r\n if (this.simResult.dungeonsCompleted < 1) {\r\n this.simResult.maxWaveReached = 0;\r\n for (let i = 0; i <= this.zone.dungeonSpawnInfo.maxWaves; i++) {\r\n let waveName = \"#\" + i.toString();\r\n const idx = this.simResult.timeSpentAlive.findIndex(e => e.name === waveName);\r\n if (idx == -1 || this.simResult.timeSpentAlive[idx].count == 0) {\r\n break;\r\n }\r\n this.simResult.maxWaveReached = i;\r\n }\r\n } else {\r\n this.simResult.maxWaveReached = this.zone.dungeonSpawnInfo.maxWaves;\r\n }\r\n }\r\n this.simResult.simulatedTime = this.simulationTime;\r\n \r\n for (let i = 0; i < this.players.length; i++) {\r\n this.simResult.setDropRateMultipliers(this.players[i]);\r\n this.simResult.setManaUsed(this.players[i]);\r\n }\r\n\r\n if (this.zone.isDungeon) {\r\n Object.entries(this.zone.dungeonSpawnInfo.fixedSpawnsMap).forEach(([wave, monsters]) => {\r\n let waveName = \"#\" + wave.toString();\r\n monsters.forEach(monster => {\r\n waveName += ',' + monster.combatMonsterHrid;\r\n });\r\n this.simResult.bossSpawns.push(waveName);\r\n });\r\n\r\n }\r\n if (this.zone.monsterSpawnInfo.bossSpawns) {\r\n for (const boss of this.zone.monsterSpawnInfo.bossSpawns) {\r\n this.simResult.bossSpawns.push(boss.combatMonsterHrid);\r\n }\r\n }\r\n\r\n return this.simResult;\r\n }\r\n\r\n reset() {\r\n this.tempDungeonCount = 0;\r\n this.simulationTime = 0;\r\n this.eventQueue.clear();\r\n this.simResult = new SimResult(this.zone, this.players.length);\r\n }\r\n\r\n async processEvent(event) {\r\n this.simulationTime = event.time;\r\n\r\n // console.log(this.simulationTime / 1e9, event.type, event);\r\n\r\n switch (event.type) {\r\n case CombatStartEvent.type:\r\n this.processCombatStartEvent(event);\r\n break;\r\n case PlayerRespawnEvent.type:\r\n this.processPlayerRespawnEvent(event);\r\n break;\r\n case EnemyRespawnEvent.type:\r\n this.processEnemyRespawnEvent(event);\r\n break;\r\n case AutoAttackEvent.type:\r\n this.processAutoAttackEvent(event);\r\n break;\r\n case ConsumableTickEvent.type:\r\n this.processConsumableTickEvent(event);\r\n break;\r\n case DamageOverTimeEvent.type:\r\n this.processDamageOverTimeTickEvent(event);\r\n break;\r\n case CheckBuffExpirationEvent.type:\r\n this.processCheckBuffExpirationEvent(event);\r\n break;\r\n case RegenTickEvent.type:\r\n this.processRegenTickEvent(event);\r\n break;\r\n case StunExpirationEvent.type:\r\n this.processStunExpirationEvent(event);\r\n break;\r\n case BlindExpirationEvent.type:\r\n this.processBlindExpirationEvent(event);\r\n break;\r\n case SilenceExpirationEvent.type:\r\n this.processSilenceExpirationEvent(event);\r\n break;\r\n case CurseExpirationEvent.type:\r\n this.processCurseExpirationEvent(event);\r\n break;\r\n case WeakenExpirationEvent.type:\r\n this.processWeakenExpirationEvent(event);\r\n break;\r\n case FuryExpirationEvent.type:\r\n this.processFuryExpirationEvent(event);\r\n break;\r\n case EnrageTickEvent.type:\r\n this.processEnrageTickEvent(event);\r\n break;\r\n case AbilityCastEndEvent.type:\r\n this.tryUseAbility(event.source, event.ability);\r\n break;\r\n case AwaitCooldownEvent.type:\r\n // console.log(\"Await CD \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(event.source);\r\n break;\r\n case CooldownReadyEvent.type:\r\n // Only used to check triggers\r\n break;\r\n }\r\n\r\n this.checkTriggers();\r\n }\r\n\r\n processCombatStartEvent(event) {\r\n // console.log(\"Combat Start \" + (this.simulationTime / 1000000000));\r\n for (let i = 0; i < this.players.length; i++) {\r\n if (event.time == 0) { // First combat start event\r\n this.players[i].generatePermanentBuffs();\r\n }\r\n this.players[i].reset(this.simulationTime);\r\n }\r\n let regenTickEvent = new RegenTickEvent(this.simulationTime + REGEN_TICK_INTERVAL);\r\n this.eventQueue.addEvent(regenTickEvent);\r\n\r\n this.startNewEncounter();\r\n }\r\n\r\n processPlayerRespawnEvent(event) {\r\n // console.log(\"Player \" + event.hrid + \" respawn at \" + + (this.simulationTime / 1000000000));\r\n let respawningPlayer = this.players.find(player => player.hrid === event.hrid);\r\n respawningPlayer.combatDetails.currentHitpoints = respawningPlayer.combatDetails.maxHitpoints;\r\n respawningPlayer.combatDetails.currentManapoints = respawningPlayer.combatDetails.maxManapoints;\r\n respawningPlayer.clearBuffs();\r\n respawningPlayer.clearCCs();\r\n if (this.allPlayersDead) {\r\n this.allPlayersDead = false;\r\n this.startAttacks();\r\n } else {\r\n this.addNextAttackEvent(respawningPlayer);\r\n }\r\n }\r\n\r\n processEnemyRespawnEvent(event) {\r\n this.startNewEncounter();\r\n }\r\n\r\n startNewEncounter() {\r\n if (this.allPlayersDead) {\r\n this.allPlayersDead = false;\r\n this.zone.failWave();\r\n }\r\n\r\n if (!this.zone.isDungeon) {\r\n this.enemies = this.zone.getRandomEncounter();\r\n } else {\r\n this.enemies = this.zone.getNextWave();\r\n this.simResult.updateTimeSpentAlive(\"#\" + (this.zone.encountersKilled - 1).toString(), true, this.simulationTime);\r\n let currentDungeonCount = this.zone.dungeonsCompleted;\r\n // console.log('wave at #' + (this.zone.encountersKilled - 1) +' completed:' + this.zone.dungeonsCompleted + ' failed:'+ this.zone.dungeonsFailed + ' temp:'+ this.tempDungeonCount);\r\n if (currentDungeonCount > this.tempDungeonCount) {\r\n this.tempDungeonCount = currentDungeonCount;\r\n for (let i = 0; i < this.players.length; i++) {\r\n this.players[i].combatDetails.currentHitpoints = this.players[i].combatDetails.maxHitpoints;\r\n this.players[i].combatDetails.currentManapoints = this.players[i].combatDetails.maxManapoints;\r\n // this.simResult.playerRanOutOfMana[this.players[i].hrid] = false;\r\n }\r\n }\r\n }\r\n\r\n this.enemies.forEach((enemy) => {\r\n enemy.reset(this.simulationTime);\r\n this.simResult.updateTimeSpentAlive(enemy.hrid, true, this.simulationTime);\r\n //console.log(enemy.hrid, \"spawned\");\r\n });\r\n\r\n this.eventQueue.clearEventsOfType(EnrageTickEvent.type);\r\n let enrageTickEvent = new EnrageTickEvent(this.simulationTime + ENRAGE_TICK_INTERVAL, ENRAGE_TICK_INTERVAL);\r\n this.eventQueue.addEvent(enrageTickEvent);\r\n this.enrageBeginTime = this.simulationTime;\r\n\r\n this.eventQueue.clearEventsOfType(AbilityCastEndEvent.type);\r\n\r\n this.startAttacks();\r\n }\r\n\r\n startAttacks() {\r\n let units = [...this.players];\r\n if (this.enemies) {\r\n units.push(...this.enemies);\r\n }\r\n\r\n for (const unit of units) {\r\n if (unit.combatDetails.currentHitpoints <= 0) {\r\n continue;\r\n }\r\n\r\n /*-if (unit.isPlayer) {\r\n // console.log(\"Start Attacks \" + (this.simulationTime / 1000000000));\r\n }*/\r\n this.addNextAttackEvent(unit);\r\n }\r\n }\r\n\r\n checkParry(targets) {\r\n let parryUnits = targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0 && unit.combatDetails.combatStats.parry > 0);\r\n if (parryUnits.length <= 0) {\r\n return undefined;\r\n }\r\n let randomIndex = Math.floor(Math.random() * parryUnits.length);\r\n if (parryUnits[randomIndex].combatDetails.combatStats.parry > Math.random()) {\r\n return parryUnits[randomIndex];\r\n }\r\n return undefined;\r\n }\r\n\r\n processAutoAttackEvent(event) {\r\n // console.log(\"source:\", event.source.hrid);\r\n // console.log(\"aa \" + (this.simulationTime / 1000000000));\r\n\r\n let targets = event.source.isPlayer ? this.enemies : this.players;\r\n\r\n if (!targets) {\r\n return;\r\n }\r\n\r\n const aliveTargets = targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0);\r\n\r\n for (let i = 0; i < aliveTargets.length; i++) {\r\n let target = aliveTargets[i];\r\n if (!event.source.isPlayer && aliveTargets.length > 1) {\r\n let cumulativeThreat = 0;\r\n let cumulativeRanges = [];\r\n aliveTargets.forEach(player => {\r\n let playerThreat = player.combatDetails.combatStats.threat;\r\n cumulativeThreat += playerThreat;\r\n cumulativeRanges.push({\r\n player: player,\r\n rangeStart: cumulativeThreat - playerThreat,\r\n rangeEnd: cumulativeThreat\r\n });\r\n });\r\n let randomValueHit = Math.random() * cumulativeThreat;\r\n target = cumulativeRanges.find(range => randomValueHit >= range.rangeStart && randomValueHit < range.rangeEnd).player;\r\n }\r\n let source = event.source;\r\n\r\n let parryTarget = this.checkParry(targets);\r\n if (parryTarget) {\r\n target = source;\r\n source = parryTarget;\r\n }\r\n\r\n let attackResult = CombatUtilities.processAttack(source, target);\r\n if (this.zone.isDungeon && target.isPlayer && attackResult.didHit && attackResult.damageDone > 0) {\r\n const log = this.generateCombatLog(source, \"autoAttack\", target, attackResult);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n let mayhem = source.combatDetails.combatStats.mayhem > Math.random();\r\n\r\n if (attackResult.didHit && source.combatDetails.combatStats.curse > 0) {\r\n const curseExpireTime = 15000000000;\r\n let currentCurseEvent = this.eventQueue.getMatching((event) => event.type == CurseExpirationEvent.type && event.source == target);\r\n let currentCurseAmount = 0;\r\n if (currentCurseEvent) currentCurseAmount = currentCurseEvent.curseAmount;\r\n this.eventQueue.clearMatching((event) => event.type == CurseExpirationEvent.type && event.source == target);\r\n\r\n let curseExpirationEvent = new CurseExpirationEvent(this.simulationTime + curseExpireTime, currentCurseAmount, target);\r\n const curseBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/curse\",\r\n \"typeHrid\": \"/buff_types/damage_taken\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": source.combatDetails.combatStats.curse * curseExpirationEvent.curseAmount,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": curseExpireTime\r\n };\r\n target.addBuff(curseBuff);\r\n this.eventQueue.addEvent(curseExpirationEvent);\r\n }\r\n\r\n if (source.combatDetails.combatStats.fury > 0) {\r\n let currentFuryEvent = this.eventQueue.getMatching((event) => event.type == FuryExpirationEvent.type && event.source == source);\r\n this.eventQueue.clearMatching((event) => event.type == FuryExpirationEvent.type && event.source == source);\r\n\r\n const furyExpireTime = 15000000000;\r\n const maxFuryStack = 5;\r\n\r\n let furyAmount = 0;\r\n if (currentFuryEvent) furyAmount = currentFuryEvent.furyAmount;\r\n\r\n if (attackResult.didHit) {\r\n furyAmount = Math.min(furyAmount + 1, maxFuryStack);\r\n } else {\r\n furyAmount = Math.floor(furyAmount / 2);\r\n }\r\n\r\n const furyAccuracyBuf = {\r\n \"uniqueHrid\": \"/buff_uniques/fury_accuracy\",\r\n \"typeHrid\": \"/buff_types/fury_accuracy\",\r\n \"ratioBoost\": furyAmount * source.combatDetails.combatStats.fury,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": furyExpireTime\r\n };\r\n const furyDamageBuf = {\r\n \"uniqueHrid\": \"/buff_uniques/fury_damage\",\r\n \"typeHrid\": \"/buff_types/fury_damage\",\r\n \"ratioBoost\": furyAmount * source.combatDetails.combatStats.fury,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": furyExpireTime\r\n };\r\n\r\n if (furyAmount > 0) {\r\n let furyExpirationEvent = new FuryExpirationEvent(this.simulationTime + furyExpireTime, furyAmount, source);\r\n this.eventQueue.addEvent(furyExpirationEvent);\r\n\r\n source.addBuff(furyAccuracyBuf, this.simulationTime);\r\n source.addBuff(furyDamageBuf, this.simulationTime);\r\n }\r\n else {\r\n source.removeBuff(furyAccuracyBuf);\r\n source.removeBuff(furyDamageBuf);\r\n }\r\n }\r\n\r\n if (target.combatDetails.combatStats.weaken > 0) {\r\n const weakenExpireTime = 15000000000;\r\n let currentWeakenEvent = this.eventQueue.getMatching((event) => event.type == WeakenExpirationEvent.type && event.source == source);\r\n let weakenAmount = 0;\r\n if (currentWeakenEvent)\r\n weakenAmount = currentWeakenEvent.weakenAmount;\r\n this.eventQueue.clearMatching((event) => event.type == WeakenExpirationEvent.type && event.source == source);\r\n let weakenExpirationEvent = new WeakenExpirationEvent(this.simulationTime + 15000000000, weakenAmount, source);\r\n const weakenBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/weaken\",\r\n \"typeHrid\": \"/buff_types/damage\",\r\n \"ratioBoost\": -1 * target.combatDetails.combatStats.weaken * weakenExpirationEvent.weakenAmount,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": weakenExpireTime\r\n };\r\n source.addBuff(weakenBuff);\r\n this.eventQueue.addEvent(weakenExpirationEvent);\r\n }\r\n\r\n if (!mayhem || (mayhem && attackResult.didHit) || (mayhem && i == (aliveTargets.length - 1))) {\r\n let attackType = \"autoAttack\";\r\n if (parryTarget) attackType = \"parry\";\r\n this.simResult.addAttack(\r\n source,\r\n target,\r\n \"autoAttack\",\r\n attackResult.didHit ? attackResult.damageDone : \"miss\"\r\n );\r\n }\r\n\r\n if (attackResult.lifeStealHeal > 0) {\r\n this.simResult.addHitpointsGained(source, \"lifesteal\", attackResult.lifeStealHeal);\r\n }\r\n\r\n if (attackResult.manaLeechMana > 0) {\r\n this.simResult.addManapointsGained(source, \"manaLeech\", attackResult.manaLeechMana);\r\n }\r\n\r\n if (attackResult.thornDamageDone > 0) {\r\n this.simResult.addAttack(target, source, attackResult.thornType, attackResult.thornDamageDone);\r\n }\r\n if (this.zone.isDungeon && attackResult.thornDamageDone > 0 && source.isPlayer) {\r\n const log = this.buildCombatLog(target, attackResult.thornType, source, attackResult.thornDamageDone);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (target.combatDetails.combatStats.retaliation > 0) {\r\n this.simResult.addAttack(target, source, \"retaliation\", attackResult.retaliationDamageDone > 0?attackResult.retaliationDamageDone:\"miss\");\r\n }\r\n if (this.zone.isDungeon && attackResult.retaliationDamageDone > 0 && source.isPlayer) {\r\n const log = this.buildCombatLog(target, \"retaliation\", source, attackResult.retaliationDamageDone);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (target.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(target);\r\n this.simResult.addDeath(target);\r\n if (!target.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(target.hrid, false, this.simulationTime);\r\n }\r\n // console.log(target.hrid, \"died\");\r\n }\r\n\r\n // Could die from reflect damage\r\n if (source.combatDetails.currentHitpoints == 0 && \r\n (attackResult.thornDamageDone != 0 || attackResult.retaliationDamageDone != 0)\r\n ) {\r\n this.eventQueue.clearEventsForUnit(source);\r\n this.simResult.addDeath(source);\r\n if (!source.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(source.hrid, false, this.simulationTime);\r\n }\r\n break;\r\n }\r\n\r\n if (mayhem && !attackResult.didHit) {\r\n continue;\r\n }\r\n\r\n if (!attackResult.didHit || parryTarget || source.combatDetails.combatStats.pierce <= Math.random()) {\r\n break;\r\n }\r\n }\r\n\r\n if (!this.checkEncounterEnd()) {\r\n // console.log(\"!EncounterEnd \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(event.source);\r\n }\r\n }\r\n\r\n checkEncounterEnd() {\r\n if (this.enemies) {\r\n let deadEnemies = this.enemies.filter((enemy) => enemy.combatDetails.currentHitpoints <= 0 && enemy.experienceRate == 0);\r\n if (deadEnemies.length > 0) {\r\n deadEnemies.forEach(enemy => {\r\n let aliveDuration = this.simulationTime - this.enrageBeginTime;\r\n if (aliveDuration > enemy.enrageTime) {\r\n aliveDuration = enemy.enrageTime;\r\n }\r\n enemy.experienceRate = 1.0 + aliveDuration / enemy.enrageTime;\r\n // console.log(enemy.hrid, \"alive duration\", aliveDuration, \"exp rate\", enemy.experienceRate);\r\n })\r\n }\r\n }\r\n\r\n let encounterEnded = false;\r\n\r\n if (this.enemies && !this.enemies.some((enemy) => enemy.combatDetails.currentHitpoints > 0)) {\r\n this.eventQueue.clearEventsOfType(AutoAttackEvent.type);\r\n // this.eventQueue.clearEventsOfType(AbilityCastEndEvent.type);\r\n let enemyRespawnEvent = new EnemyRespawnEvent(this.simulationTime + ENEMY_RESPAWN_INTERVAL);\r\n this.eventQueue.addEvent(enemyRespawnEvent);\r\n\r\n //calc exp before clear\r\n if (this.enemies.some(enemy => enemy.experienceRate <= 0)) {\r\n console.log(\"WARN: Some enemies have no experience rate\");\r\n }\r\n\r\n let totalExp = this.enemies.map(enemy => enemy.experience * enemy.experienceRate).reduce((a, b) => a + b, 0);\r\n this.players.forEach(player => {\r\n this.simResult.addExperienceGain(player, totalExp / this.players.length);\r\n });\r\n\r\n this.enemies = null;\r\n\r\n if (this.zone.isDungeon) {\r\n this.simResult.updateTimeSpentAlive(\"#\" + (this.zone.encountersKilled - 1).toString(), false, this.simulationTime);\r\n }\r\n this.simResult.addEncounterEnd();\r\n // console.log(\"All enemies died\");\r\n\r\n encounterEnded = true;\r\n // console.log(\"encounter end \" + (this.simulationTime / 1000000000))\r\n }\r\n\r\n this.players.forEach(player => {\r\n if ((player.combatDetails.currentHitpoints <= 0) && !this.eventQueue.containsEventOfTypeAndHrid(PlayerRespawnEvent.type, player.hrid)) {\r\n if (!this.zone.isDungeon) {\r\n let playerRespawnEvent = new PlayerRespawnEvent(this.simulationTime + PLAYER_RESPAWN_INTERVAL, player.hrid);\r\n this.eventQueue.addEvent(playerRespawnEvent);\r\n }\r\n this.simResult.addRanOutOfManaCount(player, false, this.simulationTime);\r\n // console.log(player.hrid + \" died at \" + (this.simulationTime / 1000000000) + 'in wave #' + (this.zone.encountersKilled - 1) + ' with ememies: ' + this.enemies?.map(enemy => (enemy.hrid+\"(\"+(enemy.combatDetails.currentHitpoints*100/enemy.combatDetails.maxHitpoints).toFixed(2)+\"%)\")).join(\", \"));\r\n }\r\n });\r\n\r\n if (\r\n !this.players.some((player) => player.combatDetails.currentHitpoints > 0)\r\n ) {\r\n if (this.zone.isDungeon) {\r\n console.log(\"All Players died at wave #\" + (this.zone.encountersKilled - 1) + \" with ememies: \" + this.enemies.map(enemy => (enemy.hrid+\"(\"+(enemy.combatDetails.currentHitpoints*100/enemy.combatDetails.maxHitpoints).toFixed(2)+\"%)\")).join(\", \"));\r\n\r\n this.saveWipeLogsToSimResult(this.zone.encountersKilled - 1);\r\n // console.log(this.simResult)\r\n this.wipeLogs.index = 0;\r\n this.wipeLogs.count = 0;\r\n\r\n this.eventQueue.clear();\r\n this.enemies = null;\r\n\r\n let combatStartEvent = new CombatStartEvent(this.simulationTime + RESTART_INTERVAL);\r\n this.eventQueue.addEvent(combatStartEvent);\r\n } else {\r\n this.eventQueue.clearEventsOfType(AutoAttackEvent.type);\r\n this.eventQueue.clearEventsOfType(AbilityCastEndEvent.type);\r\n }\r\n // console.log(\"All Players died\");\r\n encounterEnded = true;\r\n this.allPlayersDead = true;\r\n }\r\n\r\n return encounterEnded;\r\n }\r\n\r\n addNextAttackEvent(source) {\r\n if (this.eventQueue.getMatching((event) => (event.type == AbilityCastEndEvent.type || event.type == AutoAttackEvent.type)&& event.source == source)) {\r\n return;\r\n }\r\n\r\n let target;\r\n let friendlies;\r\n let enemies;\r\n if (source.isPlayer) {\r\n target = CombatUtilities.getTarget(this.enemies);\r\n friendlies = this.players;\r\n enemies = this.enemies;\r\n } else {\r\n target = CombatUtilities.getTarget(this.players);\r\n friendlies = this.enemies;\r\n enemies = this.players;\r\n }\r\n\r\n let usedAbility = false;\r\n let skipNextAbility = false; \r\n\r\n source.abilities\r\n .filter((ability) => ability != null)\r\n .forEach((ability) => {\r\n if (!usedAbility && !skipNextAbility && ability.shouldTrigger(this.simulationTime, source, target, friendlies, enemies)) {\r\n if (!this.canUseAbility(source, ability, true)) {\r\n skipNextAbility = true;\r\n }\r\n\r\n if (!skipNextAbility) {\r\n let castDuration = ability.castDuration;\r\n castDuration /= (1 + source.combatDetails.combatStats.castSpeed)\r\n let abilityCastEndEvent = new AbilityCastEndEvent(this.simulationTime + castDuration, source, ability);\r\n this.eventQueue.addEvent(abilityCastEndEvent);\r\n /*-if (source.isPlayer) {\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n // console.log((this.simulationTime / 1000000000) + \" Casting \" + ability.hrid + \" Cast time \" + (castDuration / 1e9) + \" Off CD at \" + ((this.simulationTime + cooldownDuration + castDuration) / 1e9) + \" CD \" + ((cooldownDuration) / 1e9));\r\n }*/\r\n usedAbility = true;\r\n }\r\n }\r\n });\r\n\r\n if (usedAbility) {\r\n source.isOutOfMana = false;\r\n return;\r\n }\r\n\r\n if (!enemies) {\r\n return;\r\n }\r\n\r\n if (!source.isBlinded) {\r\n let autoAttackEvent = new AutoAttackEvent(\r\n this.simulationTime + source.combatDetails.combatStats.attackInterval,\r\n source\r\n );\r\n /*-if (source.isPlayer) {\r\n // console.log(\"next attack \" + ((this.simulationTime + source.combatDetails.combatStats.attackInterval) / 1e9))\r\n }*/\r\n this.eventQueue.addEvent(autoAttackEvent);\r\n } else {\r\n source.isOutOfMana = true;\r\n }\r\n }\r\n\r\n processConsumableTickEvent(event) {\r\n if (event.consumable.hitpointRestore > 0) {\r\n let tickValue = CombatUtilities.calculateTickValue(\r\n event.consumable.hitpointRestore,\r\n event.totalTicks,\r\n event.currentTick\r\n );\r\n let hitpointsAdded = event.source.addHitpoints(tickValue);\r\n this.simResult.addHitpointsGained(event.source, event.consumable.hrid, hitpointsAdded);\r\n // console.log(\"Added hitpoints:\", hitpointsAdded);\r\n }\r\n\r\n if (event.consumable.manapointRestore > 0) {\r\n let tickValue = CombatUtilities.calculateTickValue(\r\n event.consumable.manapointRestore,\r\n event.totalTicks,\r\n event.currentTick\r\n );\r\n let manapointsAdded = event.source.addManapoints(tickValue);\r\n this.simResult.addManapointsGained(event.source, event.consumable.hrid, manapointsAdded);\r\n // console.log(\"Added manapoints:\", manapointsAdded);\r\n\r\n // when oom check ability trigger\r\n if (event.source.isOutOfMana) {\r\n let awaitCooldownEvent = new AwaitCooldownEvent(\r\n this.simulationTime,\r\n event.source\r\n );\r\n this.eventQueue.addEvent(awaitCooldownEvent);\r\n }\r\n }\r\n\r\n if (event.currentTick < event.totalTicks) {\r\n let consumableTickEvent = new ConsumableTickEvent(\r\n this.simulationTime + HOT_TICK_INTERVAL,\r\n event.source,\r\n event.consumable,\r\n event.totalTicks,\r\n event.currentTick + 1\r\n );\r\n this.eventQueue.addEvent(consumableTickEvent);\r\n }\r\n }\r\n\r\n processDamageOverTimeTickEvent(event) {\r\n let tickDamage = CombatUtilities.calculateTickValue(event.damage, event.totalTicks, event.currentTick);\r\n let damage = Math.min(tickDamage, event.target.combatDetails.currentHitpoints);\r\n\r\n event.target.combatDetails.currentHitpoints -= damage;\r\n this.simResult.addAttack(event.sourceRef, event.target, \"damageOverTime\", damage);\r\n\r\n const log = this.buildCombatLog(\"\", \"damageOverTime\", event.target, damage);\r\n this.addToWipeLogs(log);\r\n\r\n // console.log(event.target.hrid, \"bleed for\", damage);\r\n\r\n if (event.currentTick < event.totalTicks) {\r\n let damageOverTimeTickEvent = new DamageOverTimeEvent(\r\n this.simulationTime + DOT_TICK_INTERVAL,\r\n event.sourceRef,\r\n event.target,\r\n event.damage,\r\n event.totalTicks,\r\n event.currentTick + 1,\r\n event.combatStyleHrid\r\n );\r\n this.eventQueue.addEvent(damageOverTimeTickEvent);\r\n }\r\n\r\n if (event.target.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(event.target);\r\n this.simResult.addDeath(event.target);\r\n if (!event.target.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(event.target.hrid, false, this.simulationTime);\r\n }\r\n }\r\n\r\n this.checkEncounterEnd();\r\n }\r\n\r\n processRegenTickEvent(event) {\r\n let units = [...this.players];\r\n\r\n // regen of emeny always set to 0, ingore the proc time\r\n // if (this.enemies) {\r\n // units.push(...this.enemies);\r\n // }\r\n\r\n for (const unit of units) {\r\n if (unit.combatDetails.currentHitpoints <= 0) {\r\n continue;\r\n }\r\n\r\n let hitpointRegen = Math.floor(unit.combatDetails.maxHitpoints * unit.combatDetails.combatStats.hpRegenPer10);\r\n let hitpointsAdded = unit.addHitpoints(hitpointRegen);\r\n this.simResult.addHitpointsGained(unit, \"regen\", hitpointsAdded);\r\n // console.log(\"Added hitpoints:\", hitpointsAdded);\r\n\r\n let manapointRegen = Math.floor(unit.combatDetails.maxManapoints * unit.combatDetails.combatStats.mpRegenPer10);\r\n let manapointsAdded = unit.addManapoints(manapointRegen);\r\n this.simResult.addManapointsGained(unit, \"regen\", manapointsAdded);\r\n // console.log(\"Added manapoints:\", manapointsAdded);\r\n\r\n // when oom check ability trigger\r\n if (unit.isOutOfMana) {\r\n let awaitCooldownEvent = new AwaitCooldownEvent(\r\n this.simulationTime,\r\n unit\r\n );\r\n this.eventQueue.addEvent(awaitCooldownEvent);\r\n }\r\n }\r\n\r\n let regenTickEvent = new RegenTickEvent(this.simulationTime + REGEN_TICK_INTERVAL);\r\n this.eventQueue.addEvent(regenTickEvent);\r\n }\r\n\r\n processCheckBuffExpirationEvent(event) {\r\n event.source.removeExpiredBuffs(this.simulationTime);\r\n }\r\n\r\n processStunExpirationEvent(event) {\r\n event.source.isStunned = false;\r\n // console.log(\"Stun \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(event.source);\r\n }\r\n\r\n processBlindExpirationEvent(event) {\r\n event.source.isBlinded = false;\r\n this.addNextAttackEvent(event.source);\r\n }\r\n\r\n processSilenceExpirationEvent(event) {\r\n event.source.isSilenced = false;\r\n }\r\n\r\n processCurseExpirationEvent(event) {\r\n event.source.removeExpiredBuffs(this.simulationTime);\r\n }\r\n\r\n processWeakenExpirationEvent(event) {\r\n event.source.removeExpiredBuffs(this.simulationTime);\r\n }\r\n\r\n processFuryExpirationEvent(event) {\r\n event.source.removeExpiredBuffs(this.simulationTime);\r\n console.log(\"Fury Timeout\");\r\n }\r\n\r\n processEnrageTickEvent(event) {\r\n if (!this.enemies) return;\r\n const maxEnrageStack = 10;\r\n this.enemies.filter((enemy) => enemy.combatDetails.currentHitpoints > 0).forEach((enemy) => {\r\n let nowStack = Math.min(maxEnrageStack, Math.floor(event.encounterTime / enemy.enrageTime));\r\n\r\n if (nowStack <= 0) {\r\n return;\r\n }\r\n\r\n console.log(enemy.hrid, nowStack, \" stack Enrage at \", (event.encounterTime / ONE_SECOND));\r\n\r\n const enrageDamageBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/enrage_damage\",\r\n \"typeHrid\": \"/buff_types/damage\",\r\n \"ratioBoost\": nowStack * 0.1,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": ENRAGE_TICK_INTERVAL\r\n };\r\n const enrageAccuracyBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/enrage_accuracy\",\r\n \"typeHrid\": \"/buff_types/accuracy\",\r\n \"ratioBoost\": nowStack * 0.1,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": ENRAGE_TICK_INTERVAL\r\n };\r\n enemy.addBuff(enrageDamageBuff);\r\n enemy.addBuff(enrageAccuracyBuff);\r\n \r\n this.simResult.maxEnrageStack = Math.max(this.simResult.maxEnrageStack, nowStack);\r\n });\r\n\r\n let enrageTickEvent = new EnrageTickEvent(this.simulationTime + ENRAGE_TICK_INTERVAL, event.encounterTime + ENRAGE_TICK_INTERVAL);\r\n this.eventQueue.addEvent(enrageTickEvent);\r\n }\r\n\r\n checkTriggers() {\r\n let triggeredSomething;\r\n\r\n do {\r\n triggeredSomething = false;\r\n\r\n this.players\r\n .filter((player) => player.combatDetails.currentHitpoints > 0)\r\n .forEach((player) => {\r\n if (this.checkTriggersForUnit(player, this.players, this.enemies)) {\r\n triggeredSomething = true;\r\n }\r\n });\r\n\r\n if (this.enemies) {\r\n this.enemies\r\n .filter((enemy) => enemy.combatDetails.currentHitpoints > 0)\r\n .forEach((enemy) => {\r\n if (this.checkTriggersForUnit(enemy, this.enemies, this.players)) {\r\n triggeredSomething = true;\r\n }\r\n });\r\n }\r\n } while (triggeredSomething);\r\n }\r\n\r\n checkTriggersForUnit(unit, friendlies, enemies) {\r\n if (unit.combatDetails.currentHitpoints <= 0) {\r\n throw new Error(\"Checking triggers for a dead unit\");\r\n }\r\n\r\n let triggeredSomething = false;\r\n let target = CombatUtilities.getTarget(enemies);\r\n\r\n for (const food of unit.food) {\r\n if (food && food.shouldTrigger(this.simulationTime, unit, target, friendlies, enemies)) {\r\n let result = this.tryUseConsumable(unit, food);\r\n if (result) {\r\n triggeredSomething = true;\r\n }\r\n }\r\n }\r\n\r\n for (const drink of unit.drinks) {\r\n if (drink && drink.shouldTrigger(this.simulationTime, unit, target, friendlies, enemies)) {\r\n let result = this.tryUseConsumable(unit, drink);\r\n if (result) {\r\n triggeredSomething = true;\r\n }\r\n }\r\n }\r\n\r\n return triggeredSomething;\r\n }\r\n\r\n tryUseConsumable(source, consumable) {\r\n // console.log(\"Consuming:\", consumable);\r\n\r\n if (source.combatDetails.currentHitpoints <= 0) {\r\n return false;\r\n }\r\n\r\n consumable.lastUsed = this.simulationTime;\r\n let consumeCooldown = consumable.cooldownDuration;\r\n if (source.combatDetails.combatStats.drinkConcentration > 0 && consumable.catagoryHrid.includes(\"drink\")) {\r\n consumeCooldown = consumeCooldown / (1 + source.combatDetails.combatStats.drinkConcentration);\r\n } else if (source.combatDetails.combatStats.foodHaste > 0 && consumable.catagoryHrid.includes(\"food\")) {\r\n consumeCooldown = consumeCooldown / (1 + source.combatDetails.combatStats.foodHaste);\r\n }\r\n let cooldownReadyEvent = new CooldownReadyEvent(this.simulationTime + consumeCooldown);\r\n this.eventQueue.addEvent(cooldownReadyEvent);\r\n\r\n this.simResult.addConsumableUse(source, consumable);\r\n\r\n if (consumable.recoveryDuration == 0) {\r\n if (consumable.hitpointRestore > 0) {\r\n let hitpointsAdded = source.addHitpoints(consumable.hitpointRestore);\r\n this.simResult.addHitpointsGained(source, consumable.hrid, hitpointsAdded);\r\n // console.log(\"Added hitpoints:\", hitpointsAdded);\r\n }\r\n\r\n if (consumable.manapointRestore > 0) {\r\n let manapointsAdded = source.addManapoints(consumable.manapointRestore);\r\n this.simResult.addManapointsGained(source, consumable.hrid, manapointsAdded);\r\n // console.log(\"Added manapoints:\", manapointsAdded);\r\n\r\n // when oom check ability trigger\r\n if (source.isOutOfMana) {\r\n let awaitCooldownEvent = new AwaitCooldownEvent(\r\n this.simulationTime,\r\n source\r\n );\r\n this.eventQueue.addEvent(awaitCooldownEvent);\r\n }\r\n }\r\n } else {\r\n let consumableTickEvent = new ConsumableTickEvent(\r\n this.simulationTime + HOT_TICK_INTERVAL,\r\n source,\r\n consumable,\r\n consumable.recoveryDuration / HOT_TICK_INTERVAL,\r\n 1\r\n );\r\n this.eventQueue.addEvent(consumableTickEvent);\r\n }\r\n\r\n for (const buff of consumable.buffs) {\r\n let currentBuff = structuredClone(buff);\r\n if (source.combatDetails.combatStats.drinkConcentration > 0 && consumable.catagoryHrid.includes(\"drink\")) {\r\n currentBuff.ratioBoost *= (1 + source.combatDetails.combatStats.drinkConcentration);\r\n currentBuff.flatBoost *= (1 + source.combatDetails.combatStats.drinkConcentration);\r\n currentBuff.duration = currentBuff.duration / (1 + source.combatDetails.combatStats.drinkConcentration);\r\n }\r\n source.addBuff(currentBuff, this.simulationTime);\r\n // console.log(\"Added buff:\", currentBuff);\r\n let checkBuffExpirationEvent = new CheckBuffExpirationEvent(this.simulationTime + currentBuff.duration, source);\r\n this.eventQueue.addEvent(checkBuffExpirationEvent);\r\n }\r\n\r\n return true;\r\n }\r\n\r\n canUseAbility(source, ability, oomCheck) {\r\n if (source.combatDetails.currentHitpoints <= 0) {\r\n return false;\r\n }\r\n\r\n if (source.combatDetails.currentManapoints < ability.manaCost) {\r\n if (source.isPlayer && oomCheck) {\r\n // if (this.simResult.playerRanOutOfMana[source.hrid] == false) {\r\n // console.log(source.hrid + \" ran out of mana\" + ' at wave #' + (this.zone.encountersKilled - 1) + ' at time ' + this.simulationTime / 1000000000 + 's');\r\n // }\r\n this.simResult.addRanOutOfManaCount(source, true, this.simulationTime);\r\n }\r\n return false;\r\n }\r\n if (source.isPlayer && oomCheck) {\r\n this.simResult.addRanOutOfManaCount(source, false, this.simulationTime);\r\n }\r\n return true;\r\n }\r\n\r\n tryUseAbility(source, ability) {\r\n\r\n if (!this.canUseAbility(source, ability, true)) {\r\n // console.log(\"Falseeeeeee\");\r\n return false;\r\n }\r\n\r\n // console.log(\"Casting:\", ability);\r\n\r\n if (source.isPlayer) {\r\n if (source.abilityManaCosts.has(ability.hrid)) {\r\n source.abilityManaCosts.set(ability.hrid, source.abilityManaCosts.get(ability.hrid) + ability.manaCost);\r\n } else {\r\n source.abilityManaCosts.set(ability.hrid, ability.manaCost);\r\n }\r\n }\r\n\r\n source.combatDetails.currentManapoints -= ability.manaCost;\r\n\r\n ability.lastUsed = this.simulationTime;\r\n\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n\r\n /*-if (source.isPlayer) {\r\n let castDuration = ability.castDuration;\r\n castDuration /= (1 + source.combatDetails.combatStats.castSpeed)\r\n // console.log((this.simulationTime / 1000000000) + \" Used ability \" + ability.hrid + \" Cast time \" + (castDuration / 1e9));\r\n }*/\r\n\r\n let todoAbilities = [ability];\r\n\r\n if (source.combatDetails.combatStats.blaze > 0 && Math.random() < source.combatDetails.combatStats.blaze) {\r\n todoAbilities.push(new Ability(\"blaze\"));\r\n }\r\n\r\n if (source.combatDetails.combatStats.bloom > 0 && Math.random() < source.combatDetails.combatStats.bloom) {\r\n todoAbilities.push(new Ability(\"bloom\"));\r\n }\r\n\r\n for (const todoAbility of todoAbilities) {\r\n for (const abilityEffect of todoAbility.abilityEffects) {\r\n switch (abilityEffect.effectType) {\r\n case \"/ability_effect_types/buff\":\r\n this.processAbilityBuffEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/damage\":\r\n this.processAbilityDamageEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/heal\":\r\n this.processAbilityHealEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/spend_hp\":\r\n this.processAbilitySpendHpEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/revive\":\r\n this.processAbilityReviveEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/promote\":\r\n this.eventQueue.clearEventsForUnit(source);\r\n source = this.processAbilityPromoteEffect(source, todoAbility, abilityEffect);\r\n this.addNextAttackEvent(source);\r\n break;\r\n default:\r\n throw new Error(\"Unsupported effect type for ability: \" + todoAbility.hrid + \" effectType: \" + abilityEffect.effectType);\r\n }\r\n }\r\n }\r\n\r\n if (source.combatDetails.combatStats.ripple > 0 && Math.random() < source.combatDetails.combatStats.ripple) {\r\n let manapointsAdded = source.addManapoints(10);\r\n this.simResult.addManapointsGained(source, \"ripple\", manapointsAdded);\r\n for (const ability of source.abilities) {\r\n if (ability && ability.lastUsed) {\r\n const remainingCooldown = ability.lastUsed + ability.cooldownDuration - this.simulationTime;\r\n if (remainingCooldown > 0) {\r\n ability.lastUsed = Math.max(ability.lastUsed - ONE_SECOND * 2, this.simulationTime - ability.cooldownDuration);\r\n }\r\n }\r\n }\r\n }\r\n\r\n this.addNextAttackEvent(source);\r\n\r\n // Could die from reflect damage\r\n if (source.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(source);\r\n this.simResult.addDeath(source);\r\n if (!source.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(source.hrid, false, this.simulationTime);\r\n }\r\n }\r\n\r\n this.checkEncounterEnd();\r\n\r\n return true;\r\n }\r\n\r\n processAbilityBuffEffect(source, ability, abilityEffect) {\r\n if (abilityEffect.targetType == \"allAllies\") {\r\n let targets = source.isPlayer ? this.players : this.enemies;\r\n for (const target of targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0)) {\r\n for (const buff of abilityEffect.buffs) {\r\n if (ability.isSpecialAbility && buff.multiplierForSkillHrid && buff.multiplierPerSkillLevel > 0) {\r\n let multiplier = 1.0 + source.combatDetails[buff.multiplierForSkillHrid.split('/')[2] + 'Level'] * buff.multiplierPerSkillLevel;\r\n let currentBuff = structuredClone(buff);\r\n currentBuff.flatBoost *= multiplier;\r\n target.addBuff(currentBuff, this.simulationTime);\r\n } else {\r\n target.addBuff(buff, this.simulationTime);\r\n }\r\n let checkBuffExpirationEvent = new CheckBuffExpirationEvent(this.simulationTime + buff.duration, target);\r\n this.eventQueue.addEvent(checkBuffExpirationEvent);\r\n }\r\n }\r\n return;\r\n }\r\n\r\n if (abilityEffect.targetType != \"self\") {\r\n throw new Error(\"Unsupported target type for buff ability effect: \" + ability.hrid);\r\n }\r\n\r\n for (const buff of abilityEffect.buffs) {\r\n source.addBuff(buff, this.simulationTime);\r\n // console.log(\"Added buff:\", abilityEffect.buff);\r\n let checkBuffExpirationEvent = new CheckBuffExpirationEvent(this.simulationTime + buff.duration, source);\r\n this.eventQueue.addEvent(checkBuffExpirationEvent);\r\n }\r\n }\r\n\r\n processAbilityDamageEffect(source, ability, abilityEffect) {\r\n let targets;\r\n switch (abilityEffect.targetType) {\r\n case \"enemy\":\r\n case \"allEnemies\":\r\n targets = source.isPlayer ? this.enemies : this.players;\r\n break;\r\n default:\r\n throw new Error(\"Unsupported target type for damage ability effect: \" + ability.hrid);\r\n }\r\n\r\n if (!targets) {\r\n return;\r\n }\r\n\r\n let avoidTarget = [];\r\n\r\n let isSkipParry = false;\r\n\r\n for (let target of targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0)) {\r\n let parryTarget = undefined;\r\n if (!isSkipParry) {\r\n parryTarget = this.checkParry(targets);\r\n isSkipParry = true; // parry check only once on first target\r\n }\r\n \r\n if (parryTarget) {\r\n let tempTarget = source;\r\n let tempSource = parryTarget;\r\n\r\n let attackResult = CombatUtilities.processAttack(tempSource, tempTarget);\r\n\r\n this.simResult.addAttack(\r\n tempSource,\r\n tempTarget,\r\n \"parry\",\r\n attackResult.didHit ? attackResult.damageDone : \"miss\"\r\n );\r\n\r\n if (attackResult.lifeStealHeal > 0) {\r\n this.simResult.addHitpointsGained(tempSource, \"lifesteal\", attackResult.lifeStealHeal);\r\n }\r\n\r\n if (attackResult.manaLeechMana > 0) {\r\n this.simResult.addManapointsGained(tempSource, \"manaLeech\", attackResult.manaLeechMana);\r\n }\r\n\r\n if (attackResult.thornDamageDone > 0) {\r\n this.simResult.addAttack(tempTarget, tempSource, attackResult.thornType, attackResult.thornDamageDone);\r\n }\r\n if (tempTarget.combatDetails.combatStats.retaliation > 0) {\r\n this.simResult.addAttack(tempTarget, tempSource, \"retaliation\", attackResult.retaliationDamageDone > 0 ? attackResult.retaliationDamageDone : \"miss\");\r\n }\r\n\r\n if (tempTarget.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(tempTarget);\r\n this.simResult.addDeath(tempTarget);\r\n if (!tempTarget.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(tempTarget.hrid, false, this.simulationTime);\r\n }\r\n // console.log(tempTarget.hrid, \"died\");\r\n }\r\n\r\n // Could die from reflect damage\r\n if (tempSource.combatDetails.currentHitpoints == 0 && \r\n (attackResult.thornDamageDone != 0 || attackResult.retaliationDamageDone != 0)\r\n ) {\r\n this.eventQueue.clearEventsForUnit(tempSource);\r\n this.simResult.addDeath(tempSource);\r\n if (!tempSource.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(tempSource.hrid, false, this.simulationTime);\r\n }\r\n }\r\n } else {\r\n targets = targets.filter((unit) => unit && !avoidTarget.includes(unit.hrid) && unit.combatDetails.currentHitpoints > 0);\r\n if (!source.isPlayer && targets.length > 0 && abilityEffect.targetType == \"enemy\") {\r\n let cumulativeThreat = 0;\r\n let cumulativeRanges = [];\r\n targets.forEach(player => {\r\n let playerThreat = player.combatDetails.combatStats.threat;\r\n cumulativeThreat += playerThreat;\r\n cumulativeRanges.push({\r\n player: player,\r\n rangeStart: cumulativeThreat - playerThreat,\r\n rangeEnd: cumulativeThreat\r\n });\r\n });\r\n let randomValueHit = Math.random() * cumulativeThreat;\r\n target = cumulativeRanges.find(range => randomValueHit >= range.rangeStart && randomValueHit < range.rangeEnd).player;\r\n avoidTarget.push(target.hrid);\r\n }\r\n if (targets.length <= 0) {\r\n break;\r\n }\r\n\r\n let attackResult = CombatUtilities.processAttack(source, target, abilityEffect);\r\n\r\n if (this.zone.isDungeon && target.isPlayer && attackResult.didHit && attackResult.damageDone > 0) {\r\n const log = this.generateCombatLog(source, ability.hrid, target, attackResult);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (attackResult.hpDrain > 0) {\r\n this.simResult.addHitpointsGained(source, ability.hrid, attackResult.hpDrain);\r\n }\r\n\r\n if (attackResult.didHit && abilityEffect.buffs) {\r\n for (const buff of abilityEffect.buffs) {\r\n target.addBuff(buff, this.simulationTime);\r\n let checkBuffExpirationEvent = new CheckBuffExpirationEvent(\r\n this.simulationTime + buff.duration,\r\n target\r\n );\r\n this.eventQueue.addEvent(checkBuffExpirationEvent);\r\n }\r\n }\r\n\r\n if (abilityEffect.damageOverTimeRatio > 0 && attackResult.damageDone > 0) {\r\n let damageOverTimeEvent = new DamageOverTimeEvent(\r\n this.simulationTime + DOT_TICK_INTERVAL,\r\n source,\r\n target,\r\n attackResult.damageDone * abilityEffect.damageOverTimeRatio,\r\n abilityEffect.damageOverTimeDuration / DOT_TICK_INTERVAL,\r\n 1, abilityEffect.combatStyleHrid\r\n );\r\n this.eventQueue.addEvent(damageOverTimeEvent);\r\n }\r\n\r\n if (attackResult.didHit && abilityEffect.stunChance > 0 && Math.random() < (abilityEffect.stunChance * 100 / (100 + target.combatDetails.combatStats.tenacity))) {\r\n target.isStunned = true;\r\n target.stunExpireTime = this.simulationTime + abilityEffect.stunDuration;\r\n this.eventQueue.clearMatching((event) => (event.type == AutoAttackEvent.type || event.type == AbilityCastEndEvent.type || event.type == StunExpirationEvent.type) && event.source == target);\r\n let stunExpirationEvent = new StunExpirationEvent(target.stunExpireTime, target);\r\n this.eventQueue.addEvent(stunExpirationEvent);\r\n }\r\n\r\n if (attackResult.didHit && abilityEffect.blindChance > 0 && Math.random() < (abilityEffect.blindChance * 100 / (100 + target.combatDetails.combatStats.tenacity))) {\r\n target.isBlinded = true;\r\n target.blindExpireTime = this.simulationTime + abilityEffect.blindDuration;\r\n this.eventQueue.clearMatching((event) => event.type == BlindExpirationEvent.type && event.source == target)\r\n if (this.eventQueue.clearMatching((event) => event.type == AutoAttackEvent.type && event.source == target)) {\r\n // console.log(\"Blind \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(target);\r\n }\r\n let blindExpirationEvent = new BlindExpirationEvent(target.blindExpireTime, target);\r\n this.eventQueue.addEvent(blindExpirationEvent);\r\n }\r\n\r\n if (attackResult.didHit && abilityEffect.silenceChance > 0 && Math.random() < (abilityEffect.silenceChance * 100 / (100 + target.combatDetails.combatStats.tenacity))) {\r\n target.isSilenced = true;\r\n target.silenceExpireTime = this.simulationTime + abilityEffect.silenceDuration;\r\n this.eventQueue.clearMatching((event) => event.type == SilenceExpirationEvent.type && event.source == target)\r\n if (this.eventQueue.clearMatching((event) => event.type == AbilityCastEndEvent.type && event.source == target)) {\r\n // console.log(\"Silence \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(target);\r\n }\r\n let silenceExpirationEvent = new SilenceExpirationEvent(target.silenceExpireTime, target);\r\n this.eventQueue.addEvent(silenceExpirationEvent);\r\n }\r\n\r\n if (attackResult.didHit && source.combatDetails.combatStats.curse > 0 && Math.random() < (100 / (100 + target.combatDetails.combatStats.tenacity))) {\r\n const curseExpireTime = 15000000000;\r\n let currentCurseEvent = this.eventQueue.getMatching((event) => event.type == CurseExpirationEvent.type && event.source == target);\r\n let currentCurseAmount = 0;\r\n if (currentCurseEvent) currentCurseAmount = currentCurseEvent.curseAmount;\r\n this.eventQueue.clearMatching((event) => event.type == CurseExpirationEvent.type && event.source == target);\r\n\r\n let curseExpirationEvent = new CurseExpirationEvent(this.simulationTime + curseExpireTime, currentCurseAmount, target);\r\n const curseBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/curse\",\r\n \"typeHrid\": \"/buff_types/damage_taken\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": source.combatDetails.combatStats.curse * curseExpirationEvent.curseAmount,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": curseExpireTime\r\n };\r\n target.addBuff(curseBuff);\r\n this.eventQueue.addEvent(curseExpirationEvent);\r\n }\r\n\r\n if (target.combatDetails.combatStats.weaken > 0) {\r\n const weakenExpireTime = 15000000000;\r\n source.weakenExpireTime = this.simulationTime + weakenExpireTime;\r\n let currentWeakenEvent = this.eventQueue.getMatching((event) => event.type == WeakenExpirationEvent.type && event.source == source);\r\n let weakenAmount = 0;\r\n if (currentWeakenEvent)\r\n weakenAmount = currentWeakenEvent.weakenAmount;\r\n this.eventQueue.clearMatching((event) => event.type == WeakenExpirationEvent.type && event.source == source);\r\n let weakenExpirationEvent = new WeakenExpirationEvent(this.simulationTime + weakenExpireTime, weakenAmount, source);\r\n const weakenBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/weaken\",\r\n \"typeHrid\": \"/buff_types/damage\",\r\n \"ratioBoost\": -1 * target.combatDetails.combatStats.weaken * weakenExpirationEvent.weakenAmount,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": weakenExpireTime\r\n };\r\n source.addBuff(weakenBuff);\r\n this.eventQueue.addEvent(weakenExpirationEvent);\r\n }\r\n\r\n this.simResult.addAttack(\r\n source,\r\n target,\r\n ability.hrid,\r\n attackResult.didHit ? attackResult.damageDone : \"miss\"\r\n );\r\n\r\n if (attackResult.thornDamageDone > 0) {\r\n this.simResult.addAttack(target, source, attackResult.thornType, attackResult.thornDamageDone);\r\n }\r\n if (this.zone.isDungeon && attackResult.thornDamageDone > 0 && source.isPlayer) {\r\n const log = this.buildCombatLog(target, attackResult.thornType, source, attackResult.thornDamageDone);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (target.combatDetails.combatStats.retaliation > 0) {\r\n this.simResult.addAttack(target, source, \"retaliation\", attackResult.retaliationDamageDone > 0 ? attackResult.retaliationDamageDone : \"miss\");\r\n }\r\n if (this.zone.isDungeon && attackResult.retaliationDamageDone > 0 && source.isPlayer) {\r\n const log = this.buildCombatLog(target, \"retaliation\", source, attackResult.retaliationDamageDone);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (target.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(target);\r\n this.simResult.addDeath(target);\r\n if (!target.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(target.hrid, false, this.simulationTime);\r\n }\r\n // console.log(target.hrid, \"died\");\r\n }\r\n\r\n\r\n if (attackResult.didHit && abilityEffect.pierceChance > Math.random()) {\r\n continue;\r\n }\r\n }\r\n \r\n if (parryTarget)\r\n {\r\n break;\r\n }\r\n\r\n if (abilityEffect.targetType == \"enemy\") {\r\n break;\r\n }\r\n }\r\n }\r\n\r\n processAbilityHealEffect(source, ability, abilityEffect) {\r\n\r\n if (abilityEffect.targetType == \"allAllies\") {\r\n let targets = source.isPlayer ? this.players : this.enemies;\r\n for (const target of targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0)) {\r\n let amountHealed = CombatUtilities.processHeal(source, abilityEffect, target);\r\n\r\n this.simResult.addHitpointsGained(target, ability.hrid, amountHealed);\r\n }\r\n return;\r\n }\r\n\r\n if (abilityEffect.targetType == \"lowestHpAlly\") {\r\n let targets = source.isPlayer ? this.players : this.enemies;\r\n let healTarget;\r\n for (const target of targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0)) {\r\n if (!healTarget) {\r\n healTarget = target;\r\n continue;\r\n }\r\n if (target.combatDetails.currentHitpoints < healTarget.combatDetails.currentHitpoints) {\r\n healTarget = target;\r\n }\r\n }\r\n\r\n if (healTarget) {\r\n let amountHealed = CombatUtilities.processHeal(source, abilityEffect, healTarget);\r\n\r\n this.simResult.addHitpointsGained(healTarget, ability.hrid, amountHealed);\r\n }\r\n return;\r\n }\r\n\r\n if (abilityEffect.targetType != \"self\") {\r\n throw new Error(\"Unsupported target type for heal ability effect: \" + ability.hrid);\r\n }\r\n\r\n let amountHealed = CombatUtilities.processHeal(source, abilityEffect, source);\r\n\r\n this.simResult.addHitpointsGained(source, ability.hrid, amountHealed);\r\n }\r\n\r\n processAbilityReviveEffect(source, ability, abilityEffect) {\r\n if (abilityEffect.targetType != \"deadAlly\") {\r\n throw new Error(\"Unsupported target type for revive ability effect: \" + ability.hrid);\r\n }\r\n\r\n let targets = source.isPlayer ? this.players : this.enemies;\r\n let reviveTarget = targets.find((unit) => unit && unit.combatDetails.currentHitpoints <= 0);\r\n\r\n if (reviveTarget) {\r\n this.eventQueue.clearMatching((event) => event.type == PlayerRespawnEvent.type && event.hrid == reviveTarget.hrid);\r\n\r\n reviveTarget.removeExpiredBuffs(this.simulationTime);\r\n\r\n let amountHealed = CombatUtilities.processRevive(source, abilityEffect, reviveTarget);\r\n\r\n this.simResult.addHitpointsGained(reviveTarget, ability.hrid, amountHealed);\r\n\r\n this.addNextAttackEvent(reviveTarget);\r\n\r\n if (!source.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(reviveTarget.hrid, true, this.simulationTime);\r\n }\r\n\r\n // console.log(source.hrid + \" revived \" + reviveTarget.hrid + \" with \" + amountHealed + \" HP.\" + ' at wave #' + (this.zone.encountersKilled - 1) + ' at time ' + this.simulationTime / 1000000000 + 's');\r\n }\r\n return;\r\n }\r\n\r\n processAbilityPromoteEffect(source, ability, abilityEffect) {\r\n const promotionHrids = [\"/monsters/enchanted_rook\", \"/monsters/enchanted_knight\", \"/monsters/enchanted_bishop\"];\r\n let randomPromotionIndex = Math.floor(Math.random() * promotionHrids.length);\r\n return new Monster(promotionHrids[randomPromotionIndex], source.difficultyTier);\r\n }\r\n\r\n processAbilitySpendHpEffect(source, ability, abilityEffect) {\r\n if (abilityEffect.targetType != \"self\") {\r\n throw new Error(\"Unsupported target type for spend hp ability effect: \" + ability.hrid);\r\n }\r\n\r\n let hpSpent = CombatUtilities.processSpendHp(source, abilityEffect);\r\n\r\n this.simResult.addHitpointsSpent(source, ability.hrid, hpSpent);\r\n }\r\n}\r\n\r\nexport default CombatSimulator;\r\n","class CombatUnit {\r\n isPlayer;\r\n isStunned = false;\r\n stunExpireTime = null;\r\n isBlinded = false;\r\n blindExpireTime = null;\r\n isSilenced = false;\r\n silenceExpireTime = null;\r\n\r\n isOutOfMana = false;\r\n\r\n // Base levels which don't change after initialization\r\n staminaLevel = 1;\r\n intelligenceLevel = 1;\r\n attackLevel = 1;\r\n meleeLevel = 1;\r\n defenseLevel = 1;\r\n rangedLevel = 1;\r\n magicLevel = 1;\r\n\r\n experience = 0;\r\n experienceRate = 0;\r\n enrageTime = 0;\r\n\r\n abilities = [null, null, null, null];\r\n food = [null, null, null];\r\n drinks = [null, null, null];\r\n houseRooms = [];\r\n achievements = null;\r\n dropTable = [];\r\n rareDropTable = [];\r\n abilityManaCosts = new Map();\r\n\r\n // Calculated combat stats including temporary buffs\r\n combatDetails = {\r\n staminaLevel: 1,\r\n intelligenceLevel: 1,\r\n attackLevel: 1,\r\n meleeLevel: 1,\r\n defenseLevel: 1,\r\n rangedLevel: 1,\r\n magicLevel: 1,\r\n maxHitpoints: 110,\r\n currentHitpoints: 110,\r\n maxManapoints: 110,\r\n currentManapoints: 110,\r\n stabAccuracyRating: 11,\r\n slashAccuracyRating: 11,\r\n smashAccuracyRating: 11,\r\n rangedAccuracyRating: 11,\r\n magicAccuracyRating: 11,\r\n stabMaxDamage: 11,\r\n slashMaxDamage: 11,\r\n smashMaxDamage: 11,\r\n rangedMaxDamage: 11,\r\n magicMaxDamage: 11,\r\n stabEvasionRating: 11,\r\n slashEvasionRating: 11,\r\n smashEvasionRating: 11,\r\n rangedEvasionRating: 11,\r\n magicEvasionRating: 11,\r\n defensiveMaxDamage: 0,\r\n totalArmor: 0.2,\r\n totalWaterResistance: 0.4,\r\n totalNatureResistance: 0.4,\r\n totalFireResistance: 0.4,\r\n abilityHaste: 0,\r\n tenacity: 0,\r\n totalThreat: 100,\r\n combatStats: {\r\n combatStyleHrid: \"/combat_styles/smash\",\r\n damageType: \"/damage_types/physical\",\r\n attackInterval: 3000000000,\r\n autoAttackDamage: 0,\r\n abilityDamage: 0,\r\n criticalRate: 0,\r\n criticalDamage: 0,\r\n stabAccuracy: 0,\r\n slashAccuracy: 0,\r\n smashAccuracy: 0,\r\n rangedAccuracy: 0,\r\n magicAccuracy: 0,\r\n stabDamage: 0,\r\n slashDamage: 0,\r\n smashDamage: 0,\r\n rangedDamage: 0,\r\n magicDamage: 0,\r\n defensiveDamage: 0,\r\n taskDamage: 0,\r\n physicalAmplify: 0,\r\n waterAmplify: 0,\r\n natureAmplify: 0,\r\n fireAmplify: 0,\r\n healingAmplify: 0,\r\n physicalThorns: 0,\r\n elementalThorns: 0,\r\n maxHitpoints: 0,\r\n maxManapoints: 0,\r\n stabEvasion: 0,\r\n slashEvasion: 0,\r\n smashEvasion: 0,\r\n rangedEvasion: 0,\r\n magicEvasion: 0,\r\n armor: 0,\r\n waterResistance: 0,\r\n natureResistance: 0,\r\n fireResistance: 0,\r\n lifeSteal: 0,\r\n hpRegenPer10: 0.01,\r\n mpRegenPer10: 0.01,\r\n combatDropRate: 0,\r\n combatDropQuantity: 0,\r\n combatRareFind: 0,\r\n combatExperience: 0,\r\n foodSlots: 1,\r\n drinkSlots: 1,\r\n armorPenetration: 0,\r\n waterPenetration: 0,\r\n naturePenetration: 0,\r\n firePenetration: 0,\r\n manaLeech: 0,\r\n castSpeed: 0,\r\n threat: 100,\r\n parry: 0,\r\n mayhem: 0,\r\n pierce: 0,\r\n curse: 0,\r\n ripple: 0,\r\n bloom: 0,\r\n blaze: 0,\r\n weaken: 0,\r\n fury: 0,\r\n foodHaste: 0,\r\n drinkConcentration: 0,\r\n damageTaken: 0,\r\n attackSpeed: 0,\r\n armorDamageRatio: 0,\r\n hpDrainRatio: 0,\r\n primaryTraining: \"\",\r\n focusTraining: \"\",\r\n staminaExperience: 0,\r\n intelligenceExperience: 0,\r\n attackExperience: 0,\r\n defenseExperience: 0,\r\n meleeExperience: 0,\r\n rangedExperience: 0,\r\n magicExperience: 0,\r\n retaliation: 0,\r\n },\r\n };\r\n combatBuffs = {};\r\n permanentBuffs = {};\r\n zoneBuffs = {};\r\n extraBuffs = {};\r\n\r\n constructor() { }\r\n\r\n updateCombatDetails() {\r\n if (this.isPlayer) {\r\n if (this.combatDetails.combatStats.hpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01 + this.combatDetails.combatStats.hpRegenPer10;\r\n }\r\n if (this.combatDetails.combatStats.mpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01 + this.combatDetails.combatStats.mpRegenPer10;\r\n }\r\n }\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((stat) => {\r\n this.combatDetails[stat + \"Level\"] = this[stat + \"Level\"];\r\n let boosts = this.getBuffBoosts(\"/buff_types/\" + stat + \"_level\");\r\n boosts.forEach((buff) => {\r\n this.combatDetails[stat + \"Level\"] += (this[stat + \"Level\"] * buff.ratioBoost);\r\n this.combatDetails[stat + \"Level\"] += buff.flatBoost;\r\n });\r\n });\r\n\r\n this.combatDetails.maxHitpoints = Math.floor\r\n (10 * (10 + this.combatDetails.staminaLevel) + this.combatDetails.combatStats.maxHitpoints);\r\n this.combatDetails.maxManapoints = Math.floor\r\n (10 * (10 + this.combatDetails.intelligenceLevel) + this.combatDetails.combatStats.maxManapoints);\r\n\r\n let accuracyRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_accuracy\").ratioBoost;\r\n let damageRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_damage\").ratioBoost;\r\n // if (accuracyRatioBoostFromFury > 0) {\r\n // console.log(\"Fury Boost: \" + accuracyRatioBoostFromFury);\r\n // }\r\n\r\n let accuracyRatioBoost = this.getBuffBoost(\"/buff_types/accuracy\").ratioBoost;\r\n let damageRatioBoost = this.getBuffBoost(\"/buff_types/damage\").ratioBoost;\r\n\r\n [\"stab\", \"slash\", \"smash\"].forEach((style) => {\r\n this.combatDetails[style + \"AccuracyRating\"] =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Accuracy\"]) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails[style + \"MaxDamage\"] =\r\n (10 + this.combatDetails.meleeLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Damage\"]) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n let baseEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats[style + \"Evasion\"]);\r\n this.combatDetails[style + \"EvasionRating\"] = baseEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails[style + \"EvasionRating\"] += boost.flatBoost;\r\n this.combatDetails[style + \"EvasionRating\"] += baseEvasion * boost.ratioBoost;\r\n }\r\n });\r\n\r\n this.combatDetails.defensiveMaxDamage = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.defensiveDamage);\r\n\r\n // when equiped bulwark\r\n if (this.equipment?.['/equipment_types/two_hand']?.hrid.includes(\"bulwark\")) {\r\n this.combatDetails.smashMaxDamage += this.combatDetails.defensiveMaxDamage;\r\n }\r\n\r\n this.combatDetails.rangedAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.rangedAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.rangedMaxDamage =\r\n (10 + this.combatDetails.rangedLevel) *\r\n (1 + this.combatDetails.combatStats.rangedDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseRangedEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.rangedEvasion);\r\n this.combatDetails.rangedEvasionRating = baseRangedEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.rangedEvasionRating += boost.flatBoost;\r\n this.combatDetails.rangedEvasionRating += baseRangedEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.damageTaken = this.getBuffBoost(\"/buff_types/damage_taken\").flatBoost;\r\n // if (this.combatDetails.combatStats.damageTaken > 0) {\r\n // console.log(\"Damage taken: \" + this.combatDetails.combatStats.damageTaken);\r\n // }\r\n\r\n this.combatDetails.magicAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.magicAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.magicMaxDamage =\r\n (10 + this.combatDetails.magicLevel) *\r\n (1 + this.combatDetails.combatStats.magicDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseMagicEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.magicEvasion);\r\n this.combatDetails.magicEvasionRating = baseMagicEvasion;\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.magicEvasionRating += boost.flatBoost;\r\n this.combatDetails.magicEvasionRating += baseMagicEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.physicalAmplify += this.getBuffBoost(\"/buff_types/physical_amplify\").flatBoost;\r\n this.combatDetails.combatStats.waterAmplify += this.getBuffBoost(\"/buff_types/water_amplify\").flatBoost;\r\n this.combatDetails.combatStats.natureAmplify += this.getBuffBoost(\"/buff_types/nature_amplify\").flatBoost;\r\n this.combatDetails.combatStats.fireAmplify += this.getBuffBoost(\"/buff_types/fire_amplify\").flatBoost;\r\n\r\n this.combatDetails.combatStats.attackInterval /= (1 + (this.combatDetails.attackLevel / 2000));\r\n\r\n let baseAttackSpeed = this.combatDetails.combatStats.attackSpeed;\r\n this.combatDetails.combatStats.attackInterval /= (1 + baseAttackSpeed);\r\n let attackIntervalBoosts = this.getBuffBoosts(\"/buff_types/attack_speed\");\r\n let attackIntervalRatioBoost = attackIntervalBoosts\r\n .map((boost) => boost.ratioBoost)\r\n .reduce((prev, cur) => prev + cur, 0);\r\n this.combatDetails.combatStats.attackInterval /= (1 + attackIntervalRatioBoost);\r\n\r\n let baseArmor = 0.2 * this.combatDetails.defenseLevel + this.combatDetails.combatStats.armor;\r\n this.combatDetails.totalArmor = baseArmor;\r\n let armorBoosts = this.getBuffBoosts(\"/buff_types/armor\");\r\n for (const boost of armorBoosts) {\r\n this.combatDetails.totalArmor += boost.flatBoost;\r\n this.combatDetails.totalArmor += baseArmor * boost.ratioBoost;\r\n }\r\n\r\n let baseWaterResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.waterResistance;\r\n this.combatDetails.totalWaterResistance = baseWaterResistance;\r\n let waterResistanceBoosts = this.getBuffBoosts(\"/buff_types/water_resistance\");\r\n for (const boost of waterResistanceBoosts) {\r\n this.combatDetails.totalWaterResistance += boost.flatBoost;\r\n this.combatDetails.totalWaterResistance += baseWaterResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseNatureResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.natureResistance;\r\n this.combatDetails.totalNatureResistance = baseNatureResistance;\r\n let natureResistanceBoosts = this.getBuffBoosts(\"/buff_types/nature_resistance\");\r\n for (const boost of natureResistanceBoosts) {\r\n this.combatDetails.totalNatureResistance += boost.flatBoost;\r\n this.combatDetails.totalNatureResistance += baseNatureResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseFireResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.fireResistance;\r\n this.combatDetails.totalFireResistance = baseFireResistance;\r\n let fireResistanceBoosts = this.getBuffBoosts(\"/buff_types/fire_resistance\");\r\n for (const boost of fireResistanceBoosts) {\r\n this.combatDetails.totalFireResistance += boost.flatBoost;\r\n this.combatDetails.totalFireResistance += baseFireResistance * boost.ratioBoost;\r\n }\r\n\r\n let hpRegenBoosts = this.getBuffBoost(\"/buff_types/hp_regen\");\r\n this.combatDetails.combatStats.hpRegenPer10 += this.combatDetails.combatStats.hpRegenPer10 * hpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.hpRegenPer10 += hpRegenBoosts.flatBoost;\r\n\r\n let mpRegenBoosts = this.getBuffBoost(\"/buff_types/mp_regen\");\r\n this.combatDetails.combatStats.mpRegenPer10 += this.combatDetails.combatStats.mpRegenPer10 * mpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.mpRegenPer10 += mpRegenBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.lifeSteal += this.getBuffBoost(\"/buff_types/life_steal\").flatBoost;\r\n this.combatDetails.combatStats.physicalThorns += this.getBuffBoost(\r\n \"/buff_types/physical_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.elementalThorns += this.getBuffBoost(\r\n \"/buff_types/elemental_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.combatExperience += this.getBuffBoost(\"/buff_types/wisdom\").flatBoost;\r\n this.combatDetails.combatStats.criticalRate += this.getBuffBoost(\"/buff_types/critical_rate\").flatBoost;\r\n this.combatDetails.combatStats.criticalDamage += this.getBuffBoost(\"/buff_types/critical_damage\").flatBoost;\r\n\r\n this.combatDetails.combatStats.castSpeed += this.getBuffBoost(\"/buff_types/cast_speed\").flatBoost;\r\n this.combatDetails.combatStats.castSpeed += this.combatDetails[\"attackLevel\"] / 2000;\r\n\r\n let combatDropRateBoosts = this.getBuffBoost(\"/buff_types/combat_drop_rate\");\r\n this.combatDetails.combatStats.combatDropRate += (1 + this.combatDetails.combatStats.combatDropRate) * combatDropRateBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropRate += combatDropRateBoosts.flatBoost;\r\n let combatRareFindBoosts = this.getBuffBoost(\"/buff_types/rare_find\");\r\n this.combatDetails.combatStats.combatRareFind += (1 + this.combatDetails.combatStats.combatRareFind) * combatRareFindBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatRareFind += combatRareFindBoosts.flatBoost;\r\n let combatDropQuantityBoosts = this.getBuffBoost(\"/buff_types/combat_drop_quantity\");\r\n this.combatDetails.combatStats.combatDropQuantity += (1 + this.combatDetails.combatStats.combatDropQuantity) * combatDropQuantityBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropQuantity += combatDropQuantityBoosts.flatBoost;\r\n\r\n let baseThreat = 100 + this.combatDetails.combatStats.threat;\r\n this.combatDetails.totalThreat = baseThreat;\r\n let threatBoosts = this.getBuffBoost(\"/buff_types/threat\");\r\n if (threatBoosts.ratioBoost !== 0) {\r\n this.combatDetails.combatStats.threat += baseThreat * threatBoosts.ratioBoost;\r\n } else {\r\n this.combatDetails.combatStats.threat = baseThreat;\r\n }\r\n this.combatDetails.combatStats.threat += threatBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.retaliation += this.getBuffBoost(\"/buff_types/retaliation\").flatBoost;\r\n }\r\n\r\n addBuff(buff, currentTime) {\r\n buff.startTime = currentTime;\r\n this.combatBuffs[buff.uniqueHrid] = buff;\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n removeBuff(buff) {\r\n if (!this.combatBuffs[buff.uniqueHrid]) {\r\n return;\r\n }\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n addPermanentBuff(buff) {\r\n if (this.permanentBuffs[buff.typeHrid]) {\r\n this.permanentBuffs[buff.typeHrid].flatBoost += buff.flatBoost;\r\n this.permanentBuffs[buff.typeHrid].ratioBoost += buff.ratioBoost;\r\n } else {\r\n this.permanentBuffs[buff.typeHrid] = buff;\r\n }\r\n }\r\n\r\n generatePermanentBuffs() {\r\n for (let i = 0; i < this.houseRooms.length; i++) {\r\n const houseRoom = this.houseRooms[i];\r\n houseRoom.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n\r\n if (this.achievements) {\r\n this.achievements.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.zoneBuffs) {\r\n this.zoneBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.extraBuffs) {\r\n this.extraBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n }\r\n\r\n removeExpiredBuffs(currentTime) {\r\n let expiredBuffs = Object.values(this.combatBuffs).filter(\r\n (buff) => buff.startTime + buff.duration <= currentTime\r\n );\r\n expiredBuffs.forEach((buff) => {\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n });\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearBuffs() {\r\n this.combatBuffs = structuredClone(this.permanentBuffs);\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearCCs() {\r\n this.isStunned = false;\r\n this.stunExpireTime = null;\r\n this.isSilenced = false;\r\n this.silenceExpireTime = null;\r\n this.isBlinded = false;\r\n this.blindExpireTime = null;\r\n this.combatDetails.combatStats.damageTaken = 0;\r\n }\r\n\r\n getBuffBoosts(type) {\r\n let boosts = [];\r\n Object.values(this.combatBuffs)\r\n .filter((buff) => buff.typeHrid == type)\r\n .forEach((buff) => {\r\n boosts.push({ ratioBoost: buff.ratioBoost, flatBoost: buff.flatBoost });\r\n });\r\n\r\n return boosts;\r\n }\r\n\r\n getBuffBoost(type) {\r\n let boosts = this.getBuffBoosts(type);\r\n\r\n let boost = {\r\n ratioBoost: 0,\r\n flatBoost: 0,\r\n };\r\n\r\n for (let i = 0; i < boosts.length; i++) {\r\n boost.ratioBoost += boosts[i]?.ratioBoost ?? 0;\r\n boost.flatBoost += boosts[i]?.flatBoost ?? 0;\r\n }\r\n\r\n return boost;\r\n }\r\n\r\n reset(currentTime = 0) {\r\n this.clearCCs();\r\n this.clearBuffs();\r\n this.updateCombatDetails();\r\n this.resetCooldowns(currentTime);\r\n\r\n this.combatDetails.currentHitpoints = this.combatDetails.maxHitpoints;\r\n this.combatDetails.currentManapoints = this.combatDetails.maxManapoints;\r\n }\r\n\r\n resetCooldowns(currentTime = 0) {\r\n this.food.filter((food) => food != null).forEach((food) => (food.lastUsed = Number.MIN_SAFE_INTEGER));\r\n this.drinks.filter((drink) => drink != null).forEach((drink) => (drink.lastUsed = Number.MIN_SAFE_INTEGER));\r\n\r\n let haste = this.combatDetails.combatStats.abilityHaste;\r\n\r\n this.abilities\r\n .filter((ability) => ability != null)\r\n .forEach((ability) => {\r\n if (this.isPlayer) {\r\n ability.lastUsed = Number.MIN_SAFE_INTEGER;\r\n } else {\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n ability.lastUsed = currentTime - Math.floor(cooldownDuration * 0.5) + Math.floor(Math.random() * cooldownDuration * 0.5);\r\n }\r\n });\r\n }\r\n\r\n addHitpoints(hitpoints) {\r\n let hitpointsAdded = 0;\r\n\r\n if (this.combatDetails.currentHitpoints >= this.combatDetails.maxHitpoints) {\r\n return hitpointsAdded;\r\n }\r\n\r\n let newHitpoints = Math.min(this.combatDetails.currentHitpoints + hitpoints, this.combatDetails.maxHitpoints);\r\n hitpointsAdded = newHitpoints - this.combatDetails.currentHitpoints;\r\n this.combatDetails.currentHitpoints = newHitpoints;\r\n\r\n return hitpointsAdded;\r\n }\r\n\r\n addManapoints(manapoints) {\r\n let manapointsAdded = 0;\r\n\r\n if (this.combatDetails.currentManapoints >= this.combatDetails.maxManapoints) {\r\n return manapointsAdded;\r\n }\r\n\r\n let newManapoints = Math.min(\r\n this.combatDetails.currentManapoints + manapoints,\r\n this.combatDetails.maxManapoints\r\n );\r\n manapointsAdded = newManapoints - this.combatDetails.currentManapoints;\r\n this.combatDetails.currentManapoints = newManapoints;\r\n\r\n return manapointsAdded;\r\n }\r\n}\r\n\r\nexport default CombatUnit;\r\n","class CombatUtilities {\r\n static getTarget(enemies) {\r\n if (!enemies) {\r\n return null;\r\n }\r\n let target = enemies.find((enemy) => enemy.combatDetails.currentHitpoints > 0);\r\n\r\n return target ?? null;\r\n }\r\n\r\n static randomInt(min, max) {\r\n if (max < min) {\r\n let temp = min;\r\n min = max;\r\n max = temp;\r\n }\r\n\r\n let minCeil = Math.ceil(min);\r\n let maxFloor = Math.floor(max);\r\n\r\n if (Math.floor(min) == maxFloor) {\r\n return Math.floor((min + max) / 2 + Math.random());\r\n }\r\n\r\n let minTail = -1 * (min - minCeil);\r\n let maxTail = max - maxFloor;\r\n\r\n let balancedWeight = 2 * minTail + (maxFloor - minCeil);\r\n let balancedAverage = (maxFloor + minCeil) / 2;\r\n let average = (max + min) / 2;\r\n let extraTailWeight = (balancedWeight * (average - balancedAverage)) / (maxFloor + 1 - average);\r\n let extraTailChance = Math.abs(extraTailWeight / (extraTailWeight + balancedWeight));\r\n\r\n if (Math.random() < extraTailChance) {\r\n if (maxTail > minTail) {\r\n return Math.floor(maxFloor + 1);\r\n } else {\r\n return Math.floor(minCeil - 1);\r\n }\r\n }\r\n\r\n if (maxTail > minTail) {\r\n return Math.floor(min + Math.random() * (maxFloor + minTail - min + 1));\r\n } else {\r\n return Math.floor(minCeil - maxTail + Math.random() * (max - (minCeil - maxTail) + 1));\r\n }\r\n }\r\n\r\n static processAttack(source, target, abilityEffect = null) {\r\n let combatStyle = abilityEffect\r\n ? abilityEffect.combatStyleHrid\r\n : source.combatDetails.combatStats.combatStyleHrid;\r\n let damageType = abilityEffect ? abilityEffect.damageType : source.combatDetails.combatStats.damageType;\r\n\r\n let sourceAccuracyRating = 1;\r\n let sourceAutoAttackMaxDamage = 1;\r\n let targetEvasionRating = 1;\r\n\r\n switch (combatStyle) {\r\n case \"/combat_styles/stab\":\r\n sourceAccuracyRating = source.combatDetails.stabAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.stabMaxDamage;\r\n targetEvasionRating = target.combatDetails.stabEvasionRating;\r\n break;\r\n case \"/combat_styles/slash\":\r\n sourceAccuracyRating = source.combatDetails.slashAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.slashMaxDamage;\r\n targetEvasionRating = target.combatDetails.slashEvasionRating;\r\n break;\r\n case \"/combat_styles/smash\":\r\n sourceAccuracyRating = source.combatDetails.smashAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.smashMaxDamage;\r\n targetEvasionRating = target.combatDetails.smashEvasionRating;\r\n break;\r\n case \"/combat_styles/ranged\":\r\n sourceAccuracyRating = source.combatDetails.rangedAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.rangedMaxDamage;\r\n targetEvasionRating = target.combatDetails.rangedEvasionRating;\r\n break;\r\n case \"/combat_styles/magic\":\r\n sourceAccuracyRating = source.combatDetails.magicAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.magicMaxDamage;\r\n targetEvasionRating = target.combatDetails.magicEvasionRating;\r\n break;\r\n default:\r\n throw new Error(\"Unknown combat style: \" + combatStyle);\r\n }\r\n\r\n let sourceDamageMultiplier = 1;\r\n let sourceResistance = 0;\r\n let sourcePenetration = 0;\r\n let targetResistance = 0;\r\n let targetThornPower = 0;\r\n let targetPenetration = 0;\r\n let thornType;\r\n\r\n switch (damageType) {\r\n case \"/damage_types/physical\":\r\n sourceDamageMultiplier = 1 + source.combatDetails.combatStats.physicalAmplify;\r\n sourceResistance = source.combatDetails.totalArmor;\r\n sourcePenetration = source.combatDetails.combatStats.armorPenetration;\r\n targetResistance = target.combatDetails.totalArmor;\r\n targetThornPower = target.combatDetails.combatStats.physicalThorns;\r\n targetPenetration = target.combatDetails.combatStats.armorPenetration;\r\n thornType = \"physicalThorns\";\r\n break;\r\n case \"/damage_types/water\":\r\n sourceDamageMultiplier = 1 + source.combatDetails.combatStats.waterAmplify;\r\n sourceResistance = source.combatDetails.totalWaterResistance;\r\n sourcePenetration = source.combatDetails.combatStats.waterPenetration;\r\n targetResistance = target.combatDetails.totalWaterResistance;\r\n targetThornPower = target.combatDetails.combatStats.elementalThorns;\r\n targetPenetration = target.combatDetails.combatStats.waterPenetration;\r\n thornType = \"elementalThorns\";\r\n break;\r\n case \"/damage_types/nature\":\r\n sourceDamageMultiplier = 1 + source.combatDetails.combatStats.natureAmplify;\r\n sourceResistance = source.combatDetails.totalNatureResistance;\r\n sourcePenetration = source.combatDetails.combatStats.naturePenetration;\r\n targetResistance = target.combatDetails.totalNatureResistance;\r\n targetThornPower = target.combatDetails.combatStats.elementalThorns;\r\n targetPenetration = target.combatDetails.combatStats.naturePenetration;\r\n thornType = \"elementalThorns\";\r\n break;\r\n case \"/damage_types/fire\":\r\n sourceDamageMultiplier = 1 + source.combatDetails.combatStats.fireAmplify;\r\n sourceResistance = source.combatDetails.totalFireResistance;\r\n sourcePenetration = source.combatDetails.combatStats.firePenetration;\r\n targetResistance = target.combatDetails.totalFireResistance;\r\n targetThornPower = target.combatDetails.combatStats.elementalThorns;\r\n targetPenetration = target.combatDetails.combatStats.firePenetration;\r\n thornType = \"elementalThorns\";\r\n break;\r\n default:\r\n throw new Error(\"Unknown damage type: \" + damageType);\r\n }\r\n\r\n let hitChance = 1;\r\n let critChance = 0;\r\n let isCrit = false;\r\n let bonusCritChance = source.combatDetails.combatStats.criticalRate;\r\n let bonusCritDamage = source.combatDetails.combatStats.criticalDamage;\r\n\r\n if (abilityEffect) {\r\n sourceAccuracyRating *= (1 + abilityEffect.bonusAccuracyRatio);\r\n }\r\n\r\n if (source.isWeakened) {\r\n sourceAccuracyRating = sourceAccuracyRating - (source.weakenPercentage * sourceAccuracyRating);\r\n }\r\n\r\n hitChance =\r\n Math.pow(sourceAccuracyRating, 1.4) /\r\n (Math.pow(sourceAccuracyRating, 1.4) + Math.pow(targetEvasionRating, 1.4));\r\n\r\n if (combatStyle == \"/combat_styles/ranged\") {\r\n critChance = 0.3 * hitChance;\r\n }\r\n\r\n critChance = critChance + bonusCritChance;\r\n\r\n let baseDamageFlat = abilityEffect ? abilityEffect.damageFlat : 0;\r\n let baseDamageRatio = abilityEffect ? abilityEffect.damageRatio : 1;\r\n\r\n let armorDamageRatioFlat = abilityEffect ? abilityEffect.armorDamageRatio * source.combatDetails.totalArmor : 0;\r\n\r\n let sourceMinDamage = sourceDamageMultiplier * (1 + baseDamageFlat + armorDamageRatioFlat);\r\n let sourceMaxDamage = sourceDamageMultiplier * (baseDamageRatio * sourceAutoAttackMaxDamage + baseDamageFlat + armorDamageRatioFlat);\r\n\r\n if (Math.random() < critChance) {\r\n sourceMaxDamage = sourceMaxDamage * (1 + bonusCritDamage);\r\n sourceMinDamage = sourceMaxDamage;\r\n isCrit = true;\r\n }\r\n\r\n let damageRoll = CombatUtilities.randomInt(sourceMinDamage, sourceMaxDamage);\r\n damageRoll *= (1 + source.combatDetails.combatStats.taskDamage);\r\n damageRoll *= (1 + target.combatDetails.combatStats.damageTaken);\r\n if (!abilityEffect) {\r\n damageRoll += damageRoll * source.combatDetails.combatStats.autoAttackDamage;\r\n } else {\r\n damageRoll *= (1 + source.combatDetails.combatStats.abilityDamage);\r\n }\r\n\r\n let damageDone = 0;\r\n let thornDamageDone = 0;\r\n\r\n let didHit = false;\r\n if (Math.random() < hitChance) {\r\n didHit = true;\r\n let penetratedTargetResistance = targetResistance;\r\n\r\n if (sourcePenetration > 0 && targetResistance > 0) {\r\n penetratedTargetResistance = targetResistance / (1 + sourcePenetration);\r\n }\r\n\r\n let targetDamageTakenRatio = 100 / (100 + penetratedTargetResistance);\r\n if (penetratedTargetResistance < 0) {\r\n targetDamageTakenRatio = (100 - penetratedTargetResistance) / 100;\r\n }\r\n\r\n let mitigatedDamage = Math.ceil(targetDamageTakenRatio * damageRoll);\r\n damageDone = Math.min(mitigatedDamage, target.combatDetails.currentHitpoints);\r\n target.combatDetails.currentHitpoints -= damageDone;\r\n }\r\n\r\n if (targetThornPower > 0.0 && targetResistance > -99.0) {\r\n let penetratedSourceResistance = sourceResistance\r\n\r\n if (sourceResistance > 0) {\r\n penetratedSourceResistance = sourceResistance / (1 + targetPenetration);\r\n }\r\n\r\n let sourceDamageTakenRatio = 100.0 / (100 + penetratedSourceResistance);\r\n if (penetratedSourceResistance < 0) {\r\n sourceDamageTakenRatio = (100 - penetratedSourceResistance) / 100;\r\n }\r\n\r\n let targetTaskDamageMultiplier = 1.0 + target.combatDetails.combatStats.taskDamage;\r\n let sourceDamageTakenMultiplier = 1.0 + source.combatDetails.combatStats.damageTaken;\r\n let targetDamageMultiplier = targetTaskDamageMultiplier * sourceDamageTakenMultiplier;\r\n\r\n let thornsDamageRoll = CombatUtilities.randomInt(1,\r\n targetDamageMultiplier\r\n * target.combatDetails.defensiveMaxDamage\r\n * (1.0 + targetResistance / 100.0)\r\n * targetThornPower);\r\n\r\n let mitigatedThornsDamage = Math.ceil(sourceDamageTakenRatio * thornsDamageRoll);\r\n\r\n thornDamageDone = Math.min(mitigatedThornsDamage, source.combatDetails.currentHitpoints);\r\n source.combatDetails.currentHitpoints -= thornDamageDone;\r\n }\r\n\r\n let retaliationDamageDone = 0;\r\n if (target.combatDetails.combatStats.retaliation > 0) {\r\n let retaliationHitChance = \r\n Math.pow(target.combatDetails.smashAccuracyRating, 1.4) /\r\n (Math.pow(target.combatDetails.smashAccuracyRating, 1.4) + Math.pow(source.combatDetails.smashEvasionRating, 1.4));\r\n\r\n if (retaliationHitChance > Math.random()) {\r\n let sourceEffectiveArmor = source.combatDetails.totalArmor;\r\n if (sourceEffectiveArmor > 0) {\r\n sourceEffectiveArmor = sourceEffectiveArmor / (1.0 + target.combatDetails.combatStats.armorPenetration);\r\n }\r\n\r\n let sourceDamageTakenRatio = 100.0 / (100.0 + sourceEffectiveArmor);\r\n if (sourceEffectiveArmor < 0) {\r\n sourceDamageTakenRatio = (100.0 - sourceEffectiveArmor) / 100.0;\r\n }\r\n\r\n let targetTaskDamageMultiplier = 1.0 + target.combatDetails.combatStats.taskDamage;\r\n let sourceDamageTakenMultiplier = 1.0 + source.combatDetails.combatStats.damageTaken;\r\n let retaliationDamageMultiplier = targetTaskDamageMultiplier * sourceDamageTakenMultiplier;\r\n\r\n let premitigatedDamage = damageRoll;\r\n premitigatedDamage = Math.min(premitigatedDamage, target.combatDetails.defensiveMaxDamage * 5);\r\n\r\n let retaliationMinDamage = retaliationDamageMultiplier * target.combatDetails.combatStats.retaliation * premitigatedDamage;\r\n let retaliationMaxDamage = retaliationDamageMultiplier * target.combatDetails.combatStats.retaliation * (target.combatDetails.defensiveMaxDamage + premitigatedDamage);\r\n\r\n let retaliationDamageRoll = CombatUtilities.randomInt(retaliationMinDamage, retaliationMaxDamage);\r\n let mitigatedRetaliationDamage = Math.ceil(sourceDamageTakenRatio * retaliationDamageRoll);\r\n retaliationDamageDone = Math.min(mitigatedRetaliationDamage, source.combatDetails.currentHitpoints);\r\n source.combatDetails.currentHitpoints -= retaliationDamageDone;\r\n }\r\n }\r\n\r\n let lifeStealHeal = 0;\r\n if (!abilityEffect && didHit && source.combatDetails.combatStats.lifeSteal > 0) {\r\n lifeStealHeal = source.addHitpoints(Math.floor(source.combatDetails.combatStats.lifeSteal * damageDone));\r\n }\r\n\r\n let hpDrain = 0;\r\n if (abilityEffect && didHit && abilityEffect.hpDrainRatio > 0) {\r\n let healingAmplify = 1 + source.combatDetails.combatStats.healingAmplify;\r\n hpDrain = source.addHitpoints(Math.floor(abilityEffect.hpDrainRatio * damageDone * healingAmplify));\r\n }\r\n\r\n let manaLeechMana = 0;\r\n if (!abilityEffect && didHit && source.combatDetails.combatStats.manaLeech > 0) {\r\n manaLeechMana = source.addManapoints(Math.floor(source.combatDetails.combatStats.manaLeech * damageDone));\r\n }\r\n\r\n return { damageDone, didHit, thornDamageDone, thornType, retaliationDamageDone, lifeStealHeal, hpDrain, manaLeechMana, isCrit};\r\n }\r\n\r\n static processHeal(source, abilityEffect, target) {\r\n if (abilityEffect.combatStyleHrid != \"/combat_styles/magic\") {\r\n throw new Error(\"Heal ability effect not supported for combat style: \" + abilityEffect.combatStyleHrid);\r\n }\r\n\r\n let healingAmplify = 1 + source.combatDetails.combatStats.healingAmplify;\r\n let magicMaxDamage = source.combatDetails.magicMaxDamage;\r\n\r\n let baseHealFlat = abilityEffect.damageFlat;\r\n let baseHealRatio = abilityEffect.damageRatio;\r\n\r\n let minHeal = healingAmplify * (1 + baseHealFlat);\r\n let maxHeal = healingAmplify * (baseHealRatio * magicMaxDamage + baseHealFlat);\r\n\r\n let heal = this.randomInt(minHeal, maxHeal);\r\n let amountHealed = target.addHitpoints(heal);\r\n\r\n return amountHealed;\r\n }\r\n\r\n static processRevive(source, abilityEffect, target) {\r\n if (abilityEffect.combatStyleHrid != \"/combat_styles/magic\") {\r\n throw new Error(\"Heal ability effect not supported for combat style: \" + abilityEffect.combatStyleHrid);\r\n }\r\n\r\n let healingAmplify = 1 + source.combatDetails.combatStats.healingAmplify;\r\n let magicMaxDamage = source.combatDetails.magicMaxDamage;\r\n\r\n let baseHealFlat = abilityEffect.damageFlat;\r\n let baseHealRatio = abilityEffect.damageRatio;\r\n\r\n let minHeal = healingAmplify * (1 + baseHealFlat);\r\n let maxHeal = healingAmplify * (baseHealRatio * magicMaxDamage + baseHealFlat);\r\n\r\n let heal = this.randomInt(minHeal, maxHeal);\r\n let amountHealed = target.addHitpoints(heal);\r\n target.combatDetails.currentManapoints = target.combatDetails.maxManapoints;\r\n target.clearCCs();\r\n\r\n // target.clearBuffs();\r\n\r\n return amountHealed;\r\n }\r\n\r\n static processSpendHp(source, abilityEffect) {\r\n let currentHp = source.combatDetails.currentHitpoints;\r\n let spendHpRatio = abilityEffect.spendHpRatio;\r\n\r\n let spentHp = Math.floor(currentHp * spendHpRatio);\r\n\r\n source.combatDetails.currentHitpoints -= spentHp;\r\n\r\n return spentHp;\r\n }\r\n\r\n static calculateTickValue(totalValue, totalTicks, currentTick) {\r\n let currentSum = Math.floor((currentTick * totalValue) / totalTicks);\r\n let previousSum = Math.floor(((currentTick - 1) * totalValue) / totalTicks);\r\n\r\n return currentSum - previousSum;\r\n }\r\n}\r\n\r\nexport default CombatUtilities;\r\n","import Buff from \"./buff\";\r\nimport itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nclass Consumable {\r\n constructor(hrid, triggers = null) {\r\n this.hrid = hrid;\r\n\r\n let gameConsumable = itemDetailMap[this.hrid];\r\n if (!gameConsumable) {\r\n throw new Error(\"No consumable found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.cooldownDuration = gameConsumable.consumableDetail.cooldownDuration;\r\n this.hitpointRestore = gameConsumable.consumableDetail.hitpointRestore;\r\n this.manapointRestore = gameConsumable.consumableDetail.manapointRestore;\r\n this.recoveryDuration = gameConsumable.consumableDetail.recoveryDuration;\r\n this.catagoryHrid = gameConsumable.categoryHrid;\r\n\r\n this.buffs = [];\r\n if (gameConsumable.consumableDetail.buffs) {\r\n for (const consumableBuff of gameConsumable.consumableDetail.buffs) {\r\n let buff = new Buff(consumableBuff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameConsumable.consumableDetail.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let consumable = new Consumable(dto.hrid, triggers);\r\n\r\n return consumable;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n let consumableHaste;\r\n if (this.catagoryHrid.includes(\"food\")) {\r\n consumableHaste = source.combatDetails.combatStats.foodHaste\r\n } else {\r\n consumableHaste = source.combatDetails.combatStats.drinkConcentration;\r\n }\r\n let cooldownDuration = this.cooldownDuration;\r\n if (consumableHaste > 0) {\r\n cooldownDuration = cooldownDuration / (1 + consumableHaste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Consumable;\r\n","class Drops {\r\n\r\n constructor(itemHrid, dropRate, minCount, maxCount, difficultyTier) {\r\n this.itemHrid = itemHrid;\r\n this.dropRate = dropRate;\r\n this.minCount = minCount;\r\n this.maxCount = maxCount;\r\n this.difficultyTier = difficultyTier;\r\n }\r\n}\r\n\r\nexport default Drops;\r\n","import itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport enhancementLevelTotalMultiplierTable from \"./data/enhancementLevelTotalBonusMultiplierTable.json\";\r\n\r\nclass Equipment {\r\n constructor(hrid, enhancementLevel) {\r\n this.hrid = hrid;\r\n let gameItem = itemDetailMap[this.hrid];\r\n if (!gameItem) {\r\n throw new Error(\"No equipment found for hrid: \" + this.hrid);\r\n }\r\n this.gameItem = gameItem;\r\n this.enhancementLevel = enhancementLevel;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let equipment = new Equipment(dto.hrid, dto.enhancementLevel);\r\n\r\n return equipment;\r\n }\r\n\r\n getCombatStat(combatStat) {\r\n let multiplier = enhancementLevelTotalMultiplierTable[this.enhancementLevel];\r\n if(this.gameItem.equipmentDetail.combatStats[combatStat]) {\r\n let enhancementBonus = this.gameItem.equipmentDetail.combatEnhancementBonuses[combatStat] || 0;\r\n let stat = this.gameItem.equipmentDetail.combatStats[combatStat] + multiplier * enhancementBonus;\r\n return stat;\r\n }\r\n return 0;\r\n }\r\n\r\n getCombatStyle() {\r\n return this.gameItem.equipmentDetail.combatStats.combatStyleHrids[0];\r\n }\r\n\r\n getDamageType() {\r\n return this.gameItem.equipmentDetail.combatStats.damageType;\r\n }\r\n\r\n getPrimaryTraining() {\r\n return this.gameItem.equipmentDetail.combatStats.primaryTraining;\r\n }\r\n\r\n getFocusTraining(){\r\n return this.gameItem.equipmentDetail.combatStats.focusTraining;\r\n }\r\n}\r\n\r\nexport default Equipment;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass AbilityCastEndEvent extends CombatEvent {\r\n static type = \"abilityCastEndEvent\";\r\n\r\n constructor(time, source, ability) {\r\n super(AbilityCastEndEvent.type, time);\r\n\r\n this.source = source;\r\n this.ability = ability;\r\n }\r\n}\r\n\r\nexport default AbilityCastEndEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass AutoAttackEvent extends CombatEvent {\r\n static type = \"autoAttack\";\r\n\r\n constructor(time, source) {\r\n super(AutoAttackEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default AutoAttackEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass AwaitCooldownEvent extends CombatEvent {\r\n static type = \"awaitCooldownEvent\";\r\n\r\n constructor(time, source) {\r\n super(AwaitCooldownEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default AwaitCooldownEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass BlindExpirationEvent extends CombatEvent {\r\n static type = \"blindExpiration\";\r\n\r\n constructor(time, source) {\r\n super(BlindExpirationEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default BlindExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass CheckBuffExpirationEvent extends CombatEvent {\r\n static type = \"checkBuffExpiration\";\r\n\r\n constructor(time, source) {\r\n super(CheckBuffExpirationEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default CheckBuffExpirationEvent;\r\n","class CombatEvent {\r\n constructor(type, time) {\r\n this.type = type;\r\n this.time = time;\r\n }\r\n}\r\n\r\nexport default CombatEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass CombatStartEvent extends CombatEvent {\r\n static type = \"combatStart\";\r\n\r\n constructor(time) {\r\n super(CombatStartEvent.type, time);\r\n }\r\n}\r\n\r\nexport default CombatStartEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass ConsumableTickEvent extends CombatEvent {\r\n static type = \"consumableTick\";\r\n\r\n constructor(time, source, consumable, totalTicks, currentTick) {\r\n super(ConsumableTickEvent.type, time);\r\n\r\n this.source = source;\r\n this.consumable = consumable;\r\n this.totalTicks = totalTicks;\r\n this.currentTick = currentTick;\r\n }\r\n}\r\n\r\nexport default ConsumableTickEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass CooldownReadyEvent extends CombatEvent {\r\n static type = \"cooldownReady\";\r\n\r\n constructor(time) {\r\n super(CooldownReadyEvent.type, time);\r\n }\r\n}\r\n\r\nexport default CooldownReadyEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass CurseExpirationEvent extends CombatEvent {\r\n static type = \"curseExpiration\";\r\n static maxCurseStacks = 5;\r\n\r\n constructor(time, curseAmount, source) {\r\n super(CurseExpirationEvent.type, time);\r\n\r\n this.curseAmount = Math.min(curseAmount + 1, CurseExpirationEvent.maxCurseStacks);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default CurseExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass DamageOverTimeEvent extends CombatEvent {\r\n static type = \"damageOverTime\";\r\n\r\n constructor(time, sourceRef, target, damage, totalTicks, currentTick, combatStyleHrid) {\r\n super(DamageOverTimeEvent.type, time);\r\n\r\n // Calling it 'source' would wrongly clear Damage Over Time when the source dies\r\n this.sourceRef = sourceRef;\r\n this.target = target;\r\n this.damage = damage;\r\n this.totalTicks = totalTicks;\r\n this.currentTick = currentTick;\r\n this.combatStyleHrid = combatStyleHrid;\r\n }\r\n}\r\n\r\nexport default DamageOverTimeEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass EnemyRespawnEvent extends CombatEvent {\r\n static type = \"enemyRespawn\";\r\n\r\n constructor(time) {\r\n super(EnemyRespawnEvent.type, time);\r\n }\r\n}\r\n\r\nexport default EnemyRespawnEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass EnrageTickEvent extends CombatEvent {\r\n static type = \"enrageTick\";\r\n\r\n constructor(time, encounterTime) {\r\n\r\n super(EnrageTickEvent.type, time);\r\n\r\n this.encounterTime = encounterTime;\r\n }\r\n}\r\n\r\nexport default EnrageTickEvent;\r\n","import Heap from \"heap-js\";\r\n\r\nclass EventQueue {\r\n constructor() {\r\n this.minHeap = new Heap((a, b) => a.time - b.time);\r\n }\r\n\r\n addEvent(event) {\r\n this.minHeap.push(event);\r\n }\r\n\r\n getNextEvent() {\r\n return this.minHeap.pop();\r\n }\r\n\r\n containsEventOfType(type) {\r\n let heapEvents = this.minHeap.toArray();\r\n\r\n return heapEvents.some((event) => event.type == type);\r\n }\r\n\r\n containsEventOfTypeAndHrid(type, hrid) {\r\n let heapEvents = this.minHeap.toArray();\r\n return heapEvents.some((event) => event.type == type && event.hrid == hrid);\r\n }\r\n\r\n clear() {\r\n this.minHeap = new Heap((a, b) => a.time - b.time);\r\n }\r\n\r\n clearEventsForUnit(unit) {\r\n this.clearMatching((event) => event.source == unit || event.target == unit);\r\n }\r\n\r\n clearEventsOfType(type) {\r\n this.clearMatching((event) => event.type == type);\r\n }\r\n\r\n clearMatching(fn) {\r\n let cleared = false;\r\n let heapEvents = this.minHeap.toArray();\r\n\r\n for (const event of heapEvents) {\r\n if (fn(event)) {\r\n this.minHeap.remove(event);\r\n cleared = true;\r\n }\r\n }\r\n return cleared;\r\n }\r\n\r\n getMatching(fn) {\r\n let heapEvents = this.minHeap.toArray(); \r\n \r\n for (const event of heapEvents) {\r\n if (fn(event)) {\r\n return event; \r\n }\r\n }\r\n \r\n return null; \r\n }\r\n}\r\n\r\nexport default EventQueue;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass FuryExpirationEvent extends CombatEvent {\r\n static type = \"furyExpiration\";\r\n\r\n constructor(time, furyAmount, source) {\r\n super(FuryExpirationEvent.type, time);\r\n \r\n this.furyAmount = furyAmount;\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default FuryExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass PlayerRespawnEvent extends CombatEvent {\r\n static type = \"playerRespawn\";\r\n\r\n constructor(time, hrid) {\r\n super(PlayerRespawnEvent.type, time);\r\n this.hrid = hrid;\r\n }\r\n}\r\n\r\nexport default PlayerRespawnEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass RegenTickEvent extends CombatEvent {\r\n static type = \"regenTick\";\r\n\r\n constructor(time) {\r\n super(RegenTickEvent.type, time);\r\n }\r\n}\r\n\r\nexport default RegenTickEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass SilenceExpirationEvent extends CombatEvent {\r\n static type = \"silenceExpiration\";\r\n\r\n constructor(time, source) {\r\n super(SilenceExpirationEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default SilenceExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass StunExpirationEvent extends CombatEvent {\r\n static type = \"stunExpiration\";\r\n\r\n constructor(time, source) {\r\n super(StunExpirationEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default StunExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass WeakenExpirationEvent extends CombatEvent {\r\n static type = \"weakenExpiration\";\r\n static maxWeakenStacks = 5;\r\n\r\n constructor(time, weakenAmount, source) {\r\n super(WeakenExpirationEvent.type, time);\r\n this.weakenAmount = Math.min(\r\n weakenAmount + 1,\r\n WeakenExpirationEvent.maxWeakenStacks\r\n );\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default WeakenExpirationEvent;","import Buff from \"./buff\";\r\nimport houseRoomDetailMap from \"./data/houseRoomDetailMap.json\";\r\n\r\nclass HouseRoom {\r\n constructor(hrid, level) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameHouseRoom = houseRoomDetailMap[this.hrid];\r\n if (!gameHouseRoom) {\r\n throw new Error(\"No house room found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.buffs = [];\r\n if (gameHouseRoom.actionBuffs) {\r\n for (const actionBuff of gameHouseRoom.actionBuffs) {\r\n let buff = new Buff(actionBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n if (gameHouseRoom.globalBuffs) {\r\n for (const globalBuff of gameHouseRoom.globalBuffs) {\r\n let buff = new Buff(globalBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default HouseRoom;","import Ability from \"./ability\";\r\nimport CombatUnit from \"./combatUnit\";\r\nimport combatMonsterDetailMap from \"./data/combatMonsterDetailMap.json\";\r\nimport Drops from \"./drops\";\r\n\r\nclass Monster extends CombatUnit {\r\n\r\n difficultyTier = 0;\r\n\r\n constructor(hrid, difficultyTier = 0) {\r\n super();\r\n\r\n this.isPlayer = false;\r\n this.hrid = hrid;\r\n this.difficultyTier = difficultyTier;\r\n\r\n let gameMonster = combatMonsterDetailMap[this.hrid];\r\n if (!gameMonster) {\r\n throw new Error(\"No monster found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.enrageTime = gameMonster.enrageTime;\r\n\r\n for (let i = 0; i < gameMonster.abilities.length; i++) {\r\n if (gameMonster.abilities[i].minDifficultyTier > this.difficultyTier) {\r\n continue;\r\n }\r\n this.abilities[i] = new Ability(gameMonster.abilities[i].abilityHrid, gameMonster.abilities[i].level);\r\n }\r\n if(gameMonster.dropTable)\r\n for (let i = 0; i < gameMonster.dropTable.length; i++) {\r\n this.dropTable[i] = new Drops(gameMonster.dropTable[i].itemHrid, gameMonster.dropTable[i].dropRate, gameMonster.dropTable[i].minCount, gameMonster.dropTable[i].maxCount, gameMonster.dropTable[i].difficultyTier);\r\n }\r\n for (let i = 0; i < gameMonster.rareDropTable.length; i++) {\r\n let dropTableItem = (gameMonster.dropTable && i < gameMonster.dropTable.length) ? gameMonster.dropTable[i] : null;\r\n let difficultyTier = dropTableItem?.difficultyTier ?? gameMonster.rareDropTable[i].minDifficultyTier;\r\n\r\n this.rareDropTable[i] = new Drops(gameMonster.rareDropTable[i].itemHrid, gameMonster.rareDropTable[i].dropRate, gameMonster.rareDropTable[i].minCount, difficultyTier);\r\n }\r\n }\r\n\r\n updateCombatDetails() {\r\n let gameMonster = combatMonsterDetailMap[this.hrid];\r\n\r\n let levelMultiplier = 1.0 + 0.25 * this.difficultyTier;\r\n let defLevelMultiplier = 1.0 + 0.15 * this.difficultyTier;\r\n let levelBonus = 20.0 * this.difficultyTier;\r\n\r\n this.staminaLevel = levelMultiplier * (gameMonster.combatDetails.staminaLevel + levelBonus);\r\n this.intelligenceLevel = levelMultiplier * (gameMonster.combatDetails.intelligenceLevel + levelBonus);\r\n this.attackLevel = levelMultiplier * (gameMonster.combatDetails.attackLevel + levelBonus);\r\n this.meleeLevel = levelMultiplier * (gameMonster.combatDetails.meleeLevel + levelBonus);\r\n this.defenseLevel = defLevelMultiplier * (gameMonster.combatDetails.defenseLevel + levelBonus);\r\n this.rangedLevel = levelMultiplier * (gameMonster.combatDetails.rangedLevel + levelBonus);\r\n this.magicLevel = levelMultiplier * (gameMonster.combatDetails.magicLevel + levelBonus);\r\n\r\n \r\n let expMultiplier = 1.0 + 0.5 * this.difficultyTier;\r\n let expBonus = 5.0 * this.difficultyTier;\r\n\r\n this.experience = expMultiplier * (gameMonster.experience + expBonus);\r\n\r\n this.combatDetails.combatStats.combatStyleHrid = gameMonster.combatDetails.combatStats.combatStyleHrids[0];\r\n\r\n for (const [key, value] of Object.entries(gameMonster.combatDetails.combatStats)) {\r\n this.combatDetails.combatStats[key] = value;\r\n }\r\n\r\n [\r\n \"stabAccuracy\",\r\n \"slashAccuracy\",\r\n \"smashAccuracy\",\r\n \"rangedAccuracy\",\r\n \"magicAccuracy\",\r\n \"stabDamage\",\r\n \"slashDamage\",\r\n \"smashDamage\",\r\n \"rangedDamage\",\r\n \"magicDamage\",\r\n \"defensiveDamage\",\r\n \"taskDamage\",\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"stabEvasion\",\r\n \"slashEvasion\",\r\n \"smashEvasion\",\r\n \"rangedEvasion\",\r\n \"magicEvasion\",\r\n \"armor\",\r\n \"waterResistance\",\r\n \"natureResistance\",\r\n \"fireResistance\",\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"combatDropRate\",\r\n \"combatRareFind\",\r\n \"combatDropQuantity\",\r\n \"combatExperience\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"abilityHaste\",\r\n \"tenacity\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"threat\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"foodHaste\",\r\n \"drinkConcentration\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"retaliation\"\r\n ].forEach((stat) => {\r\n if (gameMonster.combatDetails.combatStats[stat] == null) {\r\n this.combatDetails.combatStats[stat] = 0;\r\n }\r\n });\r\n\r\n if (this.combatDetails.combatStats.attackInterval == 0) {\r\n this.combatDetails.combatStats.attackInterval = gameMonster.combatDetails.attackInterval;\r\n }\r\n\r\n super.updateCombatDetails();\r\n }\r\n}\r\n\r\nexport default Monster;\r\n","import Ability from \"./ability\";\r\nimport CombatUnit from \"./combatUnit\";\r\nimport Consumable from \"./consumable\";\r\nimport Equipment from \"./equipment\";\r\nimport HouseRoom from \"./houseRoom\";\r\nimport Achievement from \"./achievement\";\r\n\r\nclass Player extends CombatUnit {\r\n equipment = {\r\n \"/equipment_types/head\": null,\r\n \"/equipment_types/body\": null,\r\n \"/equipment_types/legs\": null,\r\n \"/equipment_types/feet\": null,\r\n \"/equipment_types/hands\": null,\r\n \"/equipment_types/main_hand\": null,\r\n \"/equipment_types/two_hand\": null,\r\n \"/equipment_types/off_hand\": null,\r\n \"/equipment_types/pouch\": null,\r\n \"/equipment_types/back\": null,\r\n };\r\n\r\n constructor() {\r\n super();\r\n\r\n this.isPlayer = true;\r\n this.hrid = \"player\";\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let player = new Player();\r\n\r\n player.staminaLevel = dto.staminaLevel;\r\n player.intelligenceLevel = dto.intelligenceLevel;\r\n player.attackLevel = dto.attackLevel;\r\n player.meleeLevel = dto.meleeLevel;\r\n player.defenseLevel = dto.defenseLevel;\r\n player.rangedLevel = dto.rangedLevel;\r\n player.magicLevel = dto.magicLevel;\r\n\r\n player.hrid = dto.hrid;\r\n\r\n for (const [key, value] of Object.entries(dto.equipment)) {\r\n player.equipment[key] = value ? Equipment.createFromDTO(value) : null;\r\n }\r\n\r\n player.food = dto.food.map((food) => (food ? Consumable.createFromDTO(food) : null));\r\n player.drinks = dto.drinks.map((drink) => (drink ? Consumable.createFromDTO(drink) : null));\r\n player.abilities = dto.abilities.map((ability) => (ability ? Ability.createFromDTO(ability) : null));\r\n Object.entries(dto.houseRooms).forEach(houseRoom => {\r\n if (houseRoom[1] > 0) {\r\n player.houseRooms.push(new HouseRoom(houseRoom[0], houseRoom[1]))\r\n }\r\n });\r\n\r\n player.achievements = new Achievement(dto.achievements);\r\n\r\n player.debuffOnLevelGap = dto.debuffOnLevelGap;\r\n\r\n return player;\r\n }\r\n\r\n updateCombatDetails() {\r\n if (this.equipment[\"/equipment_types/main_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/main_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/main_hand\"].getPrimaryTraining();\r\n } else if (this.equipment[\"/equipment_types/two_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/two_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/two_hand\"].getPrimaryTraining();\r\n } else {\r\n this.combatDetails.combatStats.combatStyleHrid = \"/combat_styles/smash\";\r\n this.combatDetails.combatStats.damageType = \"/damage_types/physical\";\r\n this.combatDetails.combatStats.attackInterval = 3000000000;\r\n this.combatDetails.combatStats.primaryTraining = \"/skills/melee\";\r\n }\r\n\r\n if (this.equipment[\"/equipment_types/charm\"]) {\r\n this.combatDetails.combatStats.focusTraining = this.equipment[\"/equipment_types/charm\"].getFocusTraining();\r\n } else {\r\n this.combatDetails.combatStats.focusTraining = \"\";\r\n }\r\n\r\n [\r\n \"stabAccuracy\",\r\n \"slashAccuracy\",\r\n \"smashAccuracy\",\r\n \"rangedAccuracy\",\r\n \"magicAccuracy\",\r\n \"stabDamage\",\r\n \"slashDamage\",\r\n \"smashDamage\",\r\n \"rangedDamage\",\r\n \"magicDamage\",\r\n \"defensiveDamage\",\r\n \"taskDamage\",\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"stabEvasion\",\r\n \"slashEvasion\",\r\n \"smashEvasion\",\r\n \"rangedEvasion\",\r\n \"magicEvasion\",\r\n \"armor\",\r\n \"waterResistance\",\r\n \"natureResistance\",\r\n \"fireResistance\",\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"combatDropRate\",\r\n \"combatRareFind\",\r\n \"combatDropQuantity\",\r\n \"combatExperience\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"abilityHaste\",\r\n \"tenacity\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"threat\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"foodHaste\",\r\n \"drinkConcentration\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\",\r\n \"retaliation\"\r\n ].forEach((stat) => {\r\n this.combatDetails.combatStats[stat] = Object.values(this.equipment)\r\n .filter((equipment) => equipment != null)\r\n .map((equipment) => equipment.getCombatStat(stat))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n });\r\n\r\n if (this.equipment[\"/equipment_types/pouch\"]) {\r\n this.combatDetails.combatStats.foodSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"foodSlots\");\r\n this.combatDetails.combatStats.drinkSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"drinkSlots\");\r\n } else {\r\n this.combatDetails.combatStats.foodSlots = 1;\r\n this.combatDetails.combatStats.drinkSlots = 1;\r\n }\r\n\r\n super.updateCombatDetails();\r\n }\r\n}\r\n\r\nexport default Player;\r\n","import combatStyleDetailMap from \"./data/combatStyleDetailMap.json\"\r\n\r\nclass SimResult {\r\n constructor(zone, numberOfPlayers) {\r\n this.deaths = {};\r\n this.experienceGained = {};\r\n this.encounters = 0;\r\n this.attacks = {};\r\n this.consumablesUsed = {};\r\n this.hitpointsGained = {};\r\n this.manapointsGained = {};\r\n this.debuffOnLevelGap = {};\r\n this.dropRateMultiplier = {};\r\n this.rareFindMultiplier = {};\r\n this.combatDropQuantity = {};\r\n this.playerRanOutOfMana = {\r\n \"player1\": false,\r\n \"player2\": false,\r\n \"player3\": false,\r\n \"player4\": false,\r\n \"player5\": false\r\n };\r\n this.playerRanOutOfManaTime = {};\r\n this.manaUsed = {};\r\n this.timeSpentAlive = [];\r\n this.bossSpawns = [];\r\n this.hitpointsSpent = {};\r\n this.zoneName = zone.hrid;\r\n this.difficultyTier = zone.difficultyTier;\r\n this.isDungeon = false;\r\n this.dungeonsCompleted = 0;\r\n this.dungeonsFailed = 0;\r\n this.maxWaveReached = 0;\r\n this.numberOfPlayers = numberOfPlayers;\r\n this.maxEnrageStack = 0;\r\n\r\n this.wipeEvents = [];\r\n }\r\n\r\n addWipeEvent(logs, simulationTime, wave) {\r\n this.wipeEvents.push({\r\n simulationTime: simulationTime,\r\n logs: logs,\r\n wave: wave,\r\n timestamp: new Date().toISOString()\r\n });\r\n }\r\n \r\n addDeath(unit) {\r\n if (!this.deaths[unit.hrid]) {\r\n this.deaths[unit.hrid] = 0;\r\n }\r\n\r\n this.deaths[unit.hrid] += 1;\r\n }\r\n\r\n updateTimeSpentAlive(name, alive, time) {\r\n const i = this.timeSpentAlive.findIndex(e => e.name === name);\r\n if (alive) {\r\n if (i !== -1) {\r\n this.timeSpentAlive[i].alive = true;\r\n this.timeSpentAlive[i].spawnedAt = time;\r\n } else {\r\n this.timeSpentAlive.push({ name: name, timeSpentAlive: 0, spawnedAt: time, alive: true, count: 0 });\r\n }\r\n } else {\r\n const timeAlive = time - this.timeSpentAlive[i].spawnedAt;\r\n this.timeSpentAlive[i].alive = false;\r\n this.timeSpentAlive[i].timeSpentAlive += timeAlive;\r\n this.timeSpentAlive[i].count += 1;\r\n }\r\n }\r\n\r\n addExperienceGain(unit, experience) {\r\n if (!unit.isPlayer) {\r\n return;\r\n }\r\n\r\n if (!this.experienceGained[unit.hrid]) {\r\n this.experienceGained[unit.hrid] = {\r\n stamina: 0,\r\n intelligence: 0,\r\n attack: 0,\r\n melee: 0,\r\n defense: 0,\r\n ranged: 0,\r\n magic: 0,\r\n };\r\n }\r\n\r\n let experienceGainedRate = {\r\n \"stamina\": 0,\r\n \"intelligence\": 0,\r\n \"attack\": 0,\r\n \"melee\": 0,\r\n \"defense\": 0,\r\n \"ranged\": 0,\r\n \"magic\": 0,\r\n };\r\n\r\n const primaryTraining = unit.combatDetails.combatStats.primaryTraining;\r\n experienceGainedRate[primaryTraining.split(\"/\")[2]] = .3;\r\n\r\n const skillExpMap = combatStyleDetailMap[unit.combatDetails.combatStats.combatStyleHrid].skillExpMap;\r\n const skillExpMapLength = Object.keys(skillExpMap).length;\r\n\r\n const focusTraining = unit.combatDetails.combatStats.focusTraining;\r\n if (focusTraining && skillExpMap[focusTraining]) {\r\n experienceGainedRate[focusTraining.split(\"/\")[2]] += .7;\r\n } else {\r\n Object.keys(skillExpMap).forEach(skillHrid => {\r\n experienceGainedRate[skillHrid.split(\"/\")[2]] += .7 / skillExpMapLength;\r\n });\r\n }\r\n\r\n for (const [type, rate] of Object.entries(experienceGainedRate)) {\r\n if (rate <= 0) continue;\r\n\r\n const skillExperience = rate * (1 + unit.combatDetails.combatStats[type + \"Experience\"]);\r\n\r\n this.experienceGained[unit.hrid][type] += (\r\n experience\r\n * (1 + unit.combatDetails.combatStats.combatExperience)\r\n * skillExperience\r\n * (1 + unit.debuffOnLevelGap)\r\n\r\n );\r\n }\r\n }\r\n\r\n addEncounterEnd() {\r\n this.encounters++;\r\n }\r\n\r\n addAttack(source, target, ability, hit) {\r\n if (!this.attacks[source.hrid]) {\r\n this.attacks[source.hrid] = {};\r\n }\r\n if (!this.attacks[source.hrid][target.hrid]) {\r\n this.attacks[source.hrid][target.hrid] = {};\r\n }\r\n if (!this.attacks[source.hrid][target.hrid][ability]) {\r\n this.attacks[source.hrid][target.hrid][ability] = {};\r\n }\r\n\r\n if (!this.attacks[source.hrid][target.hrid][ability][hit]) {\r\n this.attacks[source.hrid][target.hrid][ability][hit] = 0;\r\n }\r\n\r\n this.attacks[source.hrid][target.hrid][ability][hit] += 1;\r\n }\r\n\r\n addConsumableUse(unit, consumable) {\r\n if (!this.consumablesUsed[unit.hrid]) {\r\n this.consumablesUsed[unit.hrid] = {};\r\n }\r\n if (!this.consumablesUsed[unit.hrid][consumable.hrid]) {\r\n this.consumablesUsed[unit.hrid][consumable.hrid] = 0;\r\n }\r\n\r\n this.consumablesUsed[unit.hrid][consumable.hrid] += 1;\r\n }\r\n\r\n addHitpointsGained(unit, source, amount) {\r\n if (!this.hitpointsGained[unit.hrid]) {\r\n this.hitpointsGained[unit.hrid] = {};\r\n }\r\n if (!this.hitpointsGained[unit.hrid][source]) {\r\n this.hitpointsGained[unit.hrid][source] = 0;\r\n }\r\n\r\n this.hitpointsGained[unit.hrid][source] += amount;\r\n }\r\n\r\n addManapointsGained(unit, source, amount) {\r\n if (!this.manapointsGained[unit.hrid]) {\r\n this.manapointsGained[unit.hrid] = {};\r\n }\r\n if (!this.manapointsGained[unit.hrid][source]) {\r\n this.manapointsGained[unit.hrid][source] = 0;\r\n }\r\n\r\n this.manapointsGained[unit.hrid][source] += amount;\r\n }\r\n\r\n setDropRateMultipliers(unit) {\r\n if (!this.dropRateMultiplier[unit.hrid]) {\r\n this.dropRateMultiplier[unit.hrid] = {};\r\n }\r\n this.dropRateMultiplier[unit.hrid] = 1 + unit.combatDetails.combatStats.combatDropRate;\r\n\r\n if (!this.rareFindMultiplier[unit.hrid]) {\r\n this.rareFindMultiplier[unit.hrid] = {};\r\n }\r\n this.rareFindMultiplier[unit.hrid] = 1 + unit.combatDetails.combatStats.combatRareFind;\r\n\r\n if (!this.combatDropQuantity[unit.hrid]) {\r\n this.combatDropQuantity[unit.hrid] = {};\r\n }\r\n this.combatDropQuantity[unit.hrid] = unit.combatDetails.combatStats.combatDropQuantity;\r\n\r\n if (!this.debuffOnLevelGap[unit.hrid]) {\r\n this.debuffOnLevelGap[unit.hrid] = {};\r\n }\r\n this.debuffOnLevelGap[unit.hrid] = unit.debuffOnLevelGap;\r\n }\r\n\r\n setManaUsed(unit) {\r\n this.manaUsed[unit.hrid] = {};\r\n for (let [key, value] of unit.abilityManaCosts.entries()) {\r\n this.manaUsed[unit.hrid][key] = value;\r\n }\r\n }\r\n\r\n addHitpointsSpent(unit, source, amount) {\r\n if (!this.hitpointsSpent[unit.hrid]) {\r\n this.hitpointsSpent[unit.hrid] = {};\r\n }\r\n if (!this.hitpointsSpent[unit.hrid][source]) {\r\n this.hitpointsSpent[unit.hrid][source] = 0;\r\n }\r\n\r\n this.hitpointsSpent[unit.hrid][source] += amount;\r\n }\r\n\r\n addRanOutOfManaCount(unit, isOutOfMana, time) {\r\n if (isOutOfMana) this.playerRanOutOfMana[unit.hrid] = true;\r\n\r\n if (!this.playerRanOutOfManaTime[unit.hrid]) {\r\n this.playerRanOutOfManaTime[unit.hrid] = {isOutOfMana: false, startTimeForOutOfMana:0, totalTimeForOutOfMana:0};\r\n }\r\n\r\n if (isOutOfMana) {\r\n if (!this.playerRanOutOfManaTime[unit.hrid].isOutOfMana) {\r\n this.playerRanOutOfManaTime[unit.hrid].isOutOfMana = true;\r\n this.playerRanOutOfManaTime[unit.hrid].startTimeForOutOfMana = time;\r\n }\r\n } else {\r\n if (this.playerRanOutOfManaTime[unit.hrid].isOutOfMana) {\r\n this.playerRanOutOfManaTime[unit.hrid].isOutOfMana = false;\r\n this.playerRanOutOfManaTime[unit.hrid].totalTimeForOutOfMana += time - this.playerRanOutOfManaTime[unit.hrid].startTimeForOutOfMana;\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default SimResult;\r\n","import combatTriggerDependencyDetailMap from \"./data/combatTriggerDependencyDetailMap.json\";\r\n\r\nclass Trigger {\r\n constructor(dependencyHrid, conditionHrid, comparatorHrid, value = 0) {\r\n this.dependencyHrid = dependencyHrid;\r\n this.conditionHrid = conditionHrid;\r\n this.comparatorHrid = comparatorHrid;\r\n this.value = value;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let trigger = new Trigger(dto.dependencyHrid, dto.conditionHrid, dto.comparatorHrid, dto.value);\r\n\r\n return trigger;\r\n }\r\n\r\n isActive(source, target, friendlies, enemies, currentTime) {\r\n if (combatTriggerDependencyDetailMap[this.dependencyHrid].isSingleTarget) {\r\n return this.isActiveSingleTarget(source, target, currentTime);\r\n } else {\r\n return this.isActiveMultiTarget(friendlies, enemies, currentTime);\r\n }\r\n }\r\n\r\n isActiveSingleTarget(source, target, currentTime) {\r\n let dependencyValue;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/self\":\r\n dependencyValue = this.getDependencyValue(source, currentTime);\r\n break;\r\n case \"/combat_trigger_dependencies/targeted_enemy\":\r\n if (!target) {\r\n return false;\r\n }\r\n dependencyValue = this.getDependencyValue(target, currentTime);\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n isActiveMultiTarget(friendlies, enemies, currentTime) {\r\n let dependency;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/all_allies\":\r\n dependency = friendlies;\r\n break;\r\n case \"/combat_trigger_dependencies/all_enemies\":\r\n if (!enemies) {\r\n return false;\r\n }\r\n dependency = enemies;\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n let dependencyValue;\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/number_of_active_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints > 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/number_of_dead_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints <= 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/lowest_hp_percentage\":\r\n dependencyValue = dependency.reduce((prev, curr) => {\r\n let currentHpPercentage = curr.combatDetails.currentHitpoints / curr.combatDetails.maxHitpoints;\r\n return currentHpPercentage < prev ? currentHpPercentage : prev;\r\n }, 2) * 100;\r\n break;\r\n default:\r\n dependencyValue = dependency\r\n .map((unit) => this.getDependencyValue(unit, currentTime))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n break;\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n getDependencyValue(source, currentTime) {\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/berserk\":\r\n case \"/combat_trigger_conditions/frenzy\":\r\n case \"/combat_trigger_conditions/precision\":\r\n case \"/combat_trigger_conditions/vampirism\":\r\n case \"/combat_trigger_conditions/attack_coffee\":\r\n case \"/combat_trigger_conditions/defense_coffee\":\r\n case \"/combat_trigger_conditions/lucky_coffee\":\r\n case \"/combat_trigger_conditions/magic_coffee\":\r\n case \"/combat_trigger_conditions/melee_coffee\":\r\n case \"/combat_trigger_conditions/ranged_coffee\":\r\n case \"/combat_trigger_conditions/swiftness_coffee\":\r\n case \"/combat_trigger_conditions/wisdom_coffee\":\r\n case \"/combat_trigger_conditions/ice_spear\":\r\n case \"/combat_trigger_conditions/puncture\":\r\n case \"/combat_trigger_conditions/frost_surge\":\r\n case \"/combat_trigger_conditions/elusiveness\":\r\n case \"/combat_trigger_conditions/channeling_coffee\":\r\n case \"/combat_trigger_conditions/fierce_aura\":\r\n case \"/combat_trigger_conditions/invincible_armor\":\r\n case \"/combat_trigger_conditions/invincible_fire_resistance\":\r\n case \"/combat_trigger_conditions/invincible_nature_resistance\":\r\n case \"/combat_trigger_conditions/invincible_water_resistance\":\r\n case \"/combat_trigger_conditions/provoke\":\r\n case \"/combat_trigger_conditions/taunt\":\r\n case \"/combat_trigger_conditions/crippling_slash\":\r\n case \"/combat_trigger_conditions/mana_spring\":\r\n case \"/combat_trigger_conditions/retribution\":\r\n case \"/combat_trigger_conditions/fracturing_impact\":\r\n case \"/combat_trigger_conditions/maim\":\r\n case \"/combat_trigger_conditions/curse\":\r\n case \"/combat_trigger_conditions/weaken\":\r\n let buffHrid = \"/buff_uniques\";\r\n buffHrid += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n return source.combatBuffs[buffHrid];\r\n case \"/combat_trigger_conditions/critical_aura\":\r\n case \"/combat_trigger_conditions/critical_coffee\":\r\n case \"/combat_trigger_conditions/intelligence_coffee\":\r\n case \"/combat_trigger_conditions/stamina_coffee\":\r\n case \"/combat_trigger_conditions/elemental_affinity\":\r\n case \"/combat_trigger_conditions/fury\":\r\n case \"/combat_trigger_conditions/guardian_aura\":\r\n case \"/combat_trigger_conditions/insanity\":\r\n case \"/combat_trigger_conditions/spike_shell\":\r\n case \"/combat_trigger_conditions/toxic_pollen\":\r\n case \"/combat_trigger_conditions/invincible\":\r\n case \"/combat_trigger_conditions/mystic_aura\":\r\n case \"/combat_trigger_conditions/pestilent_shot\":\r\n case \"/combat_trigger_conditions/smoke_burst\":\r\n case \"/combat_trigger_conditions/speed_aura\":\r\n case \"/combat_trigger_conditions/toughness\":\r\n case \"/combat_trigger_conditions/enrage\":\r\n let buffPrefix = \"/buff_uniques\";\r\n buffPrefix += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n let buffs = Object.keys(source.combatBuffs).filter(buff => buff.startsWith(buffPrefix));\r\n return source.combatBuffs[buffs?.[0]];\r\n case \"/combat_trigger_conditions/current_hp\":\r\n return source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/current_mp\":\r\n return source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/missing_hp\":\r\n return source.combatDetails.maxHitpoints - source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/missing_mp\":\r\n return source.combatDetails.maxManapoints - source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/stun_status\":\r\n // Replicate the game's behaviour of \"stun status active\" triggers activating\r\n // immediately after the stun has worn off\r\n return source.isStunned || source.stunExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/blind_status\":\r\n return source.isBlinded || source.blindExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/silence_status\":\r\n return source.isSilenced || source.silenceExpireTime == currentTime;\r\n default:\r\n throw new Error(\"Unknown conditionHrid in trigger: \" + this.conditionHrid);\r\n }\r\n }\r\n\r\n compareValue(dependencyValue) {\r\n switch (this.comparatorHrid) {\r\n case \"/combat_trigger_comparators/greater_than_equal\":\r\n return dependencyValue >= this.value;\r\n case \"/combat_trigger_comparators/less_than_equal\":\r\n return dependencyValue <= this.value;\r\n case \"/combat_trigger_comparators/is_active\":\r\n return !!dependencyValue;\r\n case \"/combat_trigger_comparators/is_inactive\":\r\n return !dependencyValue;\r\n default:\r\n throw new Error(\"Unknown comparatorHrid in trigger: \" + this.comparatorHrid);\r\n }\r\n }\r\n}\r\n\r\nexport default Trigger;\r\n","import actionDetailMap from \"./data/actionDetailMap.json\";\r\nimport Monster from \"./monster\";\r\n\r\nclass Zone {\r\n constructor(hrid, difficultyTier) {\r\n this.hrid = hrid;\r\n this.difficultyTier = difficultyTier;\r\n\r\n let gameZone = actionDetailMap[this.hrid];\r\n this.monsterSpawnInfo = gameZone.combatZoneInfo.fightInfo;\r\n this.dungeonSpawnInfo = gameZone.combatZoneInfo.dungeonInfo;\r\n this.encountersKilled = 1;\r\n this.monsterSpawnInfo.battlesPerBoss = 10;\r\n this.buffs = gameZone.buffs;\r\n this.isDungeon = gameZone.combatZoneInfo.isDungeon;\r\n this.dungeonsCompleted = 0;\r\n this.dungeonsFailed = 0;\r\n this.finalWave = false;\r\n }\r\n\r\n getRandomEncounter() {\r\n\r\n if (this.monsterSpawnInfo.bossSpawns && this.encountersKilled == this.monsterSpawnInfo.battlesPerBoss) {\r\n this.encountersKilled = 1;\r\n return this.monsterSpawnInfo.bossSpawns.map((monster) => new Monster(monster.combatMonsterHrid, monster.difficultyTier + this.difficultyTier));\r\n }\r\n\r\n let totalWeight = this.monsterSpawnInfo.randomSpawnInfo.spawns.reduce((prev, cur) => prev + cur.rate, 0);\r\n\r\n let encounterHrids = [];\r\n let totalStrength = 0;\r\n\r\n outer: for (let i = 0; i < this.monsterSpawnInfo.randomSpawnInfo.maxSpawnCount; i++) {\r\n let randomWeight = totalWeight * Math.random();\r\n let cumulativeWeight = 0;\r\n\r\n for (const spawn of this.monsterSpawnInfo.randomSpawnInfo.spawns) {\r\n cumulativeWeight += spawn.rate;\r\n if (randomWeight <= cumulativeWeight) {\r\n totalStrength += spawn.strength;\r\n\r\n if (totalStrength <= this.monsterSpawnInfo.randomSpawnInfo.maxTotalStrength) {\r\n encounterHrids.push({ 'hrid': spawn.combatMonsterHrid, 'difficultyTier': spawn.difficultyTier});\r\n\r\n } else {\r\n break outer;\r\n }\r\n break;\r\n }\r\n }\r\n }\r\n this.encountersKilled++;\r\n return encounterHrids.map((hrid) => new Monster(hrid.hrid, hrid.difficultyTier + this.difficultyTier));\r\n }\r\n\r\n failWave() {\r\n this.dungeonsFailed++;\r\n this.encountersKilled = 1;\r\n }\r\n\r\n getNextWave() {\r\n if (this.encountersKilled > this.dungeonSpawnInfo.maxWaves) {\r\n this.dungeonsCompleted++;\r\n this.encountersKilled = 1;\r\n }\r\n // console.log(\"Wave #\" + this.encountersKilled);\r\n if (this.dungeonSpawnInfo.fixedSpawnsMap.hasOwnProperty(this.encountersKilled.toString())) {\r\n let currentMonsters = this.dungeonSpawnInfo.fixedSpawnsMap[(this.encountersKilled).toString()];\r\n this.encountersKilled++;\r\n return currentMonsters.map((monster) => new Monster(monster.combatMonsterHrid, monster.difficultyTier + this.difficultyTier));\r\n } else {\r\n let monsterSpawns = {};\r\n const waveKeys = Object.keys(this.dungeonSpawnInfo.randomSpawnInfoMap).map(Number).sort((a, b) => a - b);\r\n if (this.encountersKilled > waveKeys[waveKeys.length - 1]) {\r\n monsterSpawns = this.dungeonSpawnInfo.randomSpawnInfoMap[waveKeys[waveKeys.length - 1]];\r\n } else {\r\n for (let i = 0; i < waveKeys.length - 1; i++) {\r\n if (this.encountersKilled >= waveKeys[i] && this.encountersKilled <= waveKeys[i + 1]) {\r\n monsterSpawns = this.dungeonSpawnInfo.randomSpawnInfoMap[waveKeys[i]];\r\n break;\r\n }\r\n }\r\n }\r\n let totalWeight = monsterSpawns.spawns.reduce((prev, cur) => prev + cur.rate, 0);\r\n\r\n let encounterHrids = [];\r\n let totalStrength = 0;\r\n\r\n outer: for (let i = 0; i < monsterSpawns.maxSpawnCount; i++) {\r\n let randomWeight = totalWeight * Math.random();\r\n let cumulativeWeight = 0;\r\n\r\n for (const spawn of monsterSpawns.spawns) {\r\n cumulativeWeight += spawn.rate;\r\n if (randomWeight <= cumulativeWeight) {\r\n totalStrength += spawn.strength;\r\n\r\n if (totalStrength <= monsterSpawns.maxTotalStrength) {\r\n encounterHrids.push({ 'hrid': spawn.combatMonsterHrid, 'difficultyTier': spawn.difficultyTier});\r\n\r\n } else {\r\n break outer;\r\n }\r\n break;\r\n }\r\n }\r\n }\r\n this.encountersKilled++;\r\n return encounterHrids.map((hrid) => new Monster(hrid.hrid, hrid.difficultyTier + this.difficultyTier));\r\n }\r\n }\r\n}\r\n\r\nexport default Zone;\r\n","import CombatSimulator from \"./combatsimulator/combatSimulator\";\r\nimport Player from \"./combatsimulator/player\";\r\nimport Zone from \"./combatsimulator/zone\";\r\n\r\n\r\nonmessage = async function (event) {\r\n switch (event.data.type) {\r\n case \"start_simulation\":\r\n let extraBuffs = [];\r\n if (event.data.extra.mooPass) {\r\n const mooPassBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/experience_moo_pass_buff\",\r\n \"typeHrid\": \"/buff_types/wisdom\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0.05,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": 0\r\n };\r\n extraBuffs.push(mooPassBuff);\r\n }\r\n if (event.data.extra.comExp > 0) {\r\n const comExpBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/experience_community_buff\",\r\n \"typeHrid\": \"/buff_types/wisdom\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0.005 * (event.data.extra.comExp - 1) + 0.2,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": 0\r\n };\r\n extraBuffs.push(comExpBuff);\r\n }\r\n if (event.data.extra.comDrop > 0) {\r\n const comDropBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/combat_community_buff\",\r\n \"typeHrid\": \"/buff_types/combat_drop_quantity\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0.005 * (event.data.extra.comDrop - 1) + 0.2,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": 0\r\n };\r\n extraBuffs.push(comDropBuff);\r\n }\r\n\r\n let playersData = event.data.players;\r\n let players = [];\r\n let zone = new Zone(event.data.zone.zoneHrid, event.data.zone.difficultyTier);\r\n for (let i = 0; i < playersData.length; i++) {\r\n let currentPlayer = Player.createFromDTO(structuredClone(playersData[i]));\r\n currentPlayer.zoneBuffs = zone.buffs;\r\n currentPlayer.extraBuffs = extraBuffs;\r\n players.push(currentPlayer);\r\n }\r\n let simulationTimeLimit = event.data.simulationTimeLimit;\r\n let combatSimulator = new CombatSimulator(players, zone);\r\n combatSimulator.addEventListener(\"progress\", (event) => {\r\n this.postMessage({ type: \"simulation_progress\", progress: event.detail.progress, zone: event.detail.zone, difficultyTier: event.detail.difficultyTier });\r\n });\r\n\r\n try {\r\n let simResult = await combatSimulator.simulate(simulationTimeLimit);\r\n this.postMessage({ type: \"simulation_result\", simResult: simResult });\r\n } catch (e) {\r\n console.log(e);\r\n this.postMessage({ type: \"simulation_error\", error: e });\r\n }\r\n break;\r\n }\r\n};\r\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/patchNote.json b/patchNote.json index 1c5d1580..c8160696 100644 --- a/patchNote.json +++ b/patchNote.json @@ -1,4 +1,8 @@ { + "2025年12月24日": + [ + "修复技能释放选择的缺陷,之前可能存在异常缺蓝等情况" + ], "2025年12月18日": [ "支持成就系统及对应buff效果", diff --git a/src/combatsimulator/combatSimulator.js b/src/combatsimulator/combatSimulator.js index 54277eaa..a9c03d3e 100644 --- a/src/combatsimulator/combatSimulator.js +++ b/src/combatsimulator/combatSimulator.js @@ -867,9 +867,11 @@ class CombatSimulator extends EventTarget { processRegenTickEvent(event) { let units = [...this.players]; - if (this.enemies) { - units.push(...this.enemies); - } + + // regen of emeny always set to 0, ingore the proc time + // if (this.enemies) { + // units.push(...this.enemies); + // } for (const unit of units) { if (unit.combatDetails.currentHitpoints <= 0) { @@ -1148,7 +1150,6 @@ class CombatSimulator extends EventTarget { castDuration /= (1 + source.combatDetails.combatStats.castSpeed) // console.log((this.simulationTime / 1000000000) + " Used ability " + ability.hrid + " Cast time " + (castDuration / 1e9)); }*/ - this.addNextAttackEvent(source); let todoAbilities = [ability]; @@ -1202,6 +1203,8 @@ class CombatSimulator extends EventTarget { } } + this.addNextAttackEvent(source); + // Could die from reflect damage if (source.combatDetails.currentHitpoints == 0) { this.eventQueue.clearEventsForUnit(source); From 01ba5f6734cc34a492e4c8652ae59dae1d2bab7d Mon Sep 17 00:00:00 2001 From: shykai Date: Tue, 30 Dec 2025 10:33:59 +0800 Subject: [PATCH 06/46] minDungeonTime --- dist/bundle.js | 8 +++++++- dist/bundle.js.map | 2 +- dist/locales/en/common.json | 1 + dist/locales/zh/common.json | 3 ++- dist/patchNote.json | 4 ++++ dist/src_worker_js.bundle.js | 17 +++++++++++++++++ dist/src_worker_js.bundle.js.map | 2 +- locales/en/common.json | 1 + locales/zh/common.json | 3 ++- patchNote.json | 4 ++++ src/combatsimulator/combatSimulator.js | 3 +++ src/combatsimulator/simResult.js | 14 ++++++++++++++ src/main.js | 6 ++++++ 13 files changed, 63 insertions(+), 5 deletions(-) diff --git a/dist/bundle.js b/dist/bundle.js index 762d1e0e..13783845 100644 --- a/dist/bundle.js +++ b/dist/bundle.js @@ -1466,7 +1466,7 @@ class Trigger { \************************/ /***/ ((module) => { -module.exports = /*#__PURE__*/JSON.parse('{"2025年12月24日":["修复技能释放选择的缺陷,之前可能存在异常缺蓝等情况"],"2025年12月18日":["支持成就系统及对应buff效果","地下城怪物的掉落不再生效"],"2025年12月6日":["修复游戏更新后技能在无trigger情况下由[]变为null时造成的异常"],"2025年11月7日":["兼容支持从CN镜像站调用API获取价格"],"2025年10月14日":["修复怪物攻击间隔数值未能适配攻击等级的问题"],"2025年9月17日":["修复暴击光环的trigger缺陷"],"2025年9月9日":["复活时不再错误的清空所有buff","团灭日志增加反伤、荆棘和DOT伤害记录"],"2025年8月21日":["增加单挑战斗批量模拟和对应怪物选项","增加MooPass和社区buff的选项及对应功能","精炼装备数值加强","秘法主教属性削弱","init_client_info_v1.20250819.0.json游戏数据更新"],"2025年8月20日":["修复经验和掉落计算在极端情况下的可能异常"],"2025年8月19日":["合并Test和Temp分支的rework内容","init_client_info_v1.20250818.0.json游戏数据更新"],"2025年8月18日":["修复贯穿技能可能对相同目标造成重复伤害的问题","修复团灭日志在黑夜模式下的显示异常","战斗等级公式更新","钟乳石魔像的荆棘数值调整","init_client_info_v1.20250626.0_0817.json游戏数据更新"],"2025年8月16日":["增加停止模拟按钮 by BKN46","增加技能顺序调整按钮 by BKN46","增加团灭日志 by TruthLight","怪物属性更新","奥术反射更名为报应","init_client_info_v1.20250626.0_0815.json游戏数据更新"],"2025年8月14日":["怪物属性更新","远程和法师装备属性调整","反伤计算上限调整","修复战斗间隔释放技能的异常","修复技能释放判断逻辑的异常","法力值耗尽比例更加准确","调整远程经验的15%和魔法经验的12%映射到攻击经验","init_client_info_v1.20250626.0_0813.json游戏数据更新"],"2025年8月11日":["怪物属性更新","近战和物理技能施法时间更新","盾击和重锤数值调整","双手盾防御经验加成调整","init_client_info_v1.20250626.0_0811.json游戏数据更新"],"2025年8月8日":["实现组队等级差过大时对掉落和经验的惩罚","实现怪物经验随狂暴进度百分比增加","暴击光环数值调整","增加战斗等级数值显示","增加等级差距惩罚数值显示","init_client_info_v1.20250626.0_0807.json游戏数据更新"],"2025年8月7日":["修复组队战斗时一些重复物品掉落数量异常的缺陷 by contr4l","init_client_info_v1.20250626.0_0806.json游戏数据更新"],"2025年8月3日":["怪物狂暴机制及对应trigger生效","精炼装备更新,护符数值调整,守护光环增加闪避率","init_client_info_v1.20250626.0_0802.json游戏数据更新","狂怒层数修正为5层","招架结算机制调整"],"2025年7月31日":["物品数据和怪物属性更新","尖刺外壳和奥术反射重做","强化数值更新","删除异常trigger","狮鹫盾的虚弱重做","君王剑招架对队友生效","狂怒特效最大层数修正为6层","涟漪特效增加10MP恢复","反伤正确显示其命中率","反伤机制调整","同步双手盾属性和反伤荆棘技能数值的调整"],"2025年7月22日":["暴击光环受远程等级加成","光环基础数值和等级加成调整"],"2025年7月17日":["批量模拟支持勾选星球","经验分配比例调整至30%+70%","光环及对应trigger,并按对应技能等级百分比加成","水火自然默认调整为元素光环","init_client_info_v1.20250626.0_0717.json游戏数据更新"],"2025年7月11日":["怪物经验和技能等级公式更新","闪避和抗性计算公式更新","力量更替为近战以及对应的兼容","init_client_info_v1.20250626.0_0711.json游戏数据更新"],"2025年7月10日":["修复贯穿技能由敌人释放时可能多次击中相同目标的缺陷"],"2025年7月9日":["掉落和掉率调整","经验调整","疫病射击和破甲之刺调整","怪物自动恢复移除","疫病射击trigger调整","获取价格使用官方API"],"2025年7月7日":["怪物属性缩放和地图多难度","法师技能调整和装备上\'技能伤害\'词缀生效","攻击等级和房屋等级对施法速度的影响生效","物品调整","精准重做以攻击等级计算","TEST 远程魔法经验的10%映射到攻击经验!","经验重做和护符装备"]}'); +module.exports = /*#__PURE__*/JSON.parse('{"2025年12月30日":["地下城增加最短完成时间记录"],"2025年12月24日":["修复技能释放选择的缺陷,之前可能存在异常缺蓝等情况"],"2025年12月18日":["支持成就系统及对应buff效果","地下城怪物的掉落不再生效"],"2025年12月6日":["修复游戏更新后技能在无trigger情况下由[]变为null时造成的异常"],"2025年11月7日":["兼容支持从CN镜像站调用API获取价格"],"2025年10月14日":["修复怪物攻击间隔数值未能适配攻击等级的问题"],"2025年9月17日":["修复暴击光环的trigger缺陷"],"2025年9月9日":["复活时不再错误的清空所有buff","团灭日志增加反伤、荆棘和DOT伤害记录"],"2025年8月21日":["增加单挑战斗批量模拟和对应怪物选项","增加MooPass和社区buff的选项及对应功能","精炼装备数值加强","秘法主教属性削弱","init_client_info_v1.20250819.0.json游戏数据更新"],"2025年8月20日":["修复经验和掉落计算在极端情况下的可能异常"],"2025年8月19日":["合并Test和Temp分支的rework内容","init_client_info_v1.20250818.0.json游戏数据更新"],"2025年8月18日":["修复贯穿技能可能对相同目标造成重复伤害的问题","修复团灭日志在黑夜模式下的显示异常","战斗等级公式更新","钟乳石魔像的荆棘数值调整","init_client_info_v1.20250626.0_0817.json游戏数据更新"],"2025年8月16日":["增加停止模拟按钮 by BKN46","增加技能顺序调整按钮 by BKN46","增加团灭日志 by TruthLight","怪物属性更新","奥术反射更名为报应","init_client_info_v1.20250626.0_0815.json游戏数据更新"],"2025年8月14日":["怪物属性更新","远程和法师装备属性调整","反伤计算上限调整","修复战斗间隔释放技能的异常","修复技能释放判断逻辑的异常","法力值耗尽比例更加准确","调整远程经验的15%和魔法经验的12%映射到攻击经验","init_client_info_v1.20250626.0_0813.json游戏数据更新"],"2025年8月11日":["怪物属性更新","近战和物理技能施法时间更新","盾击和重锤数值调整","双手盾防御经验加成调整","init_client_info_v1.20250626.0_0811.json游戏数据更新"],"2025年8月8日":["实现组队等级差过大时对掉落和经验的惩罚","实现怪物经验随狂暴进度百分比增加","暴击光环数值调整","增加战斗等级数值显示","增加等级差距惩罚数值显示","init_client_info_v1.20250626.0_0807.json游戏数据更新"],"2025年8月7日":["修复组队战斗时一些重复物品掉落数量异常的缺陷 by contr4l","init_client_info_v1.20250626.0_0806.json游戏数据更新"],"2025年8月3日":["怪物狂暴机制及对应trigger生效","精炼装备更新,护符数值调整,守护光环增加闪避率","init_client_info_v1.20250626.0_0802.json游戏数据更新","狂怒层数修正为5层","招架结算机制调整"],"2025年7月31日":["物品数据和怪物属性更新","尖刺外壳和奥术反射重做","强化数值更新","删除异常trigger","狮鹫盾的虚弱重做","君王剑招架对队友生效","狂怒特效最大层数修正为6层","涟漪特效增加10MP恢复","反伤正确显示其命中率","反伤机制调整","同步双手盾属性和反伤荆棘技能数值的调整"],"2025年7月22日":["暴击光环受远程等级加成","光环基础数值和等级加成调整"],"2025年7月17日":["批量模拟支持勾选星球","经验分配比例调整至30%+70%","光环及对应trigger,并按对应技能等级百分比加成","水火自然默认调整为元素光环","init_client_info_v1.20250626.0_0717.json游戏数据更新"],"2025年7月11日":["怪物经验和技能等级公式更新","闪避和抗性计算公式更新","力量更替为近战以及对应的兼容","init_client_info_v1.20250626.0_0711.json游戏数据更新"],"2025年7月10日":["修复贯穿技能由敌人释放时可能多次击中相同目标的缺陷"],"2025年7月9日":["掉落和掉率调整","经验调整","疫病射击和破甲之刺调整","怪物自动恢复移除","疫病射击trigger调整","获取价格使用官方API"],"2025年7月7日":["怪物属性缩放和地图多难度","法师技能调整和装备上\'技能伤害\'词缀生效","攻击等级和房屋等级对施法速度的影响生效","物品调整","精准重做以攻击等级计算","TEST 远程魔法经验的10%映射到攻击经验!","经验重做和护符装备"]}'); /***/ }), @@ -3431,6 +3431,12 @@ function showKills(simResult, playerToDisplay) { let averageTime = (hoursSimulated * 60 / simResult.dungeonsCompleted).toFixed(1); encountersRow = createRow(["col-md-6", "col-md-6 text-end"], ["Average Time", averageTime]); encountersRow.firstElementChild.setAttribute("data-i18n", "common:simulationResults.averageTime"); + if (simResult.minDungenonTime > 0) { + let minimumTime = (simResult.minDungenonTime / ONE_SECOND / 60).toFixed(1); + let minimumTimeRow = createRow(["col-md-6", "col-md-6 text-end"], ["Minimum Time", minimumTime]); + minimumTimeRow.firstElementChild.setAttribute("data-i18n", "common:simulationResults.minimumTime"); + newChildren.push(minimumTimeRow); + } } else { encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1); encountersRow = createRow(["col-md-6", "col-md-6 text-end"], ["Encounters", encountersPerHour]); diff --git a/dist/bundle.js.map b/dist/bundle.js.map index af7e7479..5601a3a2 100644 --- a/dist/bundle.js.map +++ b/dist/bundle.js.map @@ -1 +1 @@ -{"version":3,"file":"bundle.js","mappings":";;;;;;;;;;;;;;;;;AAA0B;AACkC;AAC5B;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,wDAAgB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,6CAAI;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;ACxNG;AACkD;AACR;AACpE;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,gEAAwB;AAChE;AACA,0CAA0C,4DAAoB;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,WAAW;;;;;;;;;;;;;;AC1B1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,IAAI,EAAC;;;;;;;;;;;;;;;ACdpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wDAAwD;AACtF,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,mBAAmB;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;;AC/gBA;AAC4B;AACtB;AAChC;AACA;AACA;AACA;AACA;AACA,6BAA6B,qDAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;ACtF4B;AACmD;AACzG;AACA;AACA;AACA;AACA,uBAAuB,qDAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,iFAAoC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS,EAAC;;;;;;;;;;;;;;;;;AC/CC;AACsC;AAChE;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,0DAAkB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS;;;;;;;;;;;;;;;;;;;;AC7BQ;AACM;AACA;AACF;AACA;AACI;AACxC;AACA,qBAAqB,mDAAU;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,kDAAS;AACrD;AACA;AACA,qDAAqD,mDAAU;AAC/D,2DAA2D,mDAAU;AACrE,qEAAqE,gDAAO;AAC5E;AACA;AACA,2CAA2C,kDAAS;AACpD;AACA,SAAS;AACT;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,MAAM,EAAC;;;;;;;;;;;;;;;;ACvLsE;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,wEAAgC;AAC5C;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UCjLvB;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;UAEA;UACA;;;;;WCzBA;WACA;WACA;WACA;WACA,yCAAyC,wCAAwC;WACjF;WACA;WACA;;;;;WCPA;WACA;WACA;WACA;WACA;;;;;WCJA;WACA;WACA;WACA;WACA,GAAG;WACH;WACA;WACA,CAAC;;;;;WCPD;;;;;WCAA;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D;;;;;WCNA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;;;;WClBA;;WAEA;WACA;WACA;WACA;WACA;WACA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrBuD;AACN;AAC2B;AACN;AACU;AAC7B;AACM;AACN;AACyD;AACF;AACE;AACA;AAClC;AACc;AACN;AACE;AACF;AACG;AACF;AACnF;AAC0C;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,2FAA4B;AAC5D,qCAAqC,qHAAiC;AACtE;AACA;AACA;AACA,iBAAiB,kEAAM;AACvB;AACA,IAAI,SAAI;AACR,IAAI,WAAM;AACV,IAAI,cAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA,sCAAsC,qEAAa;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,mCAAmC,0EAAkB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,iFAAkB;AAClD;AACA,4FAA4F,eAAe;AAC3G;AACA;AACA;AACA,uDAAuD,eAAe;AACtE,6BAA6B,KAAK,GAAG,MAAM;AAC3C;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,iFAAkB;AAClD;AACA,sCAAsC,6EAAoB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,eAAe;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oEAAoE,eAAe;AACnF,mCAAmC,eAAe;AAClD;AACA;AACA,gGAAgG,eAAe;AAC/G;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,gCAAgC,eAAe;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,iBAAiB;AACpD;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,iDAAiD,iBAAiB;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,qEAAa;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,qEAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,qEAAS;AACvD,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,6EAAoB;AACvD;AACA;AACA,qBAAqB,4EAAmB;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,sCAAsC,qEAAa;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,SAAI;AACZ,YAAY,SAAI,mBAAmB,SAAI;AACvC,2BAA2B,qEAAa,CAAC,SAAI;AAC7C,uBAAuB,SAAI;AAC3B;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,qFAAqF,SAAI;AACzF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,uCAAuC,qEAAa;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,WAAM;AACd,YAAY,WAAM,mBAAmB,WAAM;AAC3C,2BAA2B,qEAAa,CAAC,WAAM;AAC/C,uBAAuB,WAAM;AAC7B;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,sFAAsF,WAAM;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D,UAAU;AACV,0CAA0C,wEAAgB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,cAAS;AACjB,YAAY,cAAS,mBAAmB,cAAS;AACjD,8BAA8B,wEAAgB,CAAC,cAAS;AACxD,uBAAuB,cAAS;AAChC;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,4DAA4D,yFAAgC;AAC5F,2DAA2D,yFAAgC;AAC3F,4DAA4D,yFAAgC,YAAY,cAAS;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAS;AACjC,IAAI,cAAS,iBAAiB,cAAS;AACvC,IAAI,cAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,cAAS;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,SAAI;AAChC;AACA;AACA,4BAA4B,WAAM;AAClC;AACA;AACA,4BAA4B,cAAS;AACrC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,qEAAa;AACrD,MAAM;AACN,wCAAwC,wEAAgB;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,yFAAgC;AAC5C;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,wFAAgC;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,wFAAgC;AACrD;AACA;AACA;AACA,mCAAmC,uFAA+B;AAClE,MAAM;AACN,mCAAmC,uFAA+B;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uFAA+B;AACnD;AACA,qEAAqE,yFAAgC;AACrG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,wEAAe;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2GAA2G,WAAW;AACtH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,WAAW;AAC5C,yDAAyD,WAAW;AACpE;AACA;AACA;AACA,gEAAgE,WAAW;AAC3E;AACA;AACA,qGAAqG,WAAW;AAChH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qFAAqF,WAAW;AAChG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4GAA4G,WAAW;AACvH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,WAAW;AAC7C,0DAA0D,WAAW;AACrE;AACA;AACA;AACA,iEAAiE,WAAW;AAC5E;AACA;AACA,sGAAsG,WAAW;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sFAAsF,WAAW;AACjG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,OAAO;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uBAAuB;AAC3C,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,+EAAsB;AAClC,+BAA+B,+EAAsB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,iJAAiJ;AAC9L;AACA,gBAAgB,+EAAsB;AACtC,mCAAmC,+EAAsB;AACzD;AACA;AACA;AACA,qDAAqD,uIAAuI;AAC5L;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,+BAA+B;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,UAAU,kCAAkC;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uEAAuE,oDAAoD;AAC3H,iEAAiE,oDAAoD;AACrH,iFAAiF,oDAAoD;AACrI;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,eAAe;AACrC;AACA;AACA;AACA;AACA,0BAA0B,mBAAmB;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,SAAS;AAC3D;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,KAAK;AACL,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,iBAAiB;AACrC;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,+CAA+C,kBAAkB;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,+EAAsB;AACvC;AACA;AACA;AACA,SAAS;AACT;AACA,UAAU,kCAAkC,qEAAqE;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2EAA2E,wEAAgB;AAC3F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa,qEAAa;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,qEAAa;AACjC,iCAAiC,qEAAa;AAC9C;AACA,kBAAkB,SAAS,wEAAgB;AAC3C,iCAAiC,wEAAgB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,qEAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kFAAkF,IAAI;AACtF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,QAAQ;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,QAAQ;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wEAAgB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,6BAA6B;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qEAAqE,UAAU;AAC/E;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,sCAAsC,4BAA4B;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,qCAAqC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC,2FAA4B;AAChE;AACA;AACA;AACA;AACA,yCAAyC,qHAAiC;AAC1E;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,SAAI;AACxB,yCAAyC,sEAAU,CAAC,SAAI,gBAAgB,SAAI;AAC5E;AACA,kBAAkB;AAClB;AACA;AACA;AACA,oBAAoB,WAAM;AAC1B,yCAAyC,sEAAU,CAAC,WAAM,gBAAgB,WAAM;AAChF;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,cAAS,mCAAmC,yFAAgC;AAChG;AACA,sCAAsC,mEAAO,CAAC,cAAS,iDAAiD,cAAS;AACjH;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,oDAAoD;AACxE;AACA;AACA;AACA;AACA;AACA,wCAAwC,qHAAiC;AACzE;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;AACA;AACA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,wCAAwC;AACrF,kCAAkC,uDAAuD;AACzF;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,qHAAiC;AAC9E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,KAAK,SAAS,qEAAS;AAC5E;AACA,KAAK;AACL;AACA;AACA;AACA,yBAAyB,sEAAU;AACnC;AACA;AACA;AACA;AACA,0BAA0B,sEAAU;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,mEAAO;AAC1C;AACA;AACA;AACA,mBAAmB,kEAAM;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wEAAwE,UAAU;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,IAAI;AAC7D;AACA;AACA;AACA;AACA;AACA,sCAAsC,wEAAe;AACrD;AACA;AACA;AACA;AACA;AACA,4CAA4C,0BAA0B;AACtE;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,wCAAwC;AACjG,8CAA8C,uDAAuD;AACrG;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,oDAAoD;AACpF;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,WAAW,QAAQ,KAAK;AACzD;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,KAAK;AACxC,oCAAoC,KAAK,YAAY,UAAU;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,wBAAwB,WAAW,WAAW;AACnF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd,qEAAqE,WAAW;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd,sEAAsE,YAAY;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sEAAsE,cAAc,IAAI,YAAY;AACpG;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,YAAY,IAAI,eAAe,GAAG,WAAW;AACxF;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,kCAAkC;AAClC,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB,qBAAqB;AACrB,gBAAgB;AAChB,kBAAkB;AAClB,qBAAqB;AACrB,sBAAsB;AACtB,sBAAsB;AACtB,wBAAwB;AACxB;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,uFAA+B;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,SAAI;AAClB,gBAAgB,WAAM;AACtB,mBAAmB,cAAS;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,qEAAa;AACxC,yBAAyB,qEAAa;AACtC;AACA,wCAAwC,gCAAgC,qEAAa;AACrF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yCAAyC;AACzC;AACA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;AACA;AACA;AACA,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,sBAAsB,4GAAmD;AACzE;AACA,aAAa;AACb;AACA;AACA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,6CAAS;AAC9B;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,6CAAS;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sources":["webpack://mwicombatsimulator/./src/combatsimulator/ability.js","webpack://mwicombatsimulator/./src/combatsimulator/achievement.js","webpack://mwicombatsimulator/./src/combatsimulator/buff.js","webpack://mwicombatsimulator/./src/combatsimulator/combatUnit.js","webpack://mwicombatsimulator/./src/combatsimulator/consumable.js","webpack://mwicombatsimulator/./src/combatsimulator/equipment.js","webpack://mwicombatsimulator/./src/combatsimulator/houseRoom.js","webpack://mwicombatsimulator/./src/combatsimulator/player.js","webpack://mwicombatsimulator/./src/combatsimulator/trigger.js","webpack://mwicombatsimulator/webpack/bootstrap","webpack://mwicombatsimulator/webpack/runtime/define property getters","webpack://mwicombatsimulator/webpack/runtime/get javascript chunk filename","webpack://mwicombatsimulator/webpack/runtime/global","webpack://mwicombatsimulator/webpack/runtime/hasOwnProperty shorthand","webpack://mwicombatsimulator/webpack/runtime/make namespace object","webpack://mwicombatsimulator/webpack/runtime/publicPath","webpack://mwicombatsimulator/webpack/runtime/jsonp chunk loading","webpack://mwicombatsimulator/./src/main.js"],"sourcesContent":["import Buff from \"./buff\";\r\nimport abilityDetailMap from \"./data/abilityDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nconst abilityFromCombatStat = {\r\n \"blaze\":\r\n {\r\n \"hrid\": \"/abilities/blaze\",\r\n \"name\": \"Blaze\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"allEnemies\",\r\n \"effectType\": \"/ability_effect_types/damage\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"/damage_types/fire\",\r\n \"baseDamageFlat\": 0,\r\n \"baseDamageFlatLevelBonus\": 0.0,\r\n \"baseDamageRatio\": 0.3,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/number_of_active_units\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n },\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/current_hp\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n }\r\n ],\r\n },\r\n \"bloom\":\r\n {\r\n \"hrid\": \"/abilities/bloom\",\r\n \"name\": \"Bloom\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"lowestHpAlly\",\r\n \"effectType\": \"/ability_effect_types/heal\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"\",\r\n \"baseDamageFlat\": 10,\r\n \"baseDamageFlatLevelBonus\": 0,\r\n \"baseDamageRatio\": 0.15,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_allies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/lowest_hp_percentage\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/less_than_equal\",\r\n \"value\": 100\r\n }\r\n ],\r\n }\r\n}\r\n\r\nclass Ability {\r\n constructor(hrid, level = 1, triggers = null) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameAbility = abilityDetailMap[hrid];\r\n if (!gameAbility) {\r\n gameAbility = abilityFromCombatStat[hrid];\r\n }\r\n if (!gameAbility) {\r\n throw new Error(\"No ability found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.manaCost = gameAbility.manaCost;\r\n this.cooldownDuration = gameAbility.cooldownDuration;\r\n this.castDuration = gameAbility.castDuration;\r\n this.isSpecialAbility = gameAbility.isSpecialAbility;\r\n\r\n this.abilityEffects = [];\r\n\r\n for (const effect of gameAbility.abilityEffects) {\r\n let abilityEffect = {\r\n targetType: effect.targetType,\r\n effectType: effect.effectType,\r\n combatStyleHrid: effect.combatStyleHrid,\r\n damageType: effect.damageType,\r\n damageFlat: effect.baseDamageFlat + (this.level - 1) * effect.baseDamageFlatLevelBonus,\r\n damageRatio: effect.baseDamageRatio + (this.level - 1) * effect.baseDamageRatioLevelBonus,\r\n bonusAccuracyRatio: effect.bonusAccuracyRatio + (this.level - 1) * effect.bonusAccuracyRatioLevelBonus,\r\n damageOverTimeRatio: effect.damageOverTimeRatio,\r\n damageOverTimeDuration: effect.damageOverTimeDuration,\r\n armorDamageRatio: effect.armorDamageRatio + (this.level - 1) * effect.armorDamageRatioLevelBonus,\r\n hpDrainRatio: effect.hpDrainRatio,\r\n pierceChance: effect.pierceChance,\r\n blindChance: effect.blindChance,\r\n blindDuration: effect.blindDuration,\r\n silenceChance: effect.silenceChance,\r\n silenceDuration: effect.silenceDuration,\r\n stunChance: effect.stunChance,\r\n stunDuration: effect.stunDuration,\r\n spendHpRatio: effect.spendHpRatio,\r\n buffs: null,\r\n };\r\n if (effect.buffs) {\r\n abilityEffect.buffs = [];\r\n for (const buff of effect.buffs) {\r\n abilityEffect.buffs.push(new Buff(buff, this.level));\r\n }\r\n }\r\n this.abilityEffects.push(abilityEffect);\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameAbility.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let ability = new Ability(dto.hrid, dto.level, triggers);\r\n\r\n return ability;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n\r\n if (source.isSilenced) {\r\n return false;\r\n }\r\n\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = this.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Ability;\r\n","import Buff from \"./buff\";\r\nimport achievementTierDetailMap from \"./data/achievementTierDetailMap.json\";\r\nimport achievementDetailMap from \"./data/achievementDetailMap.json\";\r\n\r\nclass Achievement {\r\n constructor(achievements) {\r\n this.achievements = achievements;\r\n this.buffs = [];\r\n\r\n for(const tier of Object.values(achievementTierDetailMap)) {\r\n let isGetAll = true;\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid)\r\n for(const achievement of Object.values(detailMap)) {\r\n if(!this.achievements[achievement.hrid] || this.achievements[achievement.hrid] == false) {\r\n isGetAll = false;\r\n break;\r\n }\r\n }\r\n if(isGetAll) {\r\n let buff = new Buff(tier.buff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default Achievement;","class Buff {\r\n startTime;\r\n\r\n constructor(buff, level = 1) {\r\n this.uniqueHrid = buff.uniqueHrid;\r\n this.typeHrid = buff.typeHrid;\r\n this.ratioBoost = buff.ratioBoost + (level - 1) * buff.ratioBoostLevelBonus;\r\n this.flatBoost = buff.flatBoost + (level - 1) * buff.flatBoostLevelBonus;\r\n this.duration = buff.duration;\r\n this.multiplierForSkillHrid = buff.multiplierForSkillHrid ?? \"\";\r\n this.multiplierPerSkillLevel = buff.multiplierPerSkillLevel ?? 0;\r\n }\r\n}\r\n\r\nexport default Buff;\r\n","class CombatUnit {\r\n isPlayer;\r\n isStunned = false;\r\n stunExpireTime = null;\r\n isBlinded = false;\r\n blindExpireTime = null;\r\n isSilenced = false;\r\n silenceExpireTime = null;\r\n\r\n isOutOfMana = false;\r\n\r\n // Base levels which don't change after initialization\r\n staminaLevel = 1;\r\n intelligenceLevel = 1;\r\n attackLevel = 1;\r\n meleeLevel = 1;\r\n defenseLevel = 1;\r\n rangedLevel = 1;\r\n magicLevel = 1;\r\n\r\n experience = 0;\r\n experienceRate = 0;\r\n enrageTime = 0;\r\n\r\n abilities = [null, null, null, null];\r\n food = [null, null, null];\r\n drinks = [null, null, null];\r\n houseRooms = [];\r\n achievements = null;\r\n dropTable = [];\r\n rareDropTable = [];\r\n abilityManaCosts = new Map();\r\n\r\n // Calculated combat stats including temporary buffs\r\n combatDetails = {\r\n staminaLevel: 1,\r\n intelligenceLevel: 1,\r\n attackLevel: 1,\r\n meleeLevel: 1,\r\n defenseLevel: 1,\r\n rangedLevel: 1,\r\n magicLevel: 1,\r\n maxHitpoints: 110,\r\n currentHitpoints: 110,\r\n maxManapoints: 110,\r\n currentManapoints: 110,\r\n stabAccuracyRating: 11,\r\n slashAccuracyRating: 11,\r\n smashAccuracyRating: 11,\r\n rangedAccuracyRating: 11,\r\n magicAccuracyRating: 11,\r\n stabMaxDamage: 11,\r\n slashMaxDamage: 11,\r\n smashMaxDamage: 11,\r\n rangedMaxDamage: 11,\r\n magicMaxDamage: 11,\r\n stabEvasionRating: 11,\r\n slashEvasionRating: 11,\r\n smashEvasionRating: 11,\r\n rangedEvasionRating: 11,\r\n magicEvasionRating: 11,\r\n defensiveMaxDamage: 0,\r\n totalArmor: 0.2,\r\n totalWaterResistance: 0.4,\r\n totalNatureResistance: 0.4,\r\n totalFireResistance: 0.4,\r\n abilityHaste: 0,\r\n tenacity: 0,\r\n totalThreat: 100,\r\n combatStats: {\r\n combatStyleHrid: \"/combat_styles/smash\",\r\n damageType: \"/damage_types/physical\",\r\n attackInterval: 3000000000,\r\n autoAttackDamage: 0,\r\n abilityDamage: 0,\r\n criticalRate: 0,\r\n criticalDamage: 0,\r\n stabAccuracy: 0,\r\n slashAccuracy: 0,\r\n smashAccuracy: 0,\r\n rangedAccuracy: 0,\r\n magicAccuracy: 0,\r\n stabDamage: 0,\r\n slashDamage: 0,\r\n smashDamage: 0,\r\n rangedDamage: 0,\r\n magicDamage: 0,\r\n defensiveDamage: 0,\r\n taskDamage: 0,\r\n physicalAmplify: 0,\r\n waterAmplify: 0,\r\n natureAmplify: 0,\r\n fireAmplify: 0,\r\n healingAmplify: 0,\r\n physicalThorns: 0,\r\n elementalThorns: 0,\r\n maxHitpoints: 0,\r\n maxManapoints: 0,\r\n stabEvasion: 0,\r\n slashEvasion: 0,\r\n smashEvasion: 0,\r\n rangedEvasion: 0,\r\n magicEvasion: 0,\r\n armor: 0,\r\n waterResistance: 0,\r\n natureResistance: 0,\r\n fireResistance: 0,\r\n lifeSteal: 0,\r\n hpRegenPer10: 0.01,\r\n mpRegenPer10: 0.01,\r\n combatDropRate: 0,\r\n combatDropQuantity: 0,\r\n combatRareFind: 0,\r\n combatExperience: 0,\r\n foodSlots: 1,\r\n drinkSlots: 1,\r\n armorPenetration: 0,\r\n waterPenetration: 0,\r\n naturePenetration: 0,\r\n firePenetration: 0,\r\n manaLeech: 0,\r\n castSpeed: 0,\r\n threat: 100,\r\n parry: 0,\r\n mayhem: 0,\r\n pierce: 0,\r\n curse: 0,\r\n ripple: 0,\r\n bloom: 0,\r\n blaze: 0,\r\n weaken: 0,\r\n fury: 0,\r\n foodHaste: 0,\r\n drinkConcentration: 0,\r\n damageTaken: 0,\r\n attackSpeed: 0,\r\n armorDamageRatio: 0,\r\n hpDrainRatio: 0,\r\n primaryTraining: \"\",\r\n focusTraining: \"\",\r\n staminaExperience: 0,\r\n intelligenceExperience: 0,\r\n attackExperience: 0,\r\n defenseExperience: 0,\r\n meleeExperience: 0,\r\n rangedExperience: 0,\r\n magicExperience: 0,\r\n retaliation: 0,\r\n },\r\n };\r\n combatBuffs = {};\r\n permanentBuffs = {};\r\n zoneBuffs = {};\r\n extraBuffs = {};\r\n\r\n constructor() { }\r\n\r\n updateCombatDetails() {\r\n if (this.isPlayer) {\r\n if (this.combatDetails.combatStats.hpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01 + this.combatDetails.combatStats.hpRegenPer10;\r\n }\r\n if (this.combatDetails.combatStats.mpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01 + this.combatDetails.combatStats.mpRegenPer10;\r\n }\r\n }\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((stat) => {\r\n this.combatDetails[stat + \"Level\"] = this[stat + \"Level\"];\r\n let boosts = this.getBuffBoosts(\"/buff_types/\" + stat + \"_level\");\r\n boosts.forEach((buff) => {\r\n this.combatDetails[stat + \"Level\"] += (this[stat + \"Level\"] * buff.ratioBoost);\r\n this.combatDetails[stat + \"Level\"] += buff.flatBoost;\r\n });\r\n });\r\n\r\n this.combatDetails.maxHitpoints = Math.floor\r\n (10 * (10 + this.combatDetails.staminaLevel) + this.combatDetails.combatStats.maxHitpoints);\r\n this.combatDetails.maxManapoints = Math.floor\r\n (10 * (10 + this.combatDetails.intelligenceLevel) + this.combatDetails.combatStats.maxManapoints);\r\n\r\n let accuracyRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_accuracy\").ratioBoost;\r\n let damageRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_damage\").ratioBoost;\r\n // if (accuracyRatioBoostFromFury > 0) {\r\n // console.log(\"Fury Boost: \" + accuracyRatioBoostFromFury);\r\n // }\r\n\r\n let accuracyRatioBoost = this.getBuffBoost(\"/buff_types/accuracy\").ratioBoost;\r\n let damageRatioBoost = this.getBuffBoost(\"/buff_types/damage\").ratioBoost;\r\n\r\n [\"stab\", \"slash\", \"smash\"].forEach((style) => {\r\n this.combatDetails[style + \"AccuracyRating\"] =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Accuracy\"]) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails[style + \"MaxDamage\"] =\r\n (10 + this.combatDetails.meleeLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Damage\"]) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n let baseEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats[style + \"Evasion\"]);\r\n this.combatDetails[style + \"EvasionRating\"] = baseEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails[style + \"EvasionRating\"] += boost.flatBoost;\r\n this.combatDetails[style + \"EvasionRating\"] += baseEvasion * boost.ratioBoost;\r\n }\r\n });\r\n\r\n this.combatDetails.defensiveMaxDamage = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.defensiveDamage);\r\n\r\n // when equiped bulwark\r\n if (this.equipment?.['/equipment_types/two_hand']?.hrid.includes(\"bulwark\")) {\r\n this.combatDetails.smashMaxDamage += this.combatDetails.defensiveMaxDamage;\r\n }\r\n\r\n this.combatDetails.rangedAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.rangedAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.rangedMaxDamage =\r\n (10 + this.combatDetails.rangedLevel) *\r\n (1 + this.combatDetails.combatStats.rangedDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseRangedEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.rangedEvasion);\r\n this.combatDetails.rangedEvasionRating = baseRangedEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.rangedEvasionRating += boost.flatBoost;\r\n this.combatDetails.rangedEvasionRating += baseRangedEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.damageTaken = this.getBuffBoost(\"/buff_types/damage_taken\").flatBoost;\r\n // if (this.combatDetails.combatStats.damageTaken > 0) {\r\n // console.log(\"Damage taken: \" + this.combatDetails.combatStats.damageTaken);\r\n // }\r\n\r\n this.combatDetails.magicAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.magicAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.magicMaxDamage =\r\n (10 + this.combatDetails.magicLevel) *\r\n (1 + this.combatDetails.combatStats.magicDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseMagicEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.magicEvasion);\r\n this.combatDetails.magicEvasionRating = baseMagicEvasion;\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.magicEvasionRating += boost.flatBoost;\r\n this.combatDetails.magicEvasionRating += baseMagicEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.physicalAmplify += this.getBuffBoost(\"/buff_types/physical_amplify\").flatBoost;\r\n this.combatDetails.combatStats.waterAmplify += this.getBuffBoost(\"/buff_types/water_amplify\").flatBoost;\r\n this.combatDetails.combatStats.natureAmplify += this.getBuffBoost(\"/buff_types/nature_amplify\").flatBoost;\r\n this.combatDetails.combatStats.fireAmplify += this.getBuffBoost(\"/buff_types/fire_amplify\").flatBoost;\r\n\r\n this.combatDetails.combatStats.attackInterval /= (1 + (this.combatDetails.attackLevel / 2000));\r\n\r\n let baseAttackSpeed = this.combatDetails.combatStats.attackSpeed;\r\n this.combatDetails.combatStats.attackInterval /= (1 + baseAttackSpeed);\r\n let attackIntervalBoosts = this.getBuffBoosts(\"/buff_types/attack_speed\");\r\n let attackIntervalRatioBoost = attackIntervalBoosts\r\n .map((boost) => boost.ratioBoost)\r\n .reduce((prev, cur) => prev + cur, 0);\r\n this.combatDetails.combatStats.attackInterval /= (1 + attackIntervalRatioBoost);\r\n\r\n let baseArmor = 0.2 * this.combatDetails.defenseLevel + this.combatDetails.combatStats.armor;\r\n this.combatDetails.totalArmor = baseArmor;\r\n let armorBoosts = this.getBuffBoosts(\"/buff_types/armor\");\r\n for (const boost of armorBoosts) {\r\n this.combatDetails.totalArmor += boost.flatBoost;\r\n this.combatDetails.totalArmor += baseArmor * boost.ratioBoost;\r\n }\r\n\r\n let baseWaterResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.waterResistance;\r\n this.combatDetails.totalWaterResistance = baseWaterResistance;\r\n let waterResistanceBoosts = this.getBuffBoosts(\"/buff_types/water_resistance\");\r\n for (const boost of waterResistanceBoosts) {\r\n this.combatDetails.totalWaterResistance += boost.flatBoost;\r\n this.combatDetails.totalWaterResistance += baseWaterResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseNatureResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.natureResistance;\r\n this.combatDetails.totalNatureResistance = baseNatureResistance;\r\n let natureResistanceBoosts = this.getBuffBoosts(\"/buff_types/nature_resistance\");\r\n for (const boost of natureResistanceBoosts) {\r\n this.combatDetails.totalNatureResistance += boost.flatBoost;\r\n this.combatDetails.totalNatureResistance += baseNatureResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseFireResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.fireResistance;\r\n this.combatDetails.totalFireResistance = baseFireResistance;\r\n let fireResistanceBoosts = this.getBuffBoosts(\"/buff_types/fire_resistance\");\r\n for (const boost of fireResistanceBoosts) {\r\n this.combatDetails.totalFireResistance += boost.flatBoost;\r\n this.combatDetails.totalFireResistance += baseFireResistance * boost.ratioBoost;\r\n }\r\n\r\n let hpRegenBoosts = this.getBuffBoost(\"/buff_types/hp_regen\");\r\n this.combatDetails.combatStats.hpRegenPer10 += this.combatDetails.combatStats.hpRegenPer10 * hpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.hpRegenPer10 += hpRegenBoosts.flatBoost;\r\n\r\n let mpRegenBoosts = this.getBuffBoost(\"/buff_types/mp_regen\");\r\n this.combatDetails.combatStats.mpRegenPer10 += this.combatDetails.combatStats.mpRegenPer10 * mpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.mpRegenPer10 += mpRegenBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.lifeSteal += this.getBuffBoost(\"/buff_types/life_steal\").flatBoost;\r\n this.combatDetails.combatStats.physicalThorns += this.getBuffBoost(\r\n \"/buff_types/physical_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.elementalThorns += this.getBuffBoost(\r\n \"/buff_types/elemental_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.combatExperience += this.getBuffBoost(\"/buff_types/wisdom\").flatBoost;\r\n this.combatDetails.combatStats.criticalRate += this.getBuffBoost(\"/buff_types/critical_rate\").flatBoost;\r\n this.combatDetails.combatStats.criticalDamage += this.getBuffBoost(\"/buff_types/critical_damage\").flatBoost;\r\n\r\n this.combatDetails.combatStats.castSpeed += this.getBuffBoost(\"/buff_types/cast_speed\").flatBoost;\r\n this.combatDetails.combatStats.castSpeed += this.combatDetails[\"attackLevel\"] / 2000;\r\n\r\n let combatDropRateBoosts = this.getBuffBoost(\"/buff_types/combat_drop_rate\");\r\n this.combatDetails.combatStats.combatDropRate += (1 + this.combatDetails.combatStats.combatDropRate) * combatDropRateBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropRate += combatDropRateBoosts.flatBoost;\r\n let combatRareFindBoosts = this.getBuffBoost(\"/buff_types/rare_find\");\r\n this.combatDetails.combatStats.combatRareFind += (1 + this.combatDetails.combatStats.combatRareFind) * combatRareFindBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatRareFind += combatRareFindBoosts.flatBoost;\r\n let combatDropQuantityBoosts = this.getBuffBoost(\"/buff_types/combat_drop_quantity\");\r\n this.combatDetails.combatStats.combatDropQuantity += (1 + this.combatDetails.combatStats.combatDropQuantity) * combatDropQuantityBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropQuantity += combatDropQuantityBoosts.flatBoost;\r\n\r\n let baseThreat = 100 + this.combatDetails.combatStats.threat;\r\n this.combatDetails.totalThreat = baseThreat;\r\n let threatBoosts = this.getBuffBoost(\"/buff_types/threat\");\r\n if (threatBoosts.ratioBoost !== 0) {\r\n this.combatDetails.combatStats.threat += baseThreat * threatBoosts.ratioBoost;\r\n } else {\r\n this.combatDetails.combatStats.threat = baseThreat;\r\n }\r\n this.combatDetails.combatStats.threat += threatBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.retaliation += this.getBuffBoost(\"/buff_types/retaliation\").flatBoost;\r\n }\r\n\r\n addBuff(buff, currentTime) {\r\n buff.startTime = currentTime;\r\n this.combatBuffs[buff.uniqueHrid] = buff;\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n removeBuff(buff) {\r\n if (!this.combatBuffs[buff.uniqueHrid]) {\r\n return;\r\n }\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n addPermanentBuff(buff) {\r\n if (this.permanentBuffs[buff.typeHrid]) {\r\n this.permanentBuffs[buff.typeHrid].flatBoost += buff.flatBoost;\r\n this.permanentBuffs[buff.typeHrid].ratioBoost += buff.ratioBoost;\r\n } else {\r\n this.permanentBuffs[buff.typeHrid] = buff;\r\n }\r\n }\r\n\r\n generatePermanentBuffs() {\r\n for (let i = 0; i < this.houseRooms.length; i++) {\r\n const houseRoom = this.houseRooms[i];\r\n houseRoom.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n\r\n if (this.achievements) {\r\n this.achievements.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.zoneBuffs) {\r\n this.zoneBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.extraBuffs) {\r\n this.extraBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n }\r\n\r\n removeExpiredBuffs(currentTime) {\r\n let expiredBuffs = Object.values(this.combatBuffs).filter(\r\n (buff) => buff.startTime + buff.duration <= currentTime\r\n );\r\n expiredBuffs.forEach((buff) => {\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n });\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearBuffs() {\r\n this.combatBuffs = structuredClone(this.permanentBuffs);\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearCCs() {\r\n this.isStunned = false;\r\n this.stunExpireTime = null;\r\n this.isSilenced = false;\r\n this.silenceExpireTime = null;\r\n this.isBlinded = false;\r\n this.blindExpireTime = null;\r\n this.combatDetails.combatStats.damageTaken = 0;\r\n }\r\n\r\n getBuffBoosts(type) {\r\n let boosts = [];\r\n Object.values(this.combatBuffs)\r\n .filter((buff) => buff.typeHrid == type)\r\n .forEach((buff) => {\r\n boosts.push({ ratioBoost: buff.ratioBoost, flatBoost: buff.flatBoost });\r\n });\r\n\r\n return boosts;\r\n }\r\n\r\n getBuffBoost(type) {\r\n let boosts = this.getBuffBoosts(type);\r\n\r\n let boost = {\r\n ratioBoost: 0,\r\n flatBoost: 0,\r\n };\r\n\r\n for (let i = 0; i < boosts.length; i++) {\r\n boost.ratioBoost += boosts[i]?.ratioBoost ?? 0;\r\n boost.flatBoost += boosts[i]?.flatBoost ?? 0;\r\n }\r\n\r\n return boost;\r\n }\r\n\r\n reset(currentTime = 0) {\r\n this.clearCCs();\r\n this.clearBuffs();\r\n this.updateCombatDetails();\r\n this.resetCooldowns(currentTime);\r\n\r\n this.combatDetails.currentHitpoints = this.combatDetails.maxHitpoints;\r\n this.combatDetails.currentManapoints = this.combatDetails.maxManapoints;\r\n }\r\n\r\n resetCooldowns(currentTime = 0) {\r\n this.food.filter((food) => food != null).forEach((food) => (food.lastUsed = Number.MIN_SAFE_INTEGER));\r\n this.drinks.filter((drink) => drink != null).forEach((drink) => (drink.lastUsed = Number.MIN_SAFE_INTEGER));\r\n\r\n let haste = this.combatDetails.combatStats.abilityHaste;\r\n\r\n this.abilities\r\n .filter((ability) => ability != null)\r\n .forEach((ability) => {\r\n if (this.isPlayer) {\r\n ability.lastUsed = Number.MIN_SAFE_INTEGER;\r\n } else {\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n ability.lastUsed = currentTime - Math.floor(cooldownDuration * 0.5) + Math.floor(Math.random() * cooldownDuration * 0.5);\r\n }\r\n });\r\n }\r\n\r\n addHitpoints(hitpoints) {\r\n let hitpointsAdded = 0;\r\n\r\n if (this.combatDetails.currentHitpoints >= this.combatDetails.maxHitpoints) {\r\n return hitpointsAdded;\r\n }\r\n\r\n let newHitpoints = Math.min(this.combatDetails.currentHitpoints + hitpoints, this.combatDetails.maxHitpoints);\r\n hitpointsAdded = newHitpoints - this.combatDetails.currentHitpoints;\r\n this.combatDetails.currentHitpoints = newHitpoints;\r\n\r\n return hitpointsAdded;\r\n }\r\n\r\n addManapoints(manapoints) {\r\n let manapointsAdded = 0;\r\n\r\n if (this.combatDetails.currentManapoints >= this.combatDetails.maxManapoints) {\r\n return manapointsAdded;\r\n }\r\n\r\n let newManapoints = Math.min(\r\n this.combatDetails.currentManapoints + manapoints,\r\n this.combatDetails.maxManapoints\r\n );\r\n manapointsAdded = newManapoints - this.combatDetails.currentManapoints;\r\n this.combatDetails.currentManapoints = newManapoints;\r\n\r\n return manapointsAdded;\r\n }\r\n}\r\n\r\nexport default CombatUnit;\r\n","import Buff from \"./buff\";\r\nimport itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nclass Consumable {\r\n constructor(hrid, triggers = null) {\r\n this.hrid = hrid;\r\n\r\n let gameConsumable = itemDetailMap[this.hrid];\r\n if (!gameConsumable) {\r\n throw new Error(\"No consumable found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.cooldownDuration = gameConsumable.consumableDetail.cooldownDuration;\r\n this.hitpointRestore = gameConsumable.consumableDetail.hitpointRestore;\r\n this.manapointRestore = gameConsumable.consumableDetail.manapointRestore;\r\n this.recoveryDuration = gameConsumable.consumableDetail.recoveryDuration;\r\n this.catagoryHrid = gameConsumable.categoryHrid;\r\n\r\n this.buffs = [];\r\n if (gameConsumable.consumableDetail.buffs) {\r\n for (const consumableBuff of gameConsumable.consumableDetail.buffs) {\r\n let buff = new Buff(consumableBuff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameConsumable.consumableDetail.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let consumable = new Consumable(dto.hrid, triggers);\r\n\r\n return consumable;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n let consumableHaste;\r\n if (this.catagoryHrid.includes(\"food\")) {\r\n consumableHaste = source.combatDetails.combatStats.foodHaste\r\n } else {\r\n consumableHaste = source.combatDetails.combatStats.drinkConcentration;\r\n }\r\n let cooldownDuration = this.cooldownDuration;\r\n if (consumableHaste > 0) {\r\n cooldownDuration = cooldownDuration / (1 + consumableHaste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Consumable;\r\n","import itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport enhancementLevelTotalMultiplierTable from \"./data/enhancementLevelTotalBonusMultiplierTable.json\";\r\n\r\nclass Equipment {\r\n constructor(hrid, enhancementLevel) {\r\n this.hrid = hrid;\r\n let gameItem = itemDetailMap[this.hrid];\r\n if (!gameItem) {\r\n throw new Error(\"No equipment found for hrid: \" + this.hrid);\r\n }\r\n this.gameItem = gameItem;\r\n this.enhancementLevel = enhancementLevel;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let equipment = new Equipment(dto.hrid, dto.enhancementLevel);\r\n\r\n return equipment;\r\n }\r\n\r\n getCombatStat(combatStat) {\r\n let multiplier = enhancementLevelTotalMultiplierTable[this.enhancementLevel];\r\n if(this.gameItem.equipmentDetail.combatStats[combatStat]) {\r\n let enhancementBonus = this.gameItem.equipmentDetail.combatEnhancementBonuses[combatStat] || 0;\r\n let stat = this.gameItem.equipmentDetail.combatStats[combatStat] + multiplier * enhancementBonus;\r\n return stat;\r\n }\r\n return 0;\r\n }\r\n\r\n getCombatStyle() {\r\n return this.gameItem.equipmentDetail.combatStats.combatStyleHrids[0];\r\n }\r\n\r\n getDamageType() {\r\n return this.gameItem.equipmentDetail.combatStats.damageType;\r\n }\r\n\r\n getPrimaryTraining() {\r\n return this.gameItem.equipmentDetail.combatStats.primaryTraining;\r\n }\r\n\r\n getFocusTraining(){\r\n return this.gameItem.equipmentDetail.combatStats.focusTraining;\r\n }\r\n}\r\n\r\nexport default Equipment;\r\n","import Buff from \"./buff\";\r\nimport houseRoomDetailMap from \"./data/houseRoomDetailMap.json\";\r\n\r\nclass HouseRoom {\r\n constructor(hrid, level) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameHouseRoom = houseRoomDetailMap[this.hrid];\r\n if (!gameHouseRoom) {\r\n throw new Error(\"No house room found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.buffs = [];\r\n if (gameHouseRoom.actionBuffs) {\r\n for (const actionBuff of gameHouseRoom.actionBuffs) {\r\n let buff = new Buff(actionBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n if (gameHouseRoom.globalBuffs) {\r\n for (const globalBuff of gameHouseRoom.globalBuffs) {\r\n let buff = new Buff(globalBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default HouseRoom;","import Ability from \"./ability\";\r\nimport CombatUnit from \"./combatUnit\";\r\nimport Consumable from \"./consumable\";\r\nimport Equipment from \"./equipment\";\r\nimport HouseRoom from \"./houseRoom\";\r\nimport Achievement from \"./achievement\";\r\n\r\nclass Player extends CombatUnit {\r\n equipment = {\r\n \"/equipment_types/head\": null,\r\n \"/equipment_types/body\": null,\r\n \"/equipment_types/legs\": null,\r\n \"/equipment_types/feet\": null,\r\n \"/equipment_types/hands\": null,\r\n \"/equipment_types/main_hand\": null,\r\n \"/equipment_types/two_hand\": null,\r\n \"/equipment_types/off_hand\": null,\r\n \"/equipment_types/pouch\": null,\r\n \"/equipment_types/back\": null,\r\n };\r\n\r\n constructor() {\r\n super();\r\n\r\n this.isPlayer = true;\r\n this.hrid = \"player\";\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let player = new Player();\r\n\r\n player.staminaLevel = dto.staminaLevel;\r\n player.intelligenceLevel = dto.intelligenceLevel;\r\n player.attackLevel = dto.attackLevel;\r\n player.meleeLevel = dto.meleeLevel;\r\n player.defenseLevel = dto.defenseLevel;\r\n player.rangedLevel = dto.rangedLevel;\r\n player.magicLevel = dto.magicLevel;\r\n\r\n player.hrid = dto.hrid;\r\n\r\n for (const [key, value] of Object.entries(dto.equipment)) {\r\n player.equipment[key] = value ? Equipment.createFromDTO(value) : null;\r\n }\r\n\r\n player.food = dto.food.map((food) => (food ? Consumable.createFromDTO(food) : null));\r\n player.drinks = dto.drinks.map((drink) => (drink ? Consumable.createFromDTO(drink) : null));\r\n player.abilities = dto.abilities.map((ability) => (ability ? Ability.createFromDTO(ability) : null));\r\n Object.entries(dto.houseRooms).forEach(houseRoom => {\r\n if (houseRoom[1] > 0) {\r\n player.houseRooms.push(new HouseRoom(houseRoom[0], houseRoom[1]))\r\n }\r\n });\r\n\r\n player.achievements = new Achievement(dto.achievements);\r\n\r\n player.debuffOnLevelGap = dto.debuffOnLevelGap;\r\n\r\n return player;\r\n }\r\n\r\n updateCombatDetails() {\r\n if (this.equipment[\"/equipment_types/main_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/main_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/main_hand\"].getPrimaryTraining();\r\n } else if (this.equipment[\"/equipment_types/two_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/two_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/two_hand\"].getPrimaryTraining();\r\n } else {\r\n this.combatDetails.combatStats.combatStyleHrid = \"/combat_styles/smash\";\r\n this.combatDetails.combatStats.damageType = \"/damage_types/physical\";\r\n this.combatDetails.combatStats.attackInterval = 3000000000;\r\n this.combatDetails.combatStats.primaryTraining = \"/skills/melee\";\r\n }\r\n\r\n if (this.equipment[\"/equipment_types/charm\"]) {\r\n this.combatDetails.combatStats.focusTraining = this.equipment[\"/equipment_types/charm\"].getFocusTraining();\r\n } else {\r\n this.combatDetails.combatStats.focusTraining = \"\";\r\n }\r\n\r\n [\r\n \"stabAccuracy\",\r\n \"slashAccuracy\",\r\n \"smashAccuracy\",\r\n \"rangedAccuracy\",\r\n \"magicAccuracy\",\r\n \"stabDamage\",\r\n \"slashDamage\",\r\n \"smashDamage\",\r\n \"rangedDamage\",\r\n \"magicDamage\",\r\n \"defensiveDamage\",\r\n \"taskDamage\",\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"stabEvasion\",\r\n \"slashEvasion\",\r\n \"smashEvasion\",\r\n \"rangedEvasion\",\r\n \"magicEvasion\",\r\n \"armor\",\r\n \"waterResistance\",\r\n \"natureResistance\",\r\n \"fireResistance\",\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"combatDropRate\",\r\n \"combatRareFind\",\r\n \"combatDropQuantity\",\r\n \"combatExperience\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"abilityHaste\",\r\n \"tenacity\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"threat\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"foodHaste\",\r\n \"drinkConcentration\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\",\r\n \"retaliation\"\r\n ].forEach((stat) => {\r\n this.combatDetails.combatStats[stat] = Object.values(this.equipment)\r\n .filter((equipment) => equipment != null)\r\n .map((equipment) => equipment.getCombatStat(stat))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n });\r\n\r\n if (this.equipment[\"/equipment_types/pouch\"]) {\r\n this.combatDetails.combatStats.foodSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"foodSlots\");\r\n this.combatDetails.combatStats.drinkSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"drinkSlots\");\r\n } else {\r\n this.combatDetails.combatStats.foodSlots = 1;\r\n this.combatDetails.combatStats.drinkSlots = 1;\r\n }\r\n\r\n super.updateCombatDetails();\r\n }\r\n}\r\n\r\nexport default Player;\r\n","import combatTriggerDependencyDetailMap from \"./data/combatTriggerDependencyDetailMap.json\";\r\n\r\nclass Trigger {\r\n constructor(dependencyHrid, conditionHrid, comparatorHrid, value = 0) {\r\n this.dependencyHrid = dependencyHrid;\r\n this.conditionHrid = conditionHrid;\r\n this.comparatorHrid = comparatorHrid;\r\n this.value = value;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let trigger = new Trigger(dto.dependencyHrid, dto.conditionHrid, dto.comparatorHrid, dto.value);\r\n\r\n return trigger;\r\n }\r\n\r\n isActive(source, target, friendlies, enemies, currentTime) {\r\n if (combatTriggerDependencyDetailMap[this.dependencyHrid].isSingleTarget) {\r\n return this.isActiveSingleTarget(source, target, currentTime);\r\n } else {\r\n return this.isActiveMultiTarget(friendlies, enemies, currentTime);\r\n }\r\n }\r\n\r\n isActiveSingleTarget(source, target, currentTime) {\r\n let dependencyValue;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/self\":\r\n dependencyValue = this.getDependencyValue(source, currentTime);\r\n break;\r\n case \"/combat_trigger_dependencies/targeted_enemy\":\r\n if (!target) {\r\n return false;\r\n }\r\n dependencyValue = this.getDependencyValue(target, currentTime);\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n isActiveMultiTarget(friendlies, enemies, currentTime) {\r\n let dependency;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/all_allies\":\r\n dependency = friendlies;\r\n break;\r\n case \"/combat_trigger_dependencies/all_enemies\":\r\n if (!enemies) {\r\n return false;\r\n }\r\n dependency = enemies;\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n let dependencyValue;\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/number_of_active_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints > 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/number_of_dead_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints <= 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/lowest_hp_percentage\":\r\n dependencyValue = dependency.reduce((prev, curr) => {\r\n let currentHpPercentage = curr.combatDetails.currentHitpoints / curr.combatDetails.maxHitpoints;\r\n return currentHpPercentage < prev ? currentHpPercentage : prev;\r\n }, 2) * 100;\r\n break;\r\n default:\r\n dependencyValue = dependency\r\n .map((unit) => this.getDependencyValue(unit, currentTime))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n break;\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n getDependencyValue(source, currentTime) {\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/berserk\":\r\n case \"/combat_trigger_conditions/frenzy\":\r\n case \"/combat_trigger_conditions/precision\":\r\n case \"/combat_trigger_conditions/vampirism\":\r\n case \"/combat_trigger_conditions/attack_coffee\":\r\n case \"/combat_trigger_conditions/defense_coffee\":\r\n case \"/combat_trigger_conditions/lucky_coffee\":\r\n case \"/combat_trigger_conditions/magic_coffee\":\r\n case \"/combat_trigger_conditions/melee_coffee\":\r\n case \"/combat_trigger_conditions/ranged_coffee\":\r\n case \"/combat_trigger_conditions/swiftness_coffee\":\r\n case \"/combat_trigger_conditions/wisdom_coffee\":\r\n case \"/combat_trigger_conditions/ice_spear\":\r\n case \"/combat_trigger_conditions/puncture\":\r\n case \"/combat_trigger_conditions/frost_surge\":\r\n case \"/combat_trigger_conditions/elusiveness\":\r\n case \"/combat_trigger_conditions/channeling_coffee\":\r\n case \"/combat_trigger_conditions/fierce_aura\":\r\n case \"/combat_trigger_conditions/invincible_armor\":\r\n case \"/combat_trigger_conditions/invincible_fire_resistance\":\r\n case \"/combat_trigger_conditions/invincible_nature_resistance\":\r\n case \"/combat_trigger_conditions/invincible_water_resistance\":\r\n case \"/combat_trigger_conditions/provoke\":\r\n case \"/combat_trigger_conditions/taunt\":\r\n case \"/combat_trigger_conditions/crippling_slash\":\r\n case \"/combat_trigger_conditions/mana_spring\":\r\n case \"/combat_trigger_conditions/retribution\":\r\n case \"/combat_trigger_conditions/fracturing_impact\":\r\n case \"/combat_trigger_conditions/maim\":\r\n case \"/combat_trigger_conditions/curse\":\r\n case \"/combat_trigger_conditions/weaken\":\r\n let buffHrid = \"/buff_uniques\";\r\n buffHrid += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n return source.combatBuffs[buffHrid];\r\n case \"/combat_trigger_conditions/critical_aura\":\r\n case \"/combat_trigger_conditions/critical_coffee\":\r\n case \"/combat_trigger_conditions/intelligence_coffee\":\r\n case \"/combat_trigger_conditions/stamina_coffee\":\r\n case \"/combat_trigger_conditions/elemental_affinity\":\r\n case \"/combat_trigger_conditions/fury\":\r\n case \"/combat_trigger_conditions/guardian_aura\":\r\n case \"/combat_trigger_conditions/insanity\":\r\n case \"/combat_trigger_conditions/spike_shell\":\r\n case \"/combat_trigger_conditions/toxic_pollen\":\r\n case \"/combat_trigger_conditions/invincible\":\r\n case \"/combat_trigger_conditions/mystic_aura\":\r\n case \"/combat_trigger_conditions/pestilent_shot\":\r\n case \"/combat_trigger_conditions/smoke_burst\":\r\n case \"/combat_trigger_conditions/speed_aura\":\r\n case \"/combat_trigger_conditions/toughness\":\r\n case \"/combat_trigger_conditions/enrage\":\r\n let buffPrefix = \"/buff_uniques\";\r\n buffPrefix += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n let buffs = Object.keys(source.combatBuffs).filter(buff => buff.startsWith(buffPrefix));\r\n return source.combatBuffs[buffs?.[0]];\r\n case \"/combat_trigger_conditions/current_hp\":\r\n return source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/current_mp\":\r\n return source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/missing_hp\":\r\n return source.combatDetails.maxHitpoints - source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/missing_mp\":\r\n return source.combatDetails.maxManapoints - source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/stun_status\":\r\n // Replicate the game's behaviour of \"stun status active\" triggers activating\r\n // immediately after the stun has worn off\r\n return source.isStunned || source.stunExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/blind_status\":\r\n return source.isBlinded || source.blindExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/silence_status\":\r\n return source.isSilenced || source.silenceExpireTime == currentTime;\r\n default:\r\n throw new Error(\"Unknown conditionHrid in trigger: \" + this.conditionHrid);\r\n }\r\n }\r\n\r\n compareValue(dependencyValue) {\r\n switch (this.comparatorHrid) {\r\n case \"/combat_trigger_comparators/greater_than_equal\":\r\n return dependencyValue >= this.value;\r\n case \"/combat_trigger_comparators/less_than_equal\":\r\n return dependencyValue <= this.value;\r\n case \"/combat_trigger_comparators/is_active\":\r\n return !!dependencyValue;\r\n case \"/combat_trigger_comparators/is_inactive\":\r\n return !dependencyValue;\r\n default:\r\n throw new Error(\"Unknown comparatorHrid in trigger: \" + this.comparatorHrid);\r\n }\r\n }\r\n}\r\n\r\nexport default Trigger;\r\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","// This function allow to reference async chunks\n__webpack_require__.u = (chunkId) => {\n\t// return url for filenames based on template\n\treturn \"\" + chunkId + \".bundle.js\";\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","var scriptUrl;\nif (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + \"\";\nvar document = __webpack_require__.g.document;\nif (!scriptUrl && document) {\n\tif (document.currentScript)\n\t\tscriptUrl = document.currentScript.src;\n\tif (!scriptUrl) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tif(scripts.length) {\n\t\t\tvar i = scripts.length - 1;\n\t\t\twhile (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;\n\t\t}\n\t}\n}\n// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration\n// or pass an empty string (\"\") and set the __webpack_public_path__ variable from your code to use your own logic.\nif (!scriptUrl) throw new Error(\"Automatic publicPath is not supported in this browser\");\nscriptUrl = scriptUrl.replace(/#.*$/, \"\").replace(/\\?.*$/, \"\").replace(/\\/[^\\/]+$/, \"/\");\n__webpack_require__.p = scriptUrl;","__webpack_require__.b = document.baseURI || self.location.href;\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t\"main\": 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n// no on chunks loaded\n\n// no jsonp function","import Equipment from \"./combatsimulator/equipment.js\";\r\nimport Player from \"./combatsimulator/player.js\";\r\nimport abilityDetailMap from \"./combatsimulator/data/abilityDetailMap.json\";\r\nimport itemDetailMap from \"./combatsimulator/data/itemDetailMap.json\";\r\nimport houseRoomDetailMap from \"./combatsimulator/data/houseRoomDetailMap.json\";\r\nimport Ability from \"./combatsimulator/ability.js\";\r\nimport Consumable from \"./combatsimulator/consumable.js\";\r\nimport HouseRoom from \"./combatsimulator/houseRoom\"\r\nimport combatTriggerDependencyDetailMap from \"./combatsimulator/data/combatTriggerDependencyDetailMap.json\";\r\nimport combatTriggerConditionDetailMap from \"./combatsimulator/data/combatTriggerConditionDetailMap.json\";\r\nimport combatTriggerComparatorDetailMap from \"./combatsimulator/data/combatTriggerComparatorDetailMap.json\";\r\nimport abilitySlotsLevelRequirementList from \"./combatsimulator/data/abilitySlotsLevelRequirementList.json\";\r\nimport actionDetailMap from \"./combatsimulator/data/actionDetailMap.json\";\r\nimport combatMonsterDetailMap from \"./combatsimulator/data/combatMonsterDetailMap.json\";\r\nimport damageTypeDetailMap from \"./combatsimulator/data/damageTypeDetailMap.json\";\r\nimport combatStyleDetailMap from \"./combatsimulator/data/combatStyleDetailMap.json\";\r\nimport openableLootDropMap from \"./combatsimulator/data/openableLootDropMap.json\";\r\nimport achievementTierMap from \"./combatsimulator/data/achievementTierDetailMap.json\"\r\nimport achievementDetailMap from \"./combatsimulator/data/achievementDetailMap.json\"\r\n\r\nimport patchNote from \"../patchNote.json\";\r\n\r\nconst ONE_SECOND = 1e9;\r\nconst ONE_HOUR = 60 * 60 * ONE_SECOND;\r\n\r\nlet buttonStartSimulation = document.getElementById(\"buttonStartSimulation\");\r\nlet buttonStopSimulation = document.getElementById(\"buttonStopSimulation\");\r\nlet progressbar = document.getElementById(\"simulationProgressBar\");\r\nlet simStartTime = 0;\r\n\r\nlet worker = new Worker(new URL(\"worker.js\", import.meta.url));\r\nlet multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\nlet workerPool = [];\r\n\r\n\r\nlet player = new Player();\r\nlet selectedPlayers = [];\r\nlet food = [null, null, null];\r\nlet drinks = [null, null, null];\r\nlet abilities = [null, null, null, null];\r\nlet triggerMap = {};\r\nlet modalTriggers = [];\r\nlet currentSimResults = {};\r\n\r\nlet currentPlayerTabId = '1';\r\nlet playerDataMap = {\r\n \"1\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"2\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"3\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"4\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"5\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\"\r\n};\r\nwindow.revenue = 0;\r\nwindow.noRngRevenue = 0;\r\nwindow.expenses = 0;\r\nwindow.profit = 0;\r\nwindow.noRngProfit = 0;\r\n\r\n// #region Worker\r\n\r\nfunction onWorkerMessage(event) {\r\n switch (event.data.type) {\r\n case \"simulation_result\":\r\n progressbar.style.width = \"100%\";\r\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n //console.log(\"SIM RESULTS: \", event.data.simResult);\r\n showSimulationResult(event.data.simResult);\r\n updateContent();\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n document.getElementById('buttonShowAllSimData').style.display = 'none';\r\n break;\r\n case \"simulation_progress\":\r\n let progress = Math.floor(100 * event.data.progress);\r\n progressbar.style.width = progress + \"%\";\r\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n break;\r\n case \"simulation_error\":\r\n showErrorModal(event.data.error.toString());\r\n break;\r\n }\r\n};\r\n\r\nfunction onMultiWorkerMessage(event) {\r\n switch (event.data.type) {\r\n case \"simulation_result_allZones\":\r\n progressbar.style.width = \"100%\";\r\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n showAllSimulationResults(event.data.simResults);\r\n updateContent();\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n document.getElementById('buttonShowAllSimData').style.display = 'block';\r\n break;\r\n case \"simulation_progress\":\r\n let progress = Math.floor(100 * event.data.progress);\r\n progressbar.style.width = progress + \"%\";\r\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n break;\r\n case \"simulation_error\":\r\n showErrorModal(event.data.error.toString());\r\n break;\r\n }\r\n};\r\n\r\n// #endregion\r\n\r\n// #region Equipment\r\n\r\nfunction initEquipmentSection() {\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n initEquipmentSelect(type);\r\n initEnhancementLevelInput(type);\r\n });\r\n}\r\n\r\nfunction initEquipmentSelect(equipmentType) {\r\n let selectId = \"selectEquipment_\";\r\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\r\n selectId += \"weapon\";\r\n } else {\r\n selectId += equipmentType;\r\n }\r\n let selectElement = document.getElementById(selectId);\r\n\r\n let gameEquipment = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/equipment\")\r\n .filter((item) => item.equipmentDetail.type == \"/equipment_types/\" + equipmentType)\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const equipment of Object.values(gameEquipment)) {\r\n let opt = new Option(equipment.name, equipment.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + equipment.hrid);\r\n selectElement.add(opt);\r\n }\r\n\r\n selectElement.addEventListener(\"change\", (event) => {\r\n equipmentSelectHandler(event, equipmentType);\r\n });\r\n}\r\n\r\nfunction initHouseRoomsModal() {\r\n let houseRoomsList = document.getElementById(\"houseRoomsList\");\r\n let newChildren = [];\r\n let houseRooms = Object.values(houseRoomDetailMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n player.houseRooms = {};\r\n\r\n for (const room of Object.values(houseRooms)) {\r\n player.houseRooms[room.hrid] = 0;\r\n\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let nameCol = createElement(\"div\", \"col-md-4 offset-md-3 align-self-center\", room.name);\r\n nameCol.setAttribute(\"data-i18n\", \"houseRoomNames.\" + room.hrid);\r\n row.appendChild(nameCol);\r\n\r\n let levelCol = createElement(\"div\", \"col-md-2\");\r\n let levelInput = createHouseInput(room.hrid);\r\n\r\n levelInput.addEventListener(\"input\", function (e) {\r\n let inputValue = e.target.value;\r\n const hrid = e.target.dataset.houseHrid;\r\n player.houseRooms[hrid] = parseInt(inputValue);\r\n });\r\n\r\n levelCol.appendChild(levelInput);\r\n row.appendChild(levelCol);\r\n\r\n newChildren.push(row);\r\n }\r\n\r\n houseRoomsList.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction createHouseInput(hrid) {\r\n let levelInput = document.createElement(\"input\");\r\n levelInput.className = \"form-control\";\r\n levelInput.type = \"number\";\r\n levelInput.placeholder = 0;\r\n levelInput.min = 0;\r\n levelInput.max = 8;\r\n levelInput.step = 1;\r\n levelInput.dataset.houseHrid = hrid;\r\n\r\n return levelInput;\r\n}\r\n\r\nfunction refreshAchievementStatics() {\r\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n for(const tier of Object.values(tierMap)) {\r\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\r\n const done = Array.from(checks).filter(cb => cb.checked).length;\r\n const total = checks.length;\r\n\r\n const stat = document.getElementById(`AchTier${tier.sortIndex}Statics`);\r\n stat.innerText = `(${done}/${total})`;\r\n if (done == total) {\r\n // set to green\r\n stat.classList.remove(\"text-secondary\");\r\n stat.classList.add(\"text-success\");\r\n } else {\r\n // set to secondary\r\n stat.classList.remove(\"text-success\");\r\n stat.classList.add(\"text-secondary\");\r\n }\r\n }\r\n}\r\n\r\nfunction initAchievementsModal(){\r\n let achievementsList = document.getElementById(\"achievementsList\");\r\n let newChildren = [];\r\n player.achievements = {};\r\n\r\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n for(const tier of Object.values(tierMap)) {\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid).sort((a, b) => a.sortIndex - b.sortIndex);\r\n let detailMapCount = detailMap.length;\r\n if (detailMapCount <= 0) continue;\r\n\r\n let card = createElement(\"div\", \"card\");\r\n let cardHeader = createElement(\"div\", \"card-header d-flex align-items-center\");\r\n\r\n let cardTitle = createElement(\"a\", \"btn\", tier.name);\r\n cardTitle.setAttribute(\"data-bs-toggle\",\"collapse\");\r\n cardTitle.setAttribute(\"href\", `#AchTier${tier.sortIndex}`);\r\n cardTitle.setAttribute(\"data-i18n\", \"achievementTierNames.\"+tier.hrid);\r\n cardHeader.appendChild(cardTitle);\r\n\r\n let bufDesc = createElement(\"div\", \"small text-secondary\");\r\n let buffName = createElement(\"i\", \"\");\r\n buffName.setAttribute(\"data-i18n\", \"buffTypeNames.\"+tier[\"buff\"].typeHrid);\r\n bufDesc.appendChild(buffName);\r\n let buffValue = createElement(\"i\", \"\");\r\n buffValue.innerText = \":+\" + parseFloat(tier[\"buff\"].ratioBoost==0?tier[\"buff\"].flatBoost:tier[\"buff\"].ratioBoost)*100 + \"%\";\r\n bufDesc.appendChild(buffValue);\r\n cardHeader.appendChild(bufDesc);\r\n\r\n let cardStatics = createElement(\"div\", \"ms-auto btn\", `(0/${detailMapCount})`);\r\n cardStatics.id = `AchTier${tier.sortIndex}Statics`;\r\n cardStatics.dataset.checked = \"true\";\r\n cardStatics.addEventListener(\"click\", function (e) {\r\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\r\n for (const check of checks) {\r\n check.checked = cardStatics.dataset.checked == \"true\";\r\n const hrid = check.dataset.achievementHrid;\r\n player.achievements[hrid] = check.checked;\r\n }\r\n cardStatics.dataset.checked = cardStatics.dataset.checked == \"true\" ? \"false\" : \"true\";\r\n refreshAchievementStatics();\r\n });\r\n cardHeader.appendChild(cardStatics);\r\n\r\n card.appendChild(cardHeader);\r\n\r\n let cardMain = createElement(\"div\", \"collapse\");\r\n cardMain.id = `AchTier${tier.sortIndex}`;\r\n let cardBody = createElement(\"div\", \"card-body\");\r\n\r\n for (const detail of Object.values(detailMap)) {\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let formCheck = createElement(\"div\", \"form-check\");\r\n let input = createElement(\"input\", \"form-check-input\");\r\n input.setAttribute(\"type\", \"checkbox\");\r\n input.setAttribute(\"data-tier\", tier.sortIndex);\r\n input.id = `AchDetail${detail.sortIndex}`;\r\n input.dataset.achievementHrid = detail.hrid;\r\n input.addEventListener(\"change\", function (e) {\r\n const hrid = e.target.dataset.achievementHrid;\r\n player.achievements[hrid] = e.target.checked;\r\n\r\n refreshAchievementStatics();\r\n });\r\n formCheck.appendChild(input);\r\n\r\n let name = createElement(\"label\", \"form-check-label\", detail.name);\r\n name.setAttribute(\"data-i18n\", \"achievementNames.\" + detail.hrid);\r\n name.setAttribute(\"for\", `AchDetail${detail.sortIndex}`);\r\n formCheck.appendChild(name);\r\n row.appendChild(formCheck);\r\n cardBody.appendChild(row);\r\n }\r\n cardMain.appendChild(cardBody);\r\n card.appendChild(cardMain);\r\n\r\n newChildren.push(card);\r\n }\r\n\r\n achievementsList.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction initEnhancementLevelInput(equipmentType) {\r\n let inputId = \"inputEquipmentEnhancementLevel_\";\r\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\r\n inputId += \"weapon\";\r\n } else {\r\n inputId += equipmentType;\r\n }\r\n\r\n let inputElement = document.getElementById(inputId);\r\n inputElement.value = 0;\r\n inputElement.addEventListener(\"change\", enhancementLevelInputHandler);\r\n}\r\n\r\nfunction equipmentSelectHandler(event, type) {\r\n let equipmentType = \"/equipment_types/\" + type;\r\n\r\n if (!event.target.value) {\r\n updateEquipmentState();\r\n updateUI();\r\n return;\r\n }\r\n\r\n let gameItem = itemDetailMap[event.target.value];\r\n\r\n // Weapon select has two handlers because of mainhand and twohand weapons. Ignore the handler with the wrong type\r\n if (gameItem.equipmentDetail.type != equipmentType) {\r\n return;\r\n }\r\n\r\n if (type == \"two_hand\") {\r\n document.getElementById(\"selectEquipment_off_hand\").value = \"\";\r\n document.getElementById(\"inputEquipmentEnhancementLevel_off_hand\").value = 0;\r\n }\r\n if (type == \"off_hand\" && player.equipment[\"/equipment_types/two_hand\"]) {\r\n document.getElementById(\"selectEquipment_weapon\").value = \"\";\r\n document.getElementById(\"inputEquipmentEnhancementLevel_weapon\").value = 0;\r\n }\r\n\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\nfunction enhancementLevelInputHandler() {\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\nfunction updateEquipmentState() {\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentType = \"/equipment_types/\" + type;\r\n let selectType = type;\r\n if (type == \"main_hand\" || type == \"two_hand\") {\r\n selectType = \"weapon\";\r\n }\r\n\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + selectType);\r\n let equipmentHrid = equipmentSelect.value;\r\n\r\n if (!equipmentHrid) {\r\n player.equipment[equipmentType] = null;\r\n return;\r\n }\r\n\r\n let gameItem = itemDetailMap[equipmentHrid];\r\n\r\n // Clear old weapon if a weapon of a different type is equipped\r\n if (gameItem.equipmentDetail.type != equipmentType) {\r\n player.equipment[equipmentType] = null;\r\n return;\r\n }\r\n\r\n let enhancementLevel = Number(document.getElementById(\"inputEquipmentEnhancementLevel_\" + selectType).value);\r\n player.equipment[equipmentType] = new Equipment(gameItem.hrid, enhancementLevel);\r\n });\r\n}\r\n\r\ndocument.getElementById(\"selectEquipment_set\").onchange = changeEquipmentSetListener;\r\n\r\nfunction changeEquipmentSetListener() {\r\n let value = this.value\r\n let optgroupType = this.options[this.selectedIndex].parentNode.label;\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\"].forEach((type) => {\r\n let selectType = type;\r\n\r\n let currentEquipment = document.getElementById(\"selectEquipment_\" + selectType);\r\n if (type === \"feet\") {\r\n type = \"_boots\";\r\n }\r\n if (type === \"hands\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_bracers\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_gloves\";\r\n } else {\r\n type = \"_gauntlets\";\r\n }\r\n }\r\n if (type === \"head\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_hood\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_hat\";\r\n } else {\r\n type = \"_helmet\";\r\n }\r\n }\r\n if (type === \"legs\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_chaps\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_robe_bottoms\";\r\n } else {\r\n type = \"_plate_legs\";\r\n }\r\n }\r\n if (type === \"body\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_tunic\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_robe_top\";\r\n } else {\r\n type = \"_plate_body\";\r\n }\r\n }\r\n currentEquipment.value = \"/items/\" + value.toLowerCase() + type;\r\n });\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Combat Stats\r\n\r\nfunction updateCombatStatsUI() {\r\n player.updateCombatDetails();\r\n\r\n let combatStyleElement = document.getElementById(\"combatStat_combatStyleHrid\");\r\n let combatStyle = player.combatDetails.combatStats.combatStyleHrid;\r\n combatStyleElement.setAttribute(\"data-i18n\", \"combatStyleNames.\" + combatStyle);\r\n combatStyleElement.innerHTML = combatStyleDetailMap[combatStyle].name;\r\n\r\n let damageTypeElement = document.getElementById(\"combatStat_damageType\");\r\n let damageType = damageTypeDetailMap[player.combatDetails.combatStats.damageType];\r\n damageTypeElement.setAttribute(\"data-i18n\", \"damageTypeNames.\" + damageType.hrid);\r\n damageTypeElement.innerHTML = damageType.name;\r\n\r\n let attackIntervalElement = document.getElementById(\"combatStat_attackInterval\");\r\n attackIntervalElement.innerHTML = (player.combatDetails.combatStats.attackInterval / 1e9).toLocaleString() + \"s\";\r\n\r\n let primaryTrainingElement = document.getElementById(\"combatStat_primaryTraining\");\r\n let primaryTraining = player.combatDetails.combatStats.primaryTraining;\r\n primaryTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + primaryTraining);\r\n primaryTrainingElement.innerHTML = primaryTraining;\r\n\r\n let focusTrainingElement = document.getElementById(\"combatStat_focusTraining\");\r\n let focusTraining = player.combatDetails.combatStats.focusTraining;\r\n if (focusTraining) {\r\n focusTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + focusTraining);\r\n } else {\r\n focusTrainingElement.setAttribute(\"data-i18n\", \"characterSelectPage.slots.empty\");\r\n }\r\n focusTrainingElement.innerHTML = focusTraining;\r\n\r\n [\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"stabAccuracyRating\",\r\n \"stabMaxDamage\",\r\n \"slashAccuracyRating\",\r\n \"slashMaxDamage\",\r\n \"smashAccuracyRating\",\r\n \"smashMaxDamage\",\r\n \"rangedAccuracyRating\",\r\n \"rangedMaxDamage\",\r\n \"magicAccuracyRating\",\r\n \"magicMaxDamage\",\r\n \"defensiveMaxDamage\",\r\n \"stabEvasionRating\",\r\n \"slashEvasionRating\",\r\n \"smashEvasionRating\",\r\n \"rangedEvasionRating\",\r\n \"magicEvasionRating\",\r\n \"totalArmor\",\r\n \"totalWaterResistance\",\r\n \"totalNatureResistance\",\r\n \"totalFireResistance\",\r\n \"totalThreat\"\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n element.innerHTML = Math.floor(player.combatDetails[stat]);\r\n });\r\n\r\n [\r\n \"abilityHaste\",\r\n \"tenacity\"\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n element.innerHTML = Math.floor(player.combatDetails.combatStats[stat]);\r\n });\r\n\r\n [\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"combatExperience\",\r\n \"taskDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"drinkConcentration\",\r\n \"foodHaste\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\"\r\n\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n let value = (100 * player.combatDetails.combatStats[stat]).toLocaleString([], {\r\n minimumFractionDigits: 0,\r\n maximumFractionDigits: 4,\r\n });\r\n element.innerHTML = value + \"%\";\r\n });\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Level\r\n\r\nfunction initLevelSection() {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n levelInput.value = 1;\r\n levelInput.addEventListener(\"change\", levelInputHandler);\r\n });\r\n}\r\n\r\nfunction levelInputHandler() {\r\n updateLevels();\r\n updateUI();\r\n}\r\n\r\nfunction updateLevels() {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n player[skill + \"Level\"] = Number(levelInput.value);\r\n });\r\n updateCombatLevel();\r\n}\r\n\r\nfunction calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel) {\r\n return Math.floor(\r\n 0.1 * (staminaLevel + intelligenceLevel + attackLevel + defenseLevel + Math.max(meleeLevel, rangedLevel, magicLevel))\r\n + 0.5 * Math.max(attackLevel, defenseLevel, meleeLevel, rangedLevel, magicLevel)\r\n );\r\n}\r\n\r\n\r\nfunction updateCombatLevel() {\r\n let staminaLevel = player[\"staminaLevel\"];\r\n let intelligenceLevel = player[\"intelligenceLevel\"];\r\n let defenseLevel = player[\"defenseLevel\"];\r\n let attackLevel = player[\"attackLevel\"];\r\n let meleeLevel = player[\"meleeLevel\"];\r\n let rangedLevel = player[\"rangedLevel\"];\r\n let magicLevel = player[\"magicLevel\"];\r\n\r\n let levelInput = document.getElementById(\"inputLevel_combat\");\r\n levelInput.value = calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel);;\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Food\r\n\r\nfunction initFoodSection() {\r\n for (let i = 0; i < 3; i++) {\r\n let element = document.getElementById(\"selectFood_\" + i);\r\n\r\n let gameFoods = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/food\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const food of Object.values(gameFoods)) {\r\n let opt = new Option(food.name, food.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + food.hrid);\r\n element.add(opt);\r\n }\r\n\r\n element.addEventListener(\"change\", foodSelectHandler);\r\n }\r\n}\r\n\r\nfunction foodSelectHandler() {\r\n updateFoodState();\r\n updateUI();\r\n}\r\n\r\nfunction updateFoodState() {\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n food[i] = foodSelect.value;\r\n if (food[i] && !triggerMap[food[i]]) {\r\n let gameItem = itemDetailMap[food[i]];\r\n triggerMap[food[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateFoodUI() {\r\n for (let i = 0; i < 3; i++) {\r\n let selectElement = document.getElementById(\"selectFood_\" + i);\r\n let triggerButton = document.getElementById(\"buttonFoodTrigger_\" + i);\r\n\r\n selectElement.disabled = i >= player.combatDetails.combatStats.foodSlots;\r\n triggerButton.disabled = i >= player.combatDetails.combatStats.foodSlots || !food[i];\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Drinks\r\n\r\nfunction initDrinksSection() {\r\n for (let i = 0; i < 3; i++) {\r\n let element = document.getElementById(\"selectDrink_\" + i);\r\n\r\n let gameDrinks = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/drink\")\r\n .filter((item) => item.consumableDetail.usableInActionTypeMap[\"/action_types/combat\"])\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const drink of Object.values(gameDrinks)) {\r\n let opt = new Option(drink.name, drink.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + drink.hrid);\r\n element.add(opt);\r\n }\r\n\r\n element.addEventListener(\"change\", drinkSelectHandler);\r\n }\r\n}\r\n\r\nfunction drinkSelectHandler() {\r\n updateDrinksState();\r\n updateDrinksUI();\r\n}\r\n\r\nfunction updateDrinksState() {\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n drinks[i] = drinkSelect.value;\r\n if (drinks[i] && !triggerMap[drinks[i]]) {\r\n let gameItem = itemDetailMap[drinks[i]];\r\n triggerMap[drinks[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateDrinksUI() {\r\n for (let i = 0; i < 3; i++) {\r\n let selectElement = document.getElementById(\"selectDrink_\" + i);\r\n let triggerButton = document.getElementById(\"buttonDrinkTrigger_\" + i);\r\n\r\n selectElement.disabled = i >= player.combatDetails.combatStats.drinkSlots;\r\n triggerButton.disabled = i >= player.combatDetails.combatStats.drinkSlots || !drinks[i];\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Abilities\r\n\r\nfunction initAbilitiesSection() {\r\n for (let i = 0; i < 5; i++) {\r\n let selectElement = document.getElementById(\"selectAbility_\" + i);\r\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\r\n\r\n inputElement.value = 1;\r\n\r\n let gameAbilities;\r\n if (i == 0) {\r\n gameAbilities = Object.values(abilityDetailMap).filter(x => x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\r\n } else {\r\n gameAbilities = Object.values(abilityDetailMap).filter(x => !x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\r\n }\r\n\r\n\r\n for (const ability of Object.values(gameAbilities)) {\r\n let opt = new Option(ability.name, ability.hrid);\r\n opt.setAttribute(\"data-i18n\", \"abilityNames.\" + ability.hrid);\r\n selectElement.add(opt);\r\n }\r\n\r\n selectElement.addEventListener(\"change\", abilitySelectHandler);\r\n }\r\n}\r\n\r\nfunction abilitySelectHandler() {\r\n updateAbilityState();\r\n updateAbilityUI();\r\n}\r\n\r\nfunction updateAbilityState() {\r\n for (let i = 0; i < 5; i++) {\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\r\n abilities[i] = abilitySelect.value;\r\n if (abilities[i] && !triggerMap[abilities[i]]) {\r\n let gameAbility = abilityDetailMap[abilities[i]];\r\n triggerMap[abilities[i]] = structuredClone(gameAbility.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateAbilityUI() {\r\n for (let i = 0; i < 5; i++) {\r\n let selectElement = document.getElementById(\"selectAbility_\" + i);\r\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let triggerButton = document.getElementById(\"buttonAbilityTrigger_\" + i);\r\n\r\n selectElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\r\n inputElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\r\n triggerButton.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1] || !abilities[i];\r\n let moveUpButton = document.getElementById(\"selectAbilityMoveUp_\" + i);\r\n moveUpButton.onclick = () => swapAbilityOrder(i, -1);\r\n }\r\n}\r\n\r\nfunction swapAbilityOrder(abilityIndex, step) {\r\n const swapIndex = abilityIndex + step;\r\n if (swapIndex < 0 || swapIndex > 4) {\r\n return;\r\n }\r\n\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilityIndex);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilityIndex);\r\n\r\n const tempAbility = abilities[abilityIndex];\r\n abilities[abilityIndex] = abilities[swapIndex];\r\n abilities[swapIndex] = tempAbility;\r\n\r\n const tempLevel = abilityLevelInput.value;\r\n abilityLevelInput.value = document.getElementById(\"inputAbilityLevel_\" + swapIndex).value;\r\n document.getElementById(\"inputAbilityLevel_\" + swapIndex).value = tempLevel;\r\n\r\n abilitySelect.value = document.getElementById(\"selectAbility_\" + (swapIndex)).value;\r\n document.getElementById(\"selectAbility_\" + swapIndex).value = abilities[swapIndex];\r\n\r\n updateAbilityState();\r\n updateAbilityUI();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Trigger\r\n\r\nfunction initTriggerModal() {\r\n let modal = document.getElementById(\"triggerModal\");\r\n modal.addEventListener(\"show.bs.modal\", (event) => triggerModalShownHandler(event));\r\n\r\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\r\n triggerSaveButton.addEventListener(\"click\", (event) => triggerModalSaveHandler(event));\r\n\r\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\r\n triggerAddButton.addEventListener(\"click\", (event) => triggerAddButtonHandler(event));\r\n\r\n let triggerDefaultButton = document.getElementById(\"buttonDefaultTrigger\");\r\n triggerDefaultButton.addEventListener(\"click\", (event) => triggerDefaultButtonHandler(event));\r\n\r\n for (let i = 0; i < 4; i++) {\r\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\r\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\r\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\r\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\r\n let triggerRemoveButton = document.getElementById(\"buttonRemoveTrigger_\" + i);\r\n\r\n triggerDependencySelect.addEventListener(\"change\", (event) => triggerDependencySelectHandler(event, i));\r\n triggerConditionSelect.addEventListener(\"change\", (event) => triggerConditionSelectHandler(event, i));\r\n triggerComparatorSelect.addEventListener(\"change\", (event) => triggerComparatorSelectHander(event, i));\r\n triggerValueInput.addEventListener(\"change\", (event) => triggerValueInputHandler(event, i));\r\n triggerRemoveButton.addEventListener(\"click\", (event) => triggerRemoveButtonHandler(event, i));\r\n }\r\n}\r\n\r\nfunction triggerModalShownHandler(event) {\r\n let triggerButton = event.relatedTarget;\r\n\r\n let triggerType = triggerButton.getAttribute(\"data-bs-triggertype\");\r\n let triggerIndex = Number(triggerButton.getAttribute(\"data-bs-triggerindex\"));\r\n\r\n let triggerTarget;\r\n switch (triggerType) {\r\n case \"food\":\r\n triggerTarget = food[triggerIndex];\r\n break;\r\n case \"drink\":\r\n triggerTarget = drinks[triggerIndex];\r\n break;\r\n case \"ability\":\r\n triggerTarget = abilities[triggerIndex];\r\n break;\r\n }\r\n\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n triggerTargetnput.value = triggerTarget;\r\n modalTriggers = triggerMap[triggerTarget];\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerModalSaveHandler(event) {\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n let triggerTarget = triggerTargetnput.value;\r\n\r\n triggerMap[triggerTarget] = modalTriggers;\r\n}\r\n\r\nfunction triggerDependencySelectHandler(event, index) {\r\n modalTriggers[index].dependencyHrid = event.target.value;\r\n modalTriggers[index].conditionHrid = \"\";\r\n modalTriggers[index].comparatorHrid = \"\";\r\n modalTriggers[index].value = 0;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerConditionSelectHandler(event, index) {\r\n modalTriggers[index].conditionHrid = event.target.value;\r\n modalTriggers[index].comparatorHrid = \"\";\r\n modalTriggers[index].value = 0;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerComparatorSelectHander(event, index) {\r\n modalTriggers[index].comparatorHrid = event.target.value;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerValueInputHandler(event, index) {\r\n modalTriggers[index].value = Number(event.target.value);\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerRemoveButtonHandler(event, index) {\r\n modalTriggers.splice(index, 1);\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerAddButtonHandler(event) {\r\n if (modalTriggers.length == 4) {\r\n return;\r\n }\r\n\r\n modalTriggers.push({\r\n dependencyHrid: \"\",\r\n conditionHrid: \"\",\r\n comparatorHrid: \"\",\r\n value: 0,\r\n });\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerDefaultButtonHandler(event) {\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n let triggerTarget = triggerTargetnput.value;\r\n\r\n if (triggerTarget.startsWith(\"/items/\")) {\r\n modalTriggers = structuredClone(itemDetailMap[triggerTarget].consumableDetail.defaultCombatTriggers);\r\n } else {\r\n modalTriggers = structuredClone(abilityDetailMap[triggerTarget].defaultCombatTriggers);\r\n }\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction updateTriggerModal() {\r\n let triggerStartTextElement = document.getElementById(\"triggerStartText\");\r\n if (modalTriggers.length == 0) {\r\n triggerStartTextElement.innerHTML = \"Activate as soon as it's off cooldown\";\r\n } else {\r\n triggerStartTextElement.innerHTML = \"Activate when:\";\r\n }\r\n\r\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\r\n triggerAddButton.disabled = modalTriggers.length == 4;\r\n\r\n let triggersValid = true;\r\n\r\n for (let i = 0; i < 4; i++) {\r\n let triggerElement = document.getElementById(\"modalTrigger_\" + i);\r\n\r\n if (!modalTriggers[i]) {\r\n hideElement(triggerElement);\r\n continue;\r\n }\r\n\r\n showElement(triggerElement);\r\n\r\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\r\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\r\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\r\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\r\n\r\n showElement(triggerDependencySelect);\r\n fillTriggerDependencySelect(triggerDependencySelect);\r\n\r\n if (modalTriggers[i].dependencyHrid == \"\") {\r\n hideElement(triggerConditionSelect);\r\n hideElement(triggerComparatorSelect);\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerDependencySelect.value = modalTriggers[i].dependencyHrid;\r\n showElement(triggerConditionSelect);\r\n fillTriggerConditionSelect(triggerConditionSelect, modalTriggers[i].dependencyHrid);\r\n\r\n if (modalTriggers[i].conditionHrid == \"\") {\r\n hideElement(triggerComparatorSelect);\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerConditionSelect.value = modalTriggers[i].conditionHrid;\r\n showElement(triggerComparatorSelect);\r\n fillTriggerComparatorSelect(triggerComparatorSelect, modalTriggers[i].conditionHrid);\r\n\r\n if (modalTriggers[i].comparatorHrid == \"\") {\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerComparatorSelect.value = modalTriggers[i].comparatorHrid;\r\n\r\n if (combatTriggerComparatorDetailMap[modalTriggers[i].comparatorHrid].allowValue) {\r\n showElement(triggerValueInput);\r\n triggerValueInput.value = modalTriggers[i].value;\r\n } else {\r\n hideElement(triggerValueInput);\r\n }\r\n }\r\n\r\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\r\n triggerSaveButton.disabled = !triggersValid;\r\n\r\n updateContent();\r\n}\r\n\r\nfunction fillTriggerDependencySelect(element) {\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const dependency of Object.values(combatTriggerDependencyDetailMap).sort(\r\n (a, b) => a.sortIndex - b.sortIndex\r\n )) {\r\n let opt = new Option(dependency.name, dependency.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerDependencyNames.\" + dependency.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction fillTriggerConditionSelect(element, dependencyHrid) {\r\n let dependency = combatTriggerDependencyDetailMap[dependencyHrid];\r\n\r\n let conditions;\r\n if (dependency.isSingleTarget) {\r\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isSingleTarget);\r\n } else {\r\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isMultiTarget);\r\n }\r\n\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const condition of Object.values(conditions).sort((a, b) => a.sortIndex - b.sortIndex)) {\r\n let opt = new Option(condition.name, condition.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerConditionNames.\" + condition.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction fillTriggerComparatorSelect(element, conditionHrid) {\r\n let condition = combatTriggerConditionDetailMap[conditionHrid];\r\n\r\n let comparators = condition.allowedComparatorHrids.map((hrid) => combatTriggerComparatorDetailMap[hrid]);\r\n\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const comparator of Object.values(comparators).sort((a, b) => a.sortIndex - b.sortIndex)) {\r\n let opt = new Option(comparator.name, comparator.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerComparatorNames.\" + comparator.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction hideElement(element) {\r\n element.classList.remove(\"d-flex\");\r\n element.classList.add(\"d-none\");\r\n}\r\n\r\nfunction showElement(element) {\r\n element.classList.remove(\"d-none\");\r\n element.classList.add(\"d-flex\");\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Zones\r\n\r\nfunction initZones() {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n\r\n // TOOD dungeon wave spawns\r\n let gameZones = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const zone of Object.values(gameZones)) {\r\n let opt = new Option(zone.name, zone.hrid);\r\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + zone.hrid);\r\n zoneSelect.add(opt);\r\n }\r\n\r\n\r\n let zoneCheckBox = document.getElementById(\"zoneCheckBox\");\r\n let checkAllZonesToggle = document.getElementById('checkAllZones');\r\n\r\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\r\n simAllZonesToggle.addEventListener(\"change\", (event) => {\r\n if (simAllZonesToggle.checked) {\r\n zoneCheckBox.classList.remove(\"d-none\");\r\n zoneCheckBox.querySelectorAll(\".zone-checkbox\").forEach(checkbox => checkbox.checked = true);\r\n checkAllZonesToggle.checked = true;\r\n } else {\r\n zoneCheckBox.classList.add(\"d-none\");\r\n }\r\n });\r\n\r\n let zoneHrids = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .flat();\r\n\r\n for (const zoneHrid of zoneHrids) {\r\n const newZone = document.createElement('div');\r\n newZone.classList.add('form-check');\r\n newZone.innerHTML = `\r\n \r\n \r\n `;\r\n zoneCheckBox.append(newZone);\r\n }\r\n\r\n let checkZoneToggles = document.querySelectorAll('.zone-checkbox');\r\n checkAllZonesToggle.addEventListener('change', () => {\r\n checkZoneToggles.forEach(cb => cb.checked = checkAllZonesToggle.checked);\r\n });\r\n\r\n checkZoneToggles.forEach(cb =>\r\n cb.addEventListener('change', () => {\r\n checkAllZonesToggle.checked = [...checkZoneToggles].every(x => x.checked);\r\n })\r\n );\r\n\r\n\r\n let soloCheckBox = document.getElementById(\"soloCheckBox\");\r\n let checkAllSolosToggle = document.getElementById('checkAllSolos');\r\n\r\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\r\n simAllSoloToggle.addEventListener(\"change\", (event) => {\r\n if (simAllSoloToggle.checked) {\r\n soloCheckBox.classList.remove(\"d-none\");\r\n soloCheckBox.querySelectorAll(\".solo-checkbox\").forEach(checkbox => checkbox.checked = true);\r\n checkAllSolosToggle.checked = true;\r\n } else {\r\n soloCheckBox.classList.add(\"d-none\");\r\n }\r\n });\r\n\r\n let soloHrids = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount == 1)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .flat();\r\n\r\n for (const zoneHrid of soloHrids) {\r\n const newZone = document.createElement('div');\r\n newZone.classList.add('form-check');\r\n newZone.innerHTML = `\r\n \r\n \r\n `;\r\n soloCheckBox.append(newZone);\r\n }\r\n\r\n let checkSoloToggles = document.querySelectorAll('.solo-checkbox');\r\n checkAllSolosToggle.addEventListener('change', () => {\r\n checkSoloToggles.forEach(cb => cb.checked = checkAllSolosToggle.checked);\r\n });\r\n\r\n checkSoloToggles.forEach(cb =>\r\n cb.addEventListener('change', () => {\r\n checkAllSolosToggle.checked = [...checkSoloToggles].every(x => x.checked);\r\n })\r\n );\r\n}\r\n\r\nfunction initDungeons() {\r\n let dungeonSelect = document.getElementById(\"selectDungeon\");\r\n\r\n let gameDungeons = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category == \"/action_categories/combat/dungeons\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const dungeon of Object.values(gameDungeons)) {\r\n let opt = new Option(dungeon.name, dungeon.hrid);\r\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + dungeon.hrid);\r\n dungeonSelect.add(opt);\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Simulation Result\r\n\r\nfunction createDamageDoneAccordion(enemyIndex) {\r\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageDoneAccordionEnemy${enemyIndex}`);\r\n\r\n const colDiv = createElement('div', 'col');\r\n const accordionMainDiv = createElement('div', 'accordion');\r\n const accordionItemDiv = createElement('div', 'accordion-item');\r\n\r\n const headerH2 = createElement('h2', 'accordion-header');\r\n const button = createElement('button', 'accordion-button collapsed',\r\n `Damage Done (Enemy ${enemyIndex})`,\r\n `buttonSimulationResultDamageDoneAccordionEnemy${enemyIndex}`\r\n );\r\n button.setAttribute('type', 'button');\r\n button.setAttribute('data-bs-toggle', 'collapse');\r\n button.setAttribute('data-bs-target', `#collapseDamageDone${enemyIndex}`);\r\n button.style.padding = '0.5em';\r\n\r\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageDone${enemyIndex}`);\r\n const accordionBodyDiv = createElement('div', 'accordion-body');\r\n\r\n const headerRow = createElement('div', 'row');\r\n headerRow.innerHTML = `\r\n
Source
\r\n
Hitchance
\r\n
DPS
\r\n
%
\r\n `;\r\n\r\n const resultDiv = createElement('div', '', '', `simulationResultDamageDoneEnemy${enemyIndex}`);\r\n\r\n accordionBodyDiv.appendChild(headerRow);\r\n accordionBodyDiv.appendChild(resultDiv);\r\n collapseDiv.appendChild(accordionBodyDiv);\r\n headerH2.appendChild(button);\r\n accordionItemDiv.appendChild(headerH2);\r\n accordionItemDiv.appendChild(collapseDiv);\r\n accordionMainDiv.appendChild(accordionItemDiv);\r\n colDiv.appendChild(accordionMainDiv);\r\n accordionDiv.appendChild(colDiv);\r\n\r\n return accordionDiv;\r\n}\r\nfunction createDamageTakenAccordion(enemyIndex) {\r\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageTakenAccordionEnemy${enemyIndex}`);\r\n\r\n const colDiv = createElement('div', 'col');\r\n const accordionMainDiv = createElement('div', 'accordion');\r\n const accordionItemDiv = createElement('div', 'accordion-item');\r\n\r\n const headerH2 = createElement('h2', 'accordion-header');\r\n const button = createElement('button', 'accordion-button collapsed',\r\n `Damage Taken (Enemy ${enemyIndex})`,\r\n `buttonSimulationResultDamageTakenAccordionEnemy${enemyIndex}`\r\n );\r\n button.setAttribute('type', 'button');\r\n button.setAttribute('data-bs-toggle', 'collapse');\r\n button.setAttribute('data-bs-target', `#collapseDamageTaken${enemyIndex}`);\r\n button.style.padding = '0.5em';\r\n\r\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageTaken${enemyIndex}`);\r\n const accordionBodyDiv = createElement('div', 'accordion-body');\r\n\r\n const headerRow = createElement('div', 'row');\r\n headerRow.innerHTML = `\r\n
Source
\r\n
Hitchance
\r\n
DPS
\r\n
%
\r\n `;\r\n\r\n const resultDiv = createElement('div', '', '', `simulationResultDamageTakenEnemy${enemyIndex}`);\r\n\r\n accordionBodyDiv.appendChild(headerRow);\r\n accordionBodyDiv.appendChild(resultDiv);\r\n collapseDiv.appendChild(accordionBodyDiv);\r\n headerH2.appendChild(button);\r\n accordionItemDiv.appendChild(headerH2);\r\n accordionItemDiv.appendChild(collapseDiv);\r\n accordionMainDiv.appendChild(accordionItemDiv);\r\n colDiv.appendChild(accordionMainDiv);\r\n accordionDiv.appendChild(colDiv);\r\n\r\n return accordionDiv;\r\n}\r\n\r\n\r\nfunction initDamageDoneTaken() {\r\n for (let i = 64; i > 0; i--) {\r\n document.getElementById(\"simulationResultTotalDamageDone\").insertAdjacentElement('afterend', createDamageDoneAccordion(i));\r\n document.getElementById(\"simulationResultTotalDamageTaken\").insertAdjacentElement('afterend', createDamageTakenAccordion(i));\r\n }\r\n}\r\n\r\nfunction showSimulationResult(simResult) {\r\n currentSimResults = simResult;\r\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\r\n expensesModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\r\n revenueModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let noRngRevenueModalTable = document.querySelector(\"#noRngRevenueTable > tbody\");\r\n noRngRevenueModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let playerToDisplay = \"player1\";\r\n if (selectedPlayers.includes(parseInt(currentPlayerTabId))) {\r\n playerToDisplay = \"player\" + currentPlayerTabId;\r\n }\r\n if (!simResult.dropRateMultiplier[playerToDisplay]) {\r\n return;\r\n }\r\n\r\n showKills(simResult, playerToDisplay);\r\n showDeaths(simResult, playerToDisplay);\r\n showExperienceGained(simResult, playerToDisplay);\r\n showConsumablesUsed(simResult, playerToDisplay);\r\n showHpSpent(simResult, playerToDisplay);\r\n showManaUsed(simResult, playerToDisplay);\r\n showHitpointsGained(simResult, playerToDisplay);\r\n showManapointsGained(simResult, playerToDisplay);\r\n showDamageDone(simResult, playerToDisplay);\r\n showDamageTaken(simResult, playerToDisplay);\r\n renderWipeEvents(simResult);\r\n window.profit = window.revenue - window.expenses;\r\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\r\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\r\n window.noRngProfit = window.noRngRevenue - window.expenses;\r\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\r\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\r\n}\r\n\r\nfunction showAllSimulationResults(simResults) {\r\n let displaySimResults = manipulateSimResultsDataForDisplay(simResults);\r\n updateAllSimsModal(displaySimResults);\r\n}\r\n\r\nfunction manipulateSimResultsDataForDisplay(simResults) {\r\n let displaySimResults = [];\r\n for (let i = 0; i < simResults.length; i++) {\r\n for (let j = 0; j < selectedPlayers.length; j++) {\r\n let playerToDisplay = \"player\" + selectedPlayers[j].toString();\r\n let simResult = simResults[i];\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let zoneName = simResult.zoneName;\r\n let difficultyTier = simResult.difficultyTier;\r\n let encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\r\n let playerDeaths = simResult.deaths[playerToDisplay] ?? 0;\r\n let deathsPerHour = (playerDeaths / hoursSimulated).toFixed(2);\r\n\r\n let totalExperience = 0;\r\n if (simResult.experienceGained[playerToDisplay]) {\r\n totalExperience = Object.values(simResult.experienceGained[playerToDisplay]).reduce((prev, cur) => prev + cur, 0);\r\n }\r\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\r\n\r\n let experiencePerHour = {};\r\n const skills = [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"];\r\n skills.forEach((skill) => {\r\n const skillLower = skill.toLowerCase();\r\n let experience = simResult.experienceGained[playerToDisplay]?.[skillLower] ?? 0;\r\n let experiencePerHourValue = 0;\r\n if (experience != 0) {\r\n experiencePerHourValue = (experience / hoursSimulated).toFixed(0);\r\n }\r\n experiencePerHour[skill] = experiencePerHourValue;\r\n });\r\n getDropProfit(simResult, playerToDisplay);\r\n let noRngRevenue = simResult[\"noRngRevenue\"];\r\n let noRngProfit = simResult[\"noRngProfit\"];\r\n let expenses = simResult[\"expenses\"];\r\n\r\n let displaySimRow = {\r\n \"ZoneName\": zoneName, \"DifficultyTier\": difficultyTier, \"Player\": playerToDisplay, \"Encounters\": encountersPerHour, \"Deaths\": deathsPerHour,\r\n \"TotalExperience\": totalExperiencePerHour, \"Stamina\": experiencePerHour[\"Stamina\"],\r\n \"Intelligence\": experiencePerHour[\"Intelligence\"], \"Attack\": experiencePerHour[\"Attack\"],\r\n \"Magic\": experiencePerHour[\"Magic\"], \"Ranged\": experiencePerHour[\"Ranged\"],\r\n \"Melee\": experiencePerHour[\"Melee\"], \"Defense\": experiencePerHour[\"Defense\"],\r\n \"noRngRevenue\": noRngRevenue,\r\n \"expenses\": expenses,\r\n \"noRngProfit\": noRngProfit\r\n };\r\n displaySimResults.push(displaySimRow);\r\n }\r\n }\r\n return displaySimResults;\r\n}\r\n\r\nfunction fidDropAmount(dropAmount) {\r\n if (Number.isInteger(dropAmount)) return dropAmount;\r\n\r\n const intPart = Math.floor(dropAmount);\r\n const fracPart = dropAmount - intPart;\r\n return Math.random() < fracPart ? intPart + 1 : intPart;\r\n}\r\n\r\nfunction calcDropMaps(simResult, playerToDisplay) {\r\n let dropRateMultiplier = simResult.dropRateMultiplier[playerToDisplay];\r\n let rareFindMultiplier = simResult.rareFindMultiplier[playerToDisplay];\r\n let combatDropQuantity = simResult.combatDropQuantity[playerToDisplay];\r\n let debuffOnLevelGap = simResult.debuffOnLevelGap[playerToDisplay];\r\n\r\n let numberOfPlayers = simResult.numberOfPlayers;\r\n let monsters = Object.keys(simResult.deaths)\r\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\r\n .sort();\r\n\r\n const totalDropMap = new Map();\r\n const noRngTotalDropMap = new Map();\r\n for (const monster of monsters) {\r\n const dropMap = new Map();\r\n const rareDropMap = new Map();\r\n if (combatMonsterDetailMap[monster].dropTable) {\r\n for (const drop of combatMonsterDetailMap[monster].dropTable) {\r\n if (drop.minDifficultyTier > simResult.difficultyTier) {\r\n continue;\r\n }\r\n\r\n let multiplier = 1.0 + 0.1 * simResult.difficultyTier;\r\n let dropRate = Math.min(1.0, multiplier * (drop.dropRate + (drop.dropRatePerDifficultyTier ?? 0) * simResult.difficultyTier));\r\n if (dropRate <= 0) continue;\r\n\r\n dropMap.set(drop.itemHrid, { \"dropRate\": Math.min(1.0, dropRate * dropRateMultiplier), \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\r\n }\r\n if (combatMonsterDetailMap[monster].rareDropTable)\r\n for (const drop of combatMonsterDetailMap[monster].rareDropTable) {\r\n if (drop.minDifficultyTier > simResult.difficultyTier) {\r\n continue;\r\n }\r\n rareDropMap.set(drop.itemHrid, { \"dropRate\": drop.dropRate * rareFindMultiplier, \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\r\n }\r\n\r\n for (let dropObject of dropMap.values()) {\r\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\r\n\r\n }\r\n for (let dropObject of rareDropMap.values()) {\r\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\r\n }\r\n\r\n for (let i = 0; i < simResult.deaths[monster]; i++) {\r\n for (let dropObject of dropMap.values()) {\r\n let chance = Math.random();\r\n if (chance <= dropObject.dropRate / numberOfPlayers) {\r\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\r\n dropObject.number = dropObject.number + fidDropAmount(amount);\r\n }\r\n }\r\n for (let dropObject of rareDropMap.values()) {\r\n let chance = Math.random();\r\n if (chance <= dropObject.dropRate / numberOfPlayers) {\r\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\r\n dropObject.number = dropObject.number + fidDropAmount(amount);\r\n }\r\n }\r\n }\r\n for (let [name, dropObject] of dropMap.entries()) {\r\n if (totalDropMap.has(name)) {\r\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\r\n } else {\r\n totalDropMap.set(name, dropObject.number);\r\n }\r\n if (noRngTotalDropMap.has(name)) {\r\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\r\n } else {\r\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\r\n }\r\n }\r\n for (let [name, dropObject] of rareDropMap.entries()) {\r\n if (totalDropMap.has(name)) {\r\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\r\n } else {\r\n totalDropMap.set(name, dropObject.number);\r\n }\r\n if (noRngTotalDropMap.has(name)) {\r\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\r\n } else {\r\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return { totalDropMap, noRngTotalDropMap };\r\n}\r\n\r\nfunction getDropProfit(simResult, playerToDisplay) {\r\n let { totalDropMap, noRngTotalDropMap } = calcDropMaps(simResult, playerToDisplay);\r\n\r\n let noRngTotal = 0;\r\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\r\n let price = -1;\r\n let revenueSetting = document.getElementById('selectPrices_drops').value;\r\n if (window.prices) {\r\n let item = window.prices[name];\r\n if (item) {\r\n if (revenueSetting == 'bid') {\r\n if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n } else if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n }\r\n } else if (revenueSetting == 'ask') {\r\n if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n } else if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n }\r\n }\r\n if (price == -1) {\r\n price = item['vendor'];\r\n }\r\n }\r\n }\r\n noRngTotal += price * dropAmount;\r\n }\r\n\r\n let consumablesUsed = simResult.consumablesUsed?.[playerToDisplay];\r\n\r\n if (consumablesUsed) {\r\n consumablesUsed = Object.entries(consumablesUsed).sort((a, b) => b[1] - a[1]);\r\n } else {\r\n consumablesUsed = [];\r\n }\r\n\r\n let expenses = 0;\r\n for (const [consumable, amount] of consumablesUsed) {\r\n let price = -1;\r\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\r\n if (window.prices) {\r\n let item = window.prices[consumable];\r\n if (item) {\r\n if (expensesSetting == 'bid') {\r\n if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n } else if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n }\r\n } else if (expensesSetting == 'ask') {\r\n if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n } else if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n }\r\n }\r\n if (price == -1) {\r\n price = item['vendor'];\r\n }\r\n }\r\n }\r\n expenses += price * amount;\r\n }\r\n\r\n simResult[\"noRngRevenue\"] = (noRngTotal).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n simResult[\"expenses\"] = (expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n simResult[\"noRngProfit\"] = (noRngTotal - expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n}\r\n\r\nfunction updateAllSimsModal(data) {\r\n const tableBody = document.getElementById('allZonesData').getElementsByTagName('tbody')[0];\r\n tableBody.innerHTML = '';\r\n data.forEach(item => {\r\n const row = document.createElement('tr');\r\n\r\n Object.keys(item).forEach(key => {\r\n const cell = document.createElement('td');\r\n cell.textContent = item[key];\r\n if (key === 'ZoneName') {\r\n cell.setAttribute(\"data-i18n\", \"actionNames.\" + item[key]);\r\n }\r\n row.appendChild(cell);\r\n });\r\n\r\n tableBody.appendChild(row);\r\n });\r\n\r\n const table = document.getElementById('allZonesData');\r\n const rows = table.getElementsByTagName('tr');\r\n const numCols = rows[0].cells.length;\r\n\r\n // 遍历每一列\r\n for (let col = 5; col < numCols; col++) {\r\n let max = -Infinity;\r\n let maxCell = null;\r\n\r\n // 找到最大值及其单元格\r\n for (let row = 1; row < rows.length; row++) {\r\n const cell = rows[row].cells[col];\r\n const value = parseFloat(cell.textContent.replace(/,/g, ''));\r\n if (value > max) {\r\n max = value;\r\n maxCell = cell;\r\n }\r\n }\r\n\r\n // 将最大值单元格的背景色设置为绿色\r\n if (maxCell && max != 0) {\r\n maxCell.style.backgroundColor = 'green';\r\n maxCell.style.color = 'white'; // 设置文字颜色为白色以提高可读性\r\n }\r\n }\r\n}\r\n\r\nlet currentSortColumn = null;\r\nlet currentSortDirection = 'desc';\r\n\r\nfunction sortTable(tableId, columnIndex, direction) {\r\n const table = document.getElementById(tableId);\r\n const tbody = table.querySelector('tbody');\r\n const rows = Array.from(tbody.querySelectorAll('tr'));\r\n\r\n const sortedRows = rows.sort((rowA, rowB) => {\r\n const cellA = rowA.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\r\n const cellB = rowB.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\r\n\r\n const valueA = parseFloat(cellA.replace(/,/g, ''));\r\n const valueB = parseFloat(cellB.replace(/,/g, ''));\r\n\r\n return direction === 'asc' ? valueA - valueB : valueB - valueA;\r\n });\r\n\r\n sortedRows.forEach(row => tbody.appendChild(row));\r\n updateSortIndicators(tableId, columnIndex, direction);\r\n}\r\n\r\nfunction updateSortIndicators(tableId, columnIndex, direction) {\r\n const headers = document.querySelectorAll(`#${tableId} th`);\r\n headers.forEach((header, index) => {\r\n header.classList.remove('sort-asc', 'sort-desc');\r\n if (index === columnIndex) {\r\n header.classList.add(direction === 'asc' ? 'sort-asc' : 'sort-desc');\r\n }\r\n });\r\n}\r\n\r\ndocument.querySelectorAll('#allZonesData th').forEach((header, index) => {\r\n if (index === 0) return;\r\n if (index === 1) return;\r\n if (index === 2) return;\r\n\r\n header.addEventListener('click', () => {\r\n if (currentSortColumn === index) {\r\n currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';\r\n } else {\r\n currentSortColumn = index;\r\n currentSortDirection = 'desc';\r\n }\r\n sortTable('allZonesData', currentSortColumn, currentSortDirection);\r\n });\r\n});\r\n\r\ndocument.getElementById('buttonExportResults').addEventListener('click', function () {\r\n var table = document.getElementById('allZonesData');\r\n var csv = [];\r\n var rows = table.querySelectorAll('tr');\r\n\r\n for (var i = 0; i < rows.length; i++) {\r\n var row = rows[i];\r\n var cols = row.querySelectorAll('th, td');\r\n var csvRow = [];\r\n\r\n cols.forEach(function (col) {\r\n csvRow.push('\"' + col.innerText.replace(/\"/g, '\"\"') + '\"');\r\n });\r\n\r\n csv.push(csvRow.join(','));\r\n }\r\n\r\n var csvFile = new Blob([csv.join('\\n')], { type: 'text/csv' });\r\n var downloadLink = document.createElement('a');\r\n downloadLink.download = 'simData.csv';\r\n downloadLink.href = URL.createObjectURL(csvFile);\r\n downloadLink.style.display = 'none';\r\n document.body.appendChild(downloadLink);\r\n downloadLink.click();\r\n document.body.removeChild(downloadLink);\r\n});\r\n\r\nfunction showKills(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultKills\");\r\n let dropsResultDiv = document.getElementById(\"simulationResultDrops\");\r\n let noRngDropsResultDiv = document.getElementById(\"noRngDrops\");\r\n let newChildren = [];\r\n let newDropChildren = [];\r\n let newNoRngDropChildren = [];\r\n\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let encountersPerHour = 0;\r\n let encountersRow = null;\r\n if (simResult.isDungeon) {\r\n let wavesCompletedRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Wave Reached\", simResult.maxWaveReached]);\r\n wavesCompletedRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxWaveReached\");\r\n newChildren.push(wavesCompletedRow);\r\n let completedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Completed Dungeons\", simResult.dungeonsCompleted]);\r\n completedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsCompleted\");\r\n newChildren.push(completedDungeonsRow);\r\n if (simResult.dungeonsFailed > 0) {\r\n let failedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Failed Dungeons\", simResult.dungeonsFailed]);\r\n failedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsFailed\");\r\n newChildren.push(failedDungeonsRow);\r\n }\r\n encountersPerHour = (simResult.dungeonsCompleted / hoursSimulated).toFixed(1);\r\n let averageTime = (hoursSimulated * 60 / simResult.dungeonsCompleted).toFixed(1);\r\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Average Time\", averageTime]);\r\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.averageTime\");\r\n } else {\r\n encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\r\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Encounters\", encountersPerHour]);\r\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.encounters\");\r\n }\r\n\r\n if (simResult.maxEnrageStack > 0) {\r\n let enrageRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Enrage Stack\", simResult.maxEnrageStack]);\r\n enrageRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxEnrageStack\");\r\n newChildren.push(enrageRow);\r\n }\r\n\r\n if (simResult.debuffOnLevelGap[playerToDisplay] != 0) {\r\n let debuffOnLevelGapRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Debuff on Level Gap\", Math.round(simResult.debuffOnLevelGap[playerToDisplay] * 100) + \"%\"]);\r\n debuffOnLevelGapRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.debuffOnLevelGap\");\r\n newChildren.push(debuffOnLevelGapRow);\r\n }\r\n\r\n newChildren.push(encountersRow);\r\n\r\n Object.keys(simResult.deaths)\r\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\r\n .sort()\r\n .forEach(monster => {\r\n let killsPerHour = (simResult.deaths[monster] / hoursSimulated).toFixed(1);\r\n let monsterRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [combatMonsterDetailMap[monster].name, killsPerHour]\r\n );\r\n monsterRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + monster);\r\n newChildren.push(monsterRow);\r\n });\r\n\r\n let { totalDropMap, noRngTotalDropMap } = !simResult.isDungeon ? calcDropMaps(simResult, playerToDisplay) : {totalDropMap:new Map(), noRngTotalDropMap:new Map()};\r\n\r\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\r\n let total = 0;\r\n for (let [name, dropAmount] of totalDropMap.entries()) {\r\n let dropRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [name, dropAmount.toLocaleString()]\r\n );\r\n dropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\r\n newDropChildren.push(dropRow);\r\n\r\n let tableRow = ' tbody\");\r\n let noRngTotal = 0;\r\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\r\n let noRngDropRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [name, dropAmount.toLocaleString()]\r\n );\r\n noRngDropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\r\n newNoRngDropChildren.push(noRngDropRow);\r\n\r\n let tableRow = ' prev + cur, 0);\r\n }\r\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\r\n let totalRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Total\", totalExperiencePerHour]);\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"].forEach((skill) => {\r\n let experience = simResult.experienceGained[playerToDisplay]?.[skill.toLowerCase()] ?? 0;\r\n if (experience == 0) {\r\n return;\r\n }\r\n let experiencePerHour = (experience / hoursSimulated).toFixed(0);\r\n let experienceRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [skill, experiencePerHour]);\r\n experienceRow.firstElementChild.setAttribute(\"data-i18n\", \"leaderboardCategoryNames.\" + skill.toLowerCase());\r\n newChildren.push(experienceRow);\r\n });\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showHpSpent(simResult, playerToDisplay) {\r\n let hpSpentHeadingDiv = document.getElementById(\"simulationHpSpentHeading\");\r\n hpSpentHeadingDiv.classList.add(\"d-none\");\r\n let hpSpentDiv = document.getElementById(\"simulationHpSpent\");\r\n hpSpentDiv.classList.add(\"d-none\");\r\n\r\n if (simResult.hitpointsSpent[playerToDisplay]) {\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let hpSpentSources = [];\r\n for (const source of Object.keys(simResult.hitpointsSpent[playerToDisplay])) {\r\n let hpSpentPerHour = (simResult.hitpointsSpent[playerToDisplay][source] / hoursSimulated).toFixed(2);\r\n let hpSpentRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [abilityDetailMap[source].name, hpSpentPerHour]);\r\n hpSpentRow.firstElementChild.setAttribute(\"data-i18n\", \"abilityNames.\" + source);\r\n hpSpentSources.push(hpSpentRow);\r\n }\r\n hpSpentDiv.replaceChildren(...hpSpentSources);\r\n hpSpentHeadingDiv.classList.remove(\"d-none\");\r\n hpSpentDiv.classList.remove(\"d-none\");\r\n }\r\n}\r\n\r\nfunction showConsumablesUsed(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultConsumablesUsed\");\r\n let newChildren = [];\r\n\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n\r\n if (!simResult.consumablesUsed[playerToDisplay]) {\r\n resultDiv.replaceChildren(...newChildren);\r\n window.expenses = 0;\r\n return;\r\n }\r\n\r\n let consumablesUsed = Object.entries(simResult.consumablesUsed[playerToDisplay]).sort((a, b) => b[1] - a[1]);\r\n\r\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\r\n let total = 0;\r\n for (const [consumable, amount] of consumablesUsed) {\r\n let consumablesPerHour = (amount / hoursSimulated).toFixed(0);\r\n let consumableRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [itemDetailMap[consumable].name, consumablesPerHour]\r\n );\r\n consumableRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + consumable);\r\n newChildren.push(consumableRow);\r\n\r\n let tableRow = ' b[1] - a[1]);\r\n\r\n let totalHitpointsGained = hitpointsGained.reduce((prev, cur) => prev + cur[1], 0);\r\n let totalHitpointsPerSecond = (totalHitpointsGained / secondsSimulated).toFixed(2);\r\n let totalRow = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [\"Total\", totalHitpointsPerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [source, amount] of hitpointsGained) {\r\n if (amount == 0) {\r\n continue;\r\n }\r\n\r\n let sourceText;\r\n let sourceFullHrid;\r\n switch (source) {\r\n case \"regen\":\r\n sourceText = \"Regen\";\r\n sourceFullHrid = \"combatStats.hpRegenPer10\";\r\n break;\r\n case \"lifesteal\":\r\n sourceText = \"Life Steal\";\r\n sourceFullHrid = \"combatStats.lifeSteal\";\r\n break;\r\n case \"bloom\":\r\n sourceText = \"Bloom\";\r\n sourceFullHrid = \"combatStats.bloom\";\r\n break;\r\n default:\r\n if (itemDetailMap[source]) {\r\n sourceText = itemDetailMap[source].name;\r\n sourceFullHrid = \"itemNames.\" + source;\r\n } else if (abilityDetailMap[source]) {\r\n sourceText = abilityDetailMap[source].name;\r\n sourceFullHrid = \"abilityNames.\" + source;\r\n }\r\n break;\r\n }\r\n let hitpointsPerSecond = (amount / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * amount) / totalHitpointsGained).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [sourceText, hitpointsPerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showManapointsGained(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultManaRestored\");\r\n let newChildren = [];\r\n\r\n let secondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n if (!simResult.manapointsGained[playerToDisplay]) {\r\n resultDiv.replaceChildren(...newChildren);\r\n return;\r\n }\r\n\r\n let manapointsGained = Object.entries(simResult.manapointsGained[playerToDisplay]).sort((a, b) => b[1] - a[1]);\r\n\r\n let totalManapointsGained = manapointsGained.reduce((prev, cur) => prev + cur[1], 0);\r\n let totalManapointsPerSecond = (totalManapointsGained / secondsSimulated).toFixed(2);\r\n let totalRow = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [\"Total\", totalManapointsPerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [source, amount] of manapointsGained) {\r\n if (amount == 0) {\r\n continue;\r\n }\r\n\r\n let sourceText;\r\n let sourceFullHrid;\r\n switch (source) {\r\n case \"regen\":\r\n sourceText = \"Regen\";\r\n sourceFullHrid = \"combatStats.mpRegenPer10\";\r\n break;\r\n case \"manaLeech\":\r\n sourceText = \"Mana Leech\";\r\n sourceFullHrid = \"combatStats.manaLeech\";\r\n break;\r\n case \"ripple\":\r\n sourceText = \"Ripple\";\r\n sourceFullHrid = \"combatStats.ripple\";\r\n break;\r\n default:\r\n sourceText = itemDetailMap[source].name;\r\n sourceFullHrid = \"itemNames.\" + source;\r\n break;\r\n }\r\n let manapointsPerSecond = (amount / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * amount) / totalManapointsGained).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [sourceText, manapointsPerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n let ranOutOfManaText = simResult.playerRanOutOfMana[playerToDisplay] ? \"Yes\" : \"No\";\r\n let ranOutOfManaRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Ran out of mana\", ranOutOfManaText]);\r\n ranOutOfManaRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfMana\");\r\n ranOutOfManaRow.lastElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.\" + ranOutOfManaText);\r\n newChildren.push(ranOutOfManaRow);\r\n\r\n if (simResult.playerRanOutOfMana[playerToDisplay]) {\r\n let ranOutOfManaStat = simResult.playerRanOutOfManaTime[playerToDisplay]; // {isOutOfMana: false, startTimeForOutOfMana:0, totalTimeForOutOfMana:0};\r\n let totalTimeForOut = ranOutOfManaStat.totalTimeForOutOfMana + (ranOutOfManaStat.isOutOfMana ? (simResult.simulatedTime - ranOutOfManaStat.startTimeForOutOfMana) : 0);\r\n\r\n let ranOutOfManaStatRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [\r\n \"Run Out Ratio\",\r\n (totalTimeForOut / simResult.simulatedTime * 100).toFixed(2) + \"%\"\r\n ]\r\n );\r\n ranOutOfManaStatRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfManaRatio\");\r\n newChildren.push(ranOutOfManaStatRow);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showDamageDone(simResult, playerToDisplay) {\r\n let totalDamageDone = {};\r\n let enemyIndex = 1;\r\n\r\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n for (let i = 1; i < 64; i++) {\r\n let accordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + i);\r\n hideElement(accordion);\r\n }\r\n\r\n let bossTimeHeadingDiv = document.getElementById(\"simulationBossTimeHeading\");\r\n bossTimeHeadingDiv.classList.add(\"d-none\");\r\n let bossTimeDiv = document.getElementById(\"simulationBossTime\");\r\n bossTimeDiv.classList.add(\"d-none\");\r\n\r\n if (!simResult.attacks[playerToDisplay]) {\r\n return;\r\n }\r\n\r\n for (const [target, abilities] of Object.entries(simResult.attacks[playerToDisplay])) {\r\n let targetDamageDone = {};\r\n\r\n const i = simResult.timeSpentAlive.findIndex(e => e.name === target);\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\r\n\r\n for (const [ability, abilityCasts] of Object.entries(abilities)) {\r\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\r\n let misses = abilityCasts[\"miss\"] ?? 0;\r\n let damage = Object.entries(abilityCasts)\r\n .filter((entry) => entry[0] != \"miss\")\r\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\r\n\r\n targetDamageDone[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n if (totalDamageDone[ability]) {\r\n totalDamageDone[ability].casts += casts;\r\n totalDamageDone[ability].misses += misses;\r\n totalDamageDone[ability].damage += damage;\r\n } else {\r\n totalDamageDone[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n }\r\n }\r\n\r\n let resultDiv = document.getElementById(\"simulationResultDamageDoneEnemy\" + enemyIndex);\r\n createDamageTable(resultDiv, targetDamageDone, aliveSecondsSimulated);\r\n\r\n let resultAccordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + enemyIndex);\r\n showElement(resultAccordion);\r\n\r\n let resultAccordionButton = document.getElementById(\r\n \"buttonSimulationResultDamageDoneAccordionEnemy\" + enemyIndex\r\n );\r\n let targetName = combatMonsterDetailMap[target].name;\r\n resultAccordionButton.innerHTML = \"Damage Done (\" + \"\" + targetName + \"\" + \")\";\r\n\r\n if (simResult.bossSpawns.includes(target)) {\r\n let hoursSpentOnBoss = (aliveSecondsSimulated / 60 / 60).toFixed(2);\r\n let percentSpentOnBoss = (aliveSecondsSimulated / totalSecondsSimulated * 100).toFixed(2);\r\n\r\n let bossRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [targetName, hoursSpentOnBoss + \"h(\" + percentSpentOnBoss + \"%)\"]);\r\n bossRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + target);\r\n bossTimeDiv.replaceChildren(bossRow);\r\n\r\n bossTimeHeadingDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.classList.remove(\"d-none\");\r\n }\r\n\r\n enemyIndex++;\r\n }\r\n\r\n if (simResult.isDungeon) {\r\n let newChildren = [];\r\n for (const waveName of simResult.bossSpawns) {\r\n // waveName is something like \"#15,/monsters/jackalope,/monsters/butterjerry\"\r\n let waveNumber = waveName.split(\",\")[0];\r\n const idx = simResult.timeSpentAlive.findIndex(e => e.name === waveNumber);\r\n if (idx == -1 || simResult.timeSpentAlive[idx].count == 0) {\r\n continue;\r\n }\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[idx].timeSpentAlive / ONE_SECOND / simResult.timeSpentAlive[idx].count;\r\n let bossRow = createRow([\"col-md-6\", \"col-md-2\", \"col-md-4 text-end\"], [waveNumber, simResult.timeSpentAlive[idx].count, aliveSecondsSimulated.toFixed(1) + \"s\"]);\r\n newChildren.push(bossRow);\r\n }\r\n if (newChildren.length > 0) {\r\n bossTimeHeadingDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.replaceChildren(...newChildren);\r\n }\r\n }\r\n\r\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageDone\");\r\n createDamageTable(totalResultDiv, totalDamageDone, totalSecondsSimulated);\r\n}\r\n\r\nfunction showDamageTaken(simResult, playerToDisplay) {\r\n let totalDamageTaken = {};\r\n let enemyIndex = 1;\r\n\r\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n for (let i = 1; i < 64; i++) {\r\n let accordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + i);\r\n hideElement(accordion);\r\n }\r\n\r\n for (const [source, targets] of Object.entries(simResult.attacks)) {\r\n const validSources = [\"player1\", \"player2\", \"player3\", \"player4\", \"player5\"];\r\n if (validSources.includes(source)) {\r\n continue;\r\n }\r\n const i = simResult.timeSpentAlive.findIndex(e => e.name === source);\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\r\n let sourceDamageTaken = {};\r\n if (targets[playerToDisplay] && Object.keys(targets[playerToDisplay]).length > 0) {\r\n for (const [ability, abilityCasts] of Object.entries(targets[playerToDisplay])) {\r\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\r\n let misses = abilityCasts[\"miss\"] ?? 0;\r\n let damage = Object.entries(abilityCasts)\r\n .filter((entry) => entry[0] != \"miss\")\r\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\r\n\r\n sourceDamageTaken[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n if (totalDamageTaken[ability]) {\r\n totalDamageTaken[ability].casts += casts;\r\n totalDamageTaken[ability].misses += misses;\r\n totalDamageTaken[ability].damage += damage;\r\n } else {\r\n totalDamageTaken[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n }\r\n }\r\n }\r\n\r\n let resultDiv = document.getElementById(\"simulationResultDamageTakenEnemy\" + enemyIndex);\r\n createDamageTable(resultDiv, sourceDamageTaken, aliveSecondsSimulated);\r\n\r\n let resultAccordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + enemyIndex);\r\n showElement(resultAccordion);\r\n\r\n let resultAccordionButton = document.getElementById(\r\n \"buttonSimulationResultDamageTakenAccordionEnemy\" + enemyIndex\r\n );\r\n let sourceName = combatMonsterDetailMap[source].name;\r\n resultAccordionButton.innerHTML = \"Damage Taken (\" + \"\" + sourceName + \"\" + \")\";\r\n\r\n enemyIndex++;\r\n }\r\n\r\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageTaken\");\r\n createDamageTable(totalResultDiv, totalDamageTaken, totalSecondsSimulated);\r\n}\r\n\r\nfunction createDamageTable(resultDiv, damageDone, secondsSimulated) {\r\n let newChildren = [];\r\n\r\n let sortedDamageDone = Object.entries(damageDone).sort((a, b) => b[1].damage - a[1].damage);\r\n\r\n let totalCasts = sortedDamageDone.reduce((prev, cur) => prev + cur[1].casts, 0);\r\n let totalMisses = sortedDamageDone.reduce((prev, cur) => prev + cur[1].misses, 0);\r\n let totalDamage = sortedDamageDone.reduce((prev, cur) => prev + cur[1].damage, 0);\r\n let totalHitChance = ((100 * (totalCasts - totalMisses)) / totalCasts).toFixed(1);\r\n let totalDamagePerSecond = (totalDamage / secondsSimulated).toFixed(2);\r\n\r\n let totalRow = createRow(\r\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\r\n [\"Total\", totalHitChance + \"%\", totalDamagePerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [ability, damageInfo] of sortedDamageDone) {\r\n let abilityText;\r\n let abilityFullHrid;\r\n switch (ability) {\r\n case \"autoAttack\":\r\n abilityText = \"Auto Attack\";\r\n abilityFullHrid = \"combatUnit.autoAttack\";\r\n break;\r\n case \"parry\":\r\n abilityText = \"Parry Attack\";\r\n abilityFullHrid = \"common:simulationResults.parryAttack\";\r\n break;\r\n case \"damageOverTime\":\r\n abilityText = \"Damage Over Time\";\r\n abilityFullHrid = \"common:simulationResults.damageOverTime\";\r\n break;\r\n case \"physicalThorns\":\r\n abilityText = \"Physical Thorns\";\r\n abilityFullHrid = \"combatStats.physicalThorns\";\r\n break;\r\n case \"elementalThorns\":\r\n abilityText = \"Elemental Thorns\";\r\n abilityFullHrid = \"combatStats.elementalThorns\";\r\n break;\r\n case \"retaliation\":\r\n abilityText = \"Retaliation\";\r\n abilityFullHrid = \"combatStats.retaliation\";\r\n break;\r\n case 'blaze':\r\n abilityText = \"Blaze\";\r\n abilityFullHrid = \"combatStats.blaze\";\r\n break;\r\n default:\r\n abilityText = abilityDetailMap[ability].name;\r\n abilityFullHrid = \"abilityNames.\" + ability;\r\n break;\r\n }\r\n\r\n let hitChance = ((100 * (damageInfo.casts - damageInfo.misses)) / damageInfo.casts).toFixed(1);\r\n let damagePerSecond = (damageInfo.damage / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * damageInfo.damage) / totalDamage).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\r\n [abilityText, hitChance + \"%\", damagePerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", abilityFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction createRow(columnClassNames, columnValues) {\r\n let row = createElement(\"div\", \"row\");\r\n\r\n for (let i = 0; i < columnClassNames.length; i++) {\r\n let column = createElement(\"div\", columnClassNames[i], columnValues[i]);\r\n row.appendChild(column);\r\n }\r\n\r\n return row;\r\n}\r\n\r\nfunction createElement(tagName, className, innerHTML = \"\", id = \"\") {\r\n let element = document.createElement(tagName);\r\n element.className = className;\r\n element.innerHTML = innerHTML;\r\n if (id) element.id = id;\r\n return element;\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Simulation Controls\r\n\r\ndocument.addEventListener('DOMContentLoaded', function () {\r\n const simDungeonToggle = document.getElementById('simDungeonToggle');\r\n const playerContainer = document.getElementById('playerCheckBox');\r\n\r\n function addPlayers() {\r\n const player4 = document.createElement('div');\r\n player4.classList.add('form-check');\r\n player4.innerHTML = `\r\n \r\n \r\n `;\r\n\r\n const player5 = document.createElement('div');\r\n player5.classList.add('form-check');\r\n player5.innerHTML = `\r\n \r\n \r\n `;\r\n\r\n playerContainer.appendChild(player4);\r\n playerContainer.appendChild(player5);\r\n }\r\n\r\n function removePlayers() {\r\n const player4 = document.getElementById('player4');\r\n const player5 = document.getElementById('player5');\r\n if (player4) player4.parentElement.remove();\r\n if (player5) player5.parentElement.remove();\r\n }\r\n\r\n function updatePlayerNames() {\r\n const tabLinks = document.querySelectorAll('#playerTab .nav-link');\r\n tabLinks.forEach((tabLink, index) => {\r\n const label = document.querySelector(`label[for=\"player${index + 1}\"]`);\r\n if (label) {\r\n label.textContent = tabLink.textContent.trim();\r\n }\r\n });\r\n }\r\n\r\n function updatePlayersCheckbox(isCheck) {\r\n const boxes = playerContainer.querySelectorAll('.player-checkbox');\r\n boxes.forEach((checkBox) => { checkBox.checked = isCheck });\r\n }\r\n\r\n function updateDifficultySelect(isCheck) {\r\n const difficultySelect = document.getElementById('selectDifficulty');\r\n // disable last four option\r\n if (isCheck && Number(difficultySelect.value) >= 3) {\r\n difficultySelect.value = 0;\r\n }\r\n for (let i = 3; i < difficultySelect.options.length; i++) {\r\n difficultySelect.options[i].disabled = isCheck;\r\n }\r\n }\r\n\r\n simDungeonToggle.addEventListener('change', function () {\r\n if (simDungeonToggle.checked) {\r\n addPlayers();\r\n updatePlayersCheckbox(true);\r\n updateDifficultySelect(true);\r\n } else {\r\n removePlayers();\r\n updatePlayersCheckbox(false);\r\n updateDifficultySelect(false);\r\n }\r\n updatePlayerNames();\r\n });\r\n\r\n document.getElementById('buttonSimulationSetup').addEventListener('click', function () {\r\n updatePlayerNames();\r\n });\r\n});\r\n\r\nfunction onTabChange(event) {\r\n const nextPlayerTabId = event.target.getAttribute('href').substring(7);\r\n savePreviousPlayer(currentPlayerTabId);\r\n updateNextPlayer(nextPlayerTabId);\r\n currentPlayerTabId = nextPlayerTabId;\r\n updateState();\r\n updateUI();\r\n if (Object.keys(currentSimResults).length !== 0) {\r\n showSimulationResult(currentSimResults);\r\n }\r\n\r\n updateContent();\r\n}\r\n\r\ndocument.querySelectorAll('#playerTab .nav-link').forEach(tab => {\r\n tab.addEventListener('shown.bs.tab', onTabChange);\r\n});\r\n\r\nfunction initSimulationControls() {\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n simulationTimeInput.value = 24;\r\n\r\n buttonStartSimulation.addEventListener(\"click\", (event) => {\r\n let invalidElements = document.querySelectorAll(\":invalid\");\r\n if (invalidElements.length > 0) {\r\n invalidElements.forEach((element) => element.reportValidity());\r\n return;\r\n }\r\n savePreviousPlayer(currentPlayerTabId);\r\n\r\n const simDungeonToggle = document.getElementById(\"simDungeonToggle\");\r\n const checkboxes = document.querySelectorAll('.player-checkbox');\r\n selectedPlayers = [];\r\n checkboxes.forEach(checkbox => {\r\n if (checkbox.checked) {\r\n const playerNumber = parseInt(checkbox.id.replace('player', ''));\r\n selectedPlayers.push(playerNumber);\r\n }\r\n });\r\n\r\n if (selectedPlayers.length === 0) {\r\n alert(\"You need to select at least one player to sim.\");\r\n return;\r\n }\r\n // buttonStartSimulation.disabled = true;\r\n buttonStopSimulation.style.display = 'block';\r\n startSimulation(selectedPlayers);\r\n });\r\n\r\n buttonStopSimulation.style.display = 'none';\r\n buttonStopSimulation.addEventListener(\"click\", (event) => {\r\n progressbar.style.width = \"0%\";\r\n progressbar.innerHTML = \"0%\";\r\n if (worker) {\r\n worker.terminate();\r\n }\r\n worker = new Worker(new URL(\"worker.js\", import.meta.url));\r\n\r\n if (multiWorker) {\r\n multiWorker.terminate();\r\n }\r\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n\r\n for (let worker of workerPool) {\r\n worker.worker.terminate();\r\n }\r\n\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n });\r\n}\r\n\r\nfunction startSimulation(selectedPlayers) {\r\n let playersToSim = [];\r\n for (let j = 1; j < 6; j++) {\r\n if (selectedPlayers.includes(j)) {\r\n updateNextPlayer(j);\r\n updateState();\r\n updateUI();\r\n player.hrid = \"player\" + j.toString();\r\n for (let i = 0; i < 3; i++) {\r\n if (food[i] && i < player.combatDetails.combatStats.foodSlots) {\r\n let consumable = new Consumable(food[i], triggerMap[food[i]]);\r\n player.food[i] = consumable;\r\n } else {\r\n player.food[i] = null;\r\n }\r\n\r\n if (drinks[i] && i < player.combatDetails.combatStats.drinkSlots) {\r\n let consumable = new Consumable(drinks[i], triggerMap[drinks[i]]);\r\n player.drinks[i] = consumable;\r\n } else {\r\n player.drinks[i] = null;\r\n }\r\n }\r\n\r\n for (let i = 0; i < 5; i++) {\r\n if (abilities[i] && player.intelligenceLevel >= abilitySlotsLevelRequirementList[i + 1]) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let ability = new Ability(abilities[i], Number(abilityLevelInput.value), triggerMap[abilities[i]]);\r\n player.abilities[i] = ability;\r\n } else {\r\n player.abilities[i] = null;\r\n }\r\n }\r\n\r\n playersToSim.push(structuredClone(player));\r\n }\r\n }\r\n updateNextPlayer(currentPlayerTabId);\r\n updateState();\r\n updateUI();\r\n\r\n let maxPlayerCombatLevel = 1;\r\n for (let player of playersToSim) {\r\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\r\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\r\n }\r\n\r\n for (let player of playersToSim) {\r\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\r\n const maxDebuffOnLevelGap = 0.9;\r\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\r\n\r\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\r\n\r\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\r\n }\r\n else {\r\n player.debuffOnLevelGap = 0;\r\n }\r\n }\r\n\r\n let extra = {};\r\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\r\n extra.comExp = 0;\r\n if (document.getElementById(\"comExpToggle\").checked) {\r\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\r\n }\r\n extra.comDrop = 0;\r\n if (document.getElementById(\"comDropToggle\").checked) {\r\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\r\n }\r\n\r\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\r\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\r\n let simDungeonToggle = document.getElementById(\"simDungeonToggle\");\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let dungeonSelect = document.getElementById(\"selectDungeon\");\r\n let difficultySelect = document.getElementById(\"selectDifficulty\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let simulationTimeLimit = Number(simulationTimeInput.value) * ONE_HOUR;\r\n buttonStopSimulation.style.display = 'block';\r\n if (!simAllZonesToggle.checked && !simAllSoloToggle.checked) {\r\n let zoneHrid = zoneSelect.value;\r\n let difficultyTier = Number(difficultySelect.value);\r\n if (simDungeonToggle.checked) {\r\n zoneHrid = dungeonSelect.value;\r\n }\r\n let workerMessage = {\r\n type: \"start_simulation\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n simStartTime = Date.now();\r\n if (!worker) {\r\n worker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n }\r\n worker.onmessage = onWorkerMessage;\r\n worker.postMessage(workerMessage);\r\n } else {\r\n let targetHrids = {};\r\n\r\n if (simAllZonesToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1 &&\r\n document.getElementById(a.hrid)?.checked\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n if (simAllSoloToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount === 1 &&\r\n document.getElementById(a.hrid)?.checked\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n let simHrids = Object.values(targetHrids)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .map(action => {\r\n let result = [];\r\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\r\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\r\n }\r\n return result;\r\n })\r\n .flat();\r\n\r\n let workerMessage = {\r\n type: \"start_simulation_all_zones\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zones: simHrids,\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra: extra\r\n };\r\n simStartTime = Date.now();\r\n if (!multiWorker) {\r\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n }\r\n multiWorker.onmessage = onMultiWorkerMessage;\r\n multiWorker.postMessage(workerMessage);\r\n }\r\n}\r\n\r\nfunction parsePlayerJson(playerJson, hrid) {\r\n let playerData = {\r\n hrid: hrid,\r\n food: [],\r\n drinks: [],\r\n abilities: [],\r\n ...playerJson.player,\r\n houseRooms: playerJson.houseRooms,\r\n };\r\n playerData.equipment = {};\r\n const triggerMap = playerJson.triggerMap;\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"main_hand\", \"two_hand\", \"charm\"].forEach((type) => {\r\n let currentEquipment = playerJson.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment){\r\n playerData.equipment[`/equipment_types/${type}`] = new Equipment(currentEquipment.itemHrid, currentEquipment.enhancementLevel);\r\n }\r\n });\r\n\r\n for (const foodHrid of playerJson.food[\"/action_types/combat\"]) {\r\n if (foodHrid.itemHrid === \"\") continue;\r\n const food = new Consumable(foodHrid.itemHrid, triggerMap[foodHrid.itemHrid]);\r\n playerData.food.push(food);\r\n }\r\n for (const drinkHrid of playerJson.drinks[\"/action_types/combat\"]) {\r\n if (drinkHrid.itemHrid === \"\") continue;\r\n const drink = new Consumable(drinkHrid.itemHrid, triggerMap[drinkHrid.itemHrid]);\r\n playerData.drinks.push(drink);\r\n }\r\n for (const ability of playerJson.abilities) {\r\n if (ability.abilityHrid === \"\") continue;\r\n const abilityLevel = Number(ability.level);\r\n const abilityHrid = ability.abilityHrid;\r\n if (abilityLevel > 0) {\r\n const abilityObj = new Ability(abilityHrid, abilityLevel, triggerMap[abilityHrid]);\r\n playerData.abilities.push(abilityObj);\r\n }\r\n }\r\n const player = Player.createFromDTO(playerData)\r\n player.updateCombatDetails();\r\n player.houseRooms = playerJson.houseRooms;\r\n player.achievements = playerJson.achievements ?? {};\r\n return player;\r\n}\r\n// read JSON file to simulate\r\ndocument.getElementById(\"buttonUploadJSONSimulate\").addEventListener(\"click\", (event) => {\r\n let extra = {};\r\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\r\n extra.comExp = 0;\r\n if (document.getElementById(\"comExpToggle\").checked) {\r\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\r\n }\r\n extra.comDrop = 0;\r\n if (document.getElementById(\"comDropToggle\").checked) {\r\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\r\n }\r\n\r\n let fileInput = document.getElementById(\"inputUploadJSONSimulation\");\r\n let file = fileInput.files[0];\r\n if (!file) {\r\n alert(\"Please select a file to upload.\");\r\n return;\r\n }\r\n\r\n let reader = new FileReader();\r\n reader.onload = function (event) {\r\n let fileContent = event.target.result;\r\n const jsonDataList = JSON.parse(fileContent);\r\n try {\r\n const simDataList = [];\r\n for (const key in jsonDataList) {\r\n if (jsonDataList[key].cases) {\r\n const cases = getProductCases(jsonDataList[key], jsonDataList[key].cases);\r\n simDataList.push(...cases);\r\n } else {\r\n simDataList.push(jsonDataList[key]);\r\n }\r\n }\r\n for (const key in simDataList) {\r\n const jsonData = simDataList[key];\r\n if (!jsonData || !jsonData.zone || !jsonData.players) {\r\n alert(\"Invalid JSON file format. Please ensure it contains a 'simulationResult' property.\");\r\n return;\r\n }\r\n const playersToSim = Object.values(jsonData.players).map(\r\n (player, index) => parsePlayerJson(player, `player${index + 1}`)\r\n );\r\n\r\n let maxPlayerCombatLevel = 1;\r\n for (let player of playersToSim) {\r\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\r\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\r\n }\r\n\r\n for (let player of playersToSim) {\r\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\r\n const maxDebuffOnLevelGap = 0.9;\r\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\r\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\r\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\r\n }\r\n else {\r\n player.debuffOnLevelGap = 0;\r\n }\r\n }\r\n\r\n const simulationTimeLimit = (jsonData.simulationTimeLimit || 24) * ONE_HOUR;\r\n const simName = jsonData.name || `Json ${key}`;\r\n const zoneHrid = jsonData.zone;\r\n if (zoneHrid === \"all\") {\r\n let targetHrids = {};\r\n\r\n if (simAllZonesToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n let simHrids = Object.values(targetHrids)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .map(action => {\r\n let result = [];\r\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\r\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\r\n }\r\n return result;\r\n })\r\n .flat();\r\n\r\n let workerMessage = {\r\n simulationName: simName,\r\n type: \"start_simulation_all_zones\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zones: simHrids,\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \r\n worker.onmessage = mainWorkerOnMessage;\r\n worker.postMessage(workerMessage);\r\n customAlert(\"Simulation task Created\", \"info\")\r\n workerPool.push({\r\n workerId: workerMessage.workerId,\r\n worker: worker,\r\n });\r\n } else {\r\n let difficultyTier = jsonData.difficultyTier || 0;\r\n let workerMessage = {\r\n simulationName: simName,\r\n type: \"start_simulation\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \r\n worker.onmessage = mainWorkerOnMessage;\r\n worker.postMessage(workerMessage);\r\n customAlert(\"Simulation task Created\", \"info\")\r\n workerPool.push({\r\n workerId: workerMessage.workerId,\r\n worker: worker,\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n // alert(\"Error parsing JSON file: \" + error.message);\r\n customAlert(\"Error parsing JSON file: \" + error.message, \"danger\");\r\n }\r\n }\r\n reader.readAsText(file);\r\n});\r\n\r\n\r\n// #endregion\r\n\r\n// #region WipeEvents\r\n\r\nfunction renderWipeEvents(simResult) {\r\n const selector = document.getElementById('wipeEventSelector');\r\n const logsContainer = document.getElementById('wipeLogsContainer');\r\n const waveBadge = document.getElementById('wipeWaveBadge');\r\n const timeInfo = document.getElementById('wipeTimeInfo');\r\n\r\n selector.innerHTML = '';\r\n logsContainer.innerHTML = '';\r\n\r\n if (!simResult.wipeEvents || simResult.wipeEvents.length === 0) {\r\n selector.innerHTML = ``;\r\n logsContainer.innerHTML = `
No Wipe Events Detected
`;\r\n waveBadge.textContent = '';\r\n timeInfo.textContent = '';\r\n return;\r\n }\r\n\r\n simResult.wipeEvents.forEach((event, index) => {\r\n const wave = event.wave || '?';\r\n // const time = (event.simulationTime / 1e9).toFixed(2);\r\n // const timestamp = new Date(event.timestamp).toLocaleTimeString();\r\n\r\n const option = document.createElement('option');\r\n option.value = index;\r\n option.textContent = `#${index + 1} - 波次: ${wave}`;\r\n selector.appendChild(option);\r\n });\r\n\r\n selector.value = 0;\r\n renderSelectedWipeEvent(0, simResult);\r\n\r\n selector.addEventListener('change', () => {\r\n renderSelectedWipeEvent(selector.value, simResult);\r\n });\r\n}\r\n\r\n// 渲染选中的团灭事件\r\nfunction renderSelectedWipeEvent(index, simResult) {\r\n const logsContainer = document.getElementById('wipeLogsContainer');\r\n const waveBadge = document.getElementById('wipeWaveBadge');\r\n const timeInfo = document.getElementById('wipeTimeInfo');\r\n\r\n logsContainer.innerHTML = '';\r\n\r\n if (index < 0 || index >= simResult.wipeEvents.length) {\r\n logsContainer.innerHTML = `
No Wipe Events
`;\r\n waveBadge.textContent = '';\r\n timeInfo.textContent = '';\r\n return;\r\n }\r\n\r\n const wipeEvent = simResult.wipeEvents[index];\r\n const wave = wipeEvent.wave || '?';\r\n const time = (wipeEvent.simulationTime / 1e9).toFixed(2);\r\n const timestamp = new Date(wipeEvent.timestamp).toLocaleString();\r\n\r\n waveBadge.textContent = `波次: ${wave}`;\r\n timeInfo.textContent = `模拟时间: ${time}s | 记录时间: ${timestamp}`;\r\n\r\n const logsByTime = groupLogsByTime(wipeEvent.logs);\r\n\r\n const baseTime = logsByTime.length > 0 ? logsByTime[0].time : 0;\r\n\r\n logsByTime.forEach(group => {\r\n const timeGroupElement = document.createElement('div');\r\n timeGroupElement.className = 'log-time-group';\r\n\r\n const relativeTime = (group.time - baseTime) / 1e9;\r\n\r\n // 时间标题\r\n const timeHeader = document.createElement('div');\r\n timeHeader.className = 'log-time-header';\r\n timeHeader.textContent = `[${relativeTime.toFixed(2)}s] [Wave#${group.wave}]`;\r\n timeGroupElement.appendChild(timeHeader);\r\n\r\n // 事件列表\r\n const eventsList = document.createElement('div');\r\n eventsList.className = 'log-events';\r\n\r\n const damagedPlayers = new Set();\r\n\r\n group.logs.forEach(log => {\r\n const eventElement = document.createElement('div');\r\n eventElement.className = 'log-event';\r\n\r\n damagedPlayers.add(log.target);\r\n\r\n const sourceSpan = document.createElement('span');\r\n sourceSpan.className = 'log-source';\r\n if (log.ability === \"damageOverTime\") {\r\n sourceSpan.textContent = log.target;\r\n } else if(log.source == 'UNKNOWN_SOURCE') {\r\n sourceSpan.textContent = 'UNKNOWN';\r\n } else {\r\n sourceSpan.setAttribute('data-i18n', `monsterNames.${log.source}`);\r\n sourceSpan.textContent = log.source;\r\n }\r\n\r\n const castSpan = document.createElement('span');\r\n castSpan.className = 'log-cast';\r\n castSpan.setAttribute('data-i18n', `common:cast`);\r\n castSpan.textContent = ' cast ';\r\n\r\n const abilitySpan = document.createElement('span');\r\n abilitySpan.className = 'log-ability';\r\n if (log.ability === \"autoAttack\") {\r\n abilitySpan.setAttribute('data-i18n', 'combatUnit.autoAttack');\r\n abilitySpan.textContent = 'Auto Attack';\r\n } else if (log.ability === \"physicalThorns\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.physicalThorns`);\r\n abilitySpan.textContent = 'Physical Thorns';\r\n } else if (log.ability === \"elementalThorns\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.elementalThorns`);\r\n abilitySpan.textContent = 'Elemental Thorns';\r\n } else if (log.ability === \"retaliation\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.retaliation`);\r\n abilitySpan.textContent = 'Retaliation';\r\n } else if (log.ability === \"damageOverTime\") {\r\n abilitySpan.setAttribute('data-i18n', `common:simulationResults.damageOverTime`);\r\n abilitySpan.textContent = 'Damage Over Time';\r\n } else {\r\n abilitySpan.setAttribute('data-i18n', `abilityNames.${log.ability}`);\r\n abilitySpan.textContent = log.ability;\r\n }\r\n\r\n const toSpan = document.createElement('span');\r\n toSpan.className = 'log-to';\r\n toSpan.setAttribute('data-i18n', `common:to`);\r\n toSpan.textContent = ' to ';\r\n\r\n const targetSpan = document.createElement('span');\r\n targetSpan.className = 'log-target';\r\n targetSpan.textContent = log.target;\r\n\r\n const dealDamageSpan = document.createElement('span');\r\n dealDamageSpan.className = 'log-deal-damage';\r\n dealDamageSpan.setAttribute('data-i18n', `common:dealDamage`);\r\n dealDamageSpan.textContent = ' deal damage ';\r\n\r\n const damageDoneSpan = document.createElement('span');\r\n damageDoneSpan.className = 'log-damage-done';\r\n damageDoneSpan.textContent = log.damage;\r\n if (log.isCrit) {\r\n damageDoneSpan.style.fontWeight = 'bold';\r\n damageDoneSpan.textContent += '!!!';\r\n }\r\n\r\n eventElement.appendChild(sourceSpan);\r\n eventElement.appendChild(castSpan);\r\n eventElement.appendChild(abilitySpan);\r\n eventElement.appendChild(toSpan);\r\n eventElement.appendChild(targetSpan);\r\n eventElement.appendChild(dealDamageSpan);\r\n eventElement.appendChild(damageDoneSpan);\r\n eventElement.appendChild(document.createTextNode(` , HP ${log.beforeHp} → ${log.afterHp}`));\r\n\r\n eventsList.appendChild(eventElement);\r\n });\r\n\r\n timeGroupElement.appendChild(eventsList);\r\n\r\n const lastLog = group.logs[group.logs.length - 1];\r\n const playersHpElement = document.createElement('div');\r\n\r\n const playerHpTitle = document.createElement('span');\r\n playerHpTitle.className = 'log-players-hp';\r\n playerHpTitle.setAttribute('data-i18n', `common:playersHp`);\r\n playerHpTitle.textContent = 'Players HP: ';\r\n playersHpElement.appendChild(playerHpTitle);\r\n\r\n lastLog.playersHp.forEach((player, idx) => {\r\n const playerElement = document.createElement('span');\r\n playerElement.className = 'log-player-hp';\r\n playerElement.textContent = `${player.hrid}: ${player.current}/${player.max}`;\r\n\r\n if (player.current <= 0) {\r\n playerElement.style.color = darkModeToggle.checked ? '#FF6347' : '#CC0000';\r\n } else if (damagedPlayers.has(player.hrid)) {\r\n playerElement.style.color = darkModeToggle.checked ? '#00BFFF' : '#007BFF';\r\n }\r\n\r\n if (idx > 0) {\r\n playersHpElement.appendChild(document.createTextNode(' | '));\r\n }\r\n playersHpElement.appendChild(playerElement);\r\n });\r\n const spacer = document.createElement('div');\r\n spacer.style.height = '15px';\r\n logsContainer.appendChild(spacer);\r\n timeGroupElement.appendChild(playersHpElement);\r\n logsContainer.appendChild(timeGroupElement);\r\n });\r\n\r\n // 更新汉化\r\n updateContent()\r\n}\r\n\r\n// 按时间分组日志\r\nfunction groupLogsByTime(logs) {\r\n const groups = [];\r\n let currentGroup = null;\r\n\r\n logs.forEach(log => {\r\n if (!currentGroup || currentGroup.time !== log.time) {\r\n currentGroup = {\r\n time: log.time,\r\n wave: log.wave,\r\n logs: [log]\r\n };\r\n groups.push(currentGroup);\r\n } else {\r\n currentGroup.logs.push(log);\r\n }\r\n });\r\n\r\n groups.forEach(group => {\r\n let hpMap = {};\r\n if (group.logs.length > 0) {\r\n group.logs[0].playersHp.forEach(p => {\r\n hpMap[p.hrid] = { current: p.current, max: p.max };\r\n });\r\n }\r\n group.logs.forEach(log => {\r\n if (hpMap[log.target]) {\r\n hpMap[log.target].current = log.afterHp;\r\n }\r\n });\r\n group.logs.forEach(log => {\r\n log.playersHp = Object.entries(hpMap).map(([hrid, val]) => ({\r\n hrid,\r\n current: val.current,\r\n max: val.max\r\n }));\r\n });\r\n });\r\n\r\n return groups;\r\n}\r\n\r\n// #endregion\r\n\r\n\r\n// #region Equipment Sets\r\n\r\nfunction initEquipmentSetsModal() {\r\n let equipmentSetsModal = document.getElementById(\"equipmentSetsModal\");\r\n equipmentSetsModal.addEventListener(\"show.bs.modal\", equipmentSetsModalShownHandler);\r\n\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n equipmentSetNameInput.addEventListener(\"input\", (event) => equipmentSetNameChangedHandler(event));\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.addEventListener(\"click\", createNewEquipmentSetHandler);\r\n}\r\n\r\nfunction equipmentSetsModalShownHandler() {\r\n resetNewEquipmentSetControls();\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction resetNewEquipmentSetControls() {\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n equipmentSetNameInput.value = \"\";\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.disabled = true;\r\n}\r\n\r\nfunction updateEquipmentSetList() {\r\n let newChildren = [];\r\n let equipmentSets = loadEquipmentSets();\r\n\r\n for (const equipmentSetName of Object.keys(equipmentSets)) {\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let nameCol = createElement(\"div\", \"col align-self-center\", equipmentSetName);\r\n row.appendChild(nameCol);\r\n\r\n let loadButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let loadButton = createElement(\"button\", \"btn btn-primary\", \"Load\");\r\n loadButton.setAttribute(\"data-i18n\", \"common:controls.load\");\r\n loadButton.setAttribute(\"type\", \"button\");\r\n loadButton.addEventListener(\"click\", (_) => loadEquipmentSetHandler(equipmentSetName));\r\n loadButtonCol.appendChild(loadButton);\r\n row.appendChild(loadButtonCol);\r\n\r\n let saveButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let saveButton = createElement(\"button\", \"btn btn-primary\", \"Save\");\r\n saveButton.setAttribute(\"data-i18n\", \"common:controls.save\");\r\n saveButton.setAttribute(\"type\", \"button\");\r\n saveButton.addEventListener(\"click\", (_) => updateEquipmentSetHandler(equipmentSetName));\r\n saveButtonCol.appendChild(saveButton);\r\n row.appendChild(saveButtonCol);\r\n\r\n let deleteButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let deleteButton = createElement(\"button\", \"btn btn-danger\", \"Delete\");\r\n deleteButton.setAttribute(\"data-i18n\", \"common:controls.delete\");\r\n deleteButton.setAttribute(\"type\", \"button\");\r\n deleteButton.addEventListener(\"click\", (_) => deleteEquipmentSetHandler(equipmentSetName));\r\n deleteButtonCol.appendChild(deleteButton);\r\n row.appendChild(deleteButtonCol);\r\n\r\n newChildren.push(row);\r\n }\r\n\r\n let equipmentSetList = document.getElementById(\"equipmentSetList\");\r\n equipmentSetList.replaceChildren(...newChildren);\r\n\r\n updateContent();\r\n}\r\n\r\nfunction equipmentSetNameChangedHandler(event) {\r\n let invalid = false;\r\n\r\n if (event.target.value.length == 0) {\r\n invalid = true;\r\n }\r\n\r\n let equipmentSets = loadEquipmentSets();\r\n if (equipmentSets[event.target.value]) {\r\n invalid = true;\r\n }\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.disabled = invalid;\r\n}\r\n\r\nfunction createNewEquipmentSetHandler() {\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n let equipmentSetName = equipmentSetNameInput.value;\r\n\r\n let equipmentSet = getEquipmentSetFromUI();\r\n let equipmentSets = loadEquipmentSets();\r\n equipmentSets[equipmentSetName] = equipmentSet;\r\n saveEquipmentSets(equipmentSets);\r\n\r\n resetNewEquipmentSetControls();\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction loadEquipmentSetHandler(name) {\r\n let equipmentSets = loadEquipmentSets();\r\n loadEquipmentSetIntoUI(equipmentSets[name]);\r\n}\r\n\r\nfunction updateEquipmentSetHandler(name) {\r\n let equipmentSet = getEquipmentSetFromUI();\r\n let equipmentSets = loadEquipmentSets();\r\n equipmentSets[name] = equipmentSet;\r\n saveEquipmentSets(equipmentSets);\r\n}\r\n\r\nfunction deleteEquipmentSetHandler(name) {\r\n let equipmentSets = loadEquipmentSets();\r\n delete equipmentSets[name];\r\n saveEquipmentSets(equipmentSets);\r\n\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction loadEquipmentSets() {\r\n return JSON.parse(localStorage.getItem(\"equipmentSets\")) ?? {};\r\n}\r\n\r\nfunction saveEquipmentSets(equipmentSets) {\r\n localStorage.setItem(\"equipmentSets\", JSON.stringify(equipmentSets));\r\n}\r\n\r\nfunction getEquipmentSetFromUI() {\r\n let equipmentSet = {\r\n levels: {},\r\n equipment: {},\r\n food: {},\r\n drinks: {},\r\n abilities: {},\r\n triggerMap: {},\r\n houseRooms: {},\r\n achievements: {},\r\n };\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n equipmentSet.levels[skill] = Number(levelInput.value);\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n\r\n equipmentSet.equipment[type] = {\r\n equipment: equipmentSelect.value,\r\n enhancementLevel: Number(enhancementLevelInput.value),\r\n };\r\n });\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n equipmentSet.food[i] = foodSelect.value;\r\n }\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n equipmentSet.drinks[i] = drinkSelect.value;\r\n }\r\n\r\n for (let i = 0; i < 5; i++) {\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n equipmentSet.abilities[i] = {\r\n ability: abilitySelect.value,\r\n level: Number(abilityLevelInput.value),\r\n };\r\n }\r\n\r\n equipmentSet.triggerMap = triggerMap;\r\n\r\n equipmentSet.houseRooms = player.houseRooms;\r\n equipmentSet.achievements = player.achievements;\r\n\r\n return equipmentSet;\r\n}\r\n\r\nfunction fixTriggerMap(triggerMap) {\r\n let delKeys = []\r\n for (const key of Object.keys(triggerMap)) {\r\n let err = false;\r\n if (null == triggerMap[key]) {\r\n triggerMap[key] = [];\r\n }\r\n for (const trigger of triggerMap[key]) {\r\n if (!combatTriggerConditionDetailMap[trigger.conditionHrid]) {\r\n err = true;\r\n break;\r\n }\r\n }\r\n if (err) {\r\n delKeys.push(key);\r\n }\r\n }\r\n for (const key of delKeys) {\r\n delete triggerMap[key];\r\n }\r\n}\r\n\r\nfunction loadEquipmentSetIntoUI(equipmentSet) {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !equipmentSet.levels[\"meleeLevel\"] && equipmentSet.levels[\"powerLevel\"]) {\r\n equipmentSet.levels[\"meleeLevel\"] = equipmentSet.levels[\"powerLevel\"];\r\n }\r\n levelInput.value = equipmentSet.levels[skill] ?? 1;\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n\r\n let currentEquipment = equipmentSet.equipment[type];\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.equipment;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n foodSelect.value = equipmentSet.food[i];\r\n }\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n drinkSelect.value = equipmentSet.drinks[i].replace(\"power\", \"melee\");\r\n }\r\n\r\n let hasSpecial = false;\r\n if (equipmentSet.abilities && Object.keys(equipmentSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n equipmentSet.abilities[i].ability == \"/abilities/aqua_aura\" ||\r\n equipmentSet.abilities[i].ability == \"/abilities/flame_aura\" ||\r\n equipmentSet.abilities[i].ability == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n equipmentSet.abilities[i].ability = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (equipmentSet.abilities[i].ability == \"/abilities/arcane_reflection\") {\r\n equipmentSet.abilities[i].ability = \"/abilities/retribution\";\r\n }\r\n\r\n abilitySelect.value = equipmentSet.abilities[i].ability;\r\n abilityLevelInput.value = equipmentSet.abilities[i].level;\r\n }\r\n\r\n triggerMap = equipmentSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n\r\n if (equipmentSet.houseRooms) {\r\n for (const room in equipmentSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (equipmentSet.houseRooms[room]) {\r\n field.value = equipmentSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = equipmentSet.houseRooms;\r\n } else {\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n\r\n if (equipmentSet.achievements) {\r\n for (const achievement in equipmentSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (equipmentSet.achievements[achievement]) {\r\n field.checked = true;\r\n } else {\r\n field.checked = false;\r\n }\r\n player.achievements[achievement] = field.checked;\r\n }\r\n } else {\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n refreshAchievementStatics();\r\n\r\n updateState();\r\n updateUI();\r\n\r\n updateContent();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Error Handling\r\n\r\nfunction initErrorHandling() {\r\n window.addEventListener(\"error\", (event) => {\r\n showErrorModal(event.message);\r\n });\r\n\r\n let copyErrorButton = document.getElementById(\"buttonCopyError\");\r\n copyErrorButton.addEventListener(\"click\", (event) => {\r\n let errorInput = document.getElementById(\"inputError\");\r\n navigator.clipboard.writeText(errorInput.value);\r\n });\r\n}\r\n\r\nfunction initImportExportModal() {\r\n let exportSetButton = document.getElementById(\"buttonExportSet\");\r\n exportSetButton.addEventListener(\"click\", (event) => {\r\n savePreviousPlayer(currentPlayerTabId);\r\n const activeTab = document.querySelector('#importTab .nav-link.active');\r\n if (activeTab.id === 'group-combat-tab') {\r\n doGroupExport();\r\n } else if (activeTab.id === 'solo-tab') {\r\n doSoloExport();\r\n }\r\n });\r\n\r\n let importSetButton = document.getElementById(\"buttonImportSet\");\r\n importSetButton.addEventListener(\"click\", (event) => {\r\n const activeTab = document.querySelector('#importTab .nav-link.active');\r\n if (activeTab.id === 'group-combat-tab') {\r\n doGroupImport();\r\n } else if (activeTab.id === 'solo-tab') {\r\n doSoloImport();\r\n }\r\n updateState();\r\n updateUI();\r\n resetImportInputs();\r\n });\r\n}\r\n\r\nfunction resetImportInputs() {\r\n document.getElementById('inputSetGroupCombatAll').value = '';\r\n document.getElementById('inputSetGroupCombatplayer1').value = '';\r\n document.getElementById('inputSetGroupCombatplayer2').value = '';\r\n document.getElementById('inputSetGroupCombatplayer3').value = '';\r\n document.getElementById('inputSetGroupCombatplayer4').value = '';\r\n document.getElementById('inputSetGroupCombatplayer5').value = '';\r\n document.getElementById('inputSetSolo').value = '';\r\n}\r\n\r\nfunction doGroupExport() {\r\n try {\r\n navigator.clipboard.writeText(JSON.stringify(playerDataMap)).then(() => alert(\"Current Group has been copied to clipboard.\"));\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction doSoloExport() {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let equipmentArray = [];\r\n for (const item in player.equipment) {\r\n if (player.equipment[item] != null) {\r\n equipmentArray.push({\r\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\r\n \"itemHrid\": player.equipment[item].hrid,\r\n \"enhancementLevel\": player.equipment[item].enhancementLevel\r\n });\r\n }\r\n }\r\n let playerArray = {\r\n \"attackLevel\": player.attackLevel,\r\n \"magicLevel\": player.magicLevel,\r\n \"meleeLevel\": player.meleeLevel,\r\n \"rangedLevel\": player.rangedLevel,\r\n \"defenseLevel\": player.defenseLevel,\r\n \"staminaLevel\": player.staminaLevel,\r\n \"intelligenceLevel\": player.intelligenceLevel,\r\n \"equipment\": equipmentArray\r\n };\r\n let abilitiesArray = [];\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let abilityName = document.getElementById(\"selectAbility_\" + i);\r\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\r\n }\r\n let drinksArray = [];\r\n for (let i = 0; i < drinks?.length; i++) {\r\n drinksArray.push({ \"itemHrid\": drinks[i] });\r\n }\r\n let foodArray = [];\r\n for (let i = 0; i < food?.length; i++) {\r\n foodArray.push({ \"itemHrid\": food[i] });\r\n }\r\n let state = {\r\n player: playerArray,\r\n food: { \"/action_types/combat\": foodArray },\r\n drinks: { \"/action_types/combat\": drinksArray },\r\n abilities: abilitiesArray,\r\n triggerMap: triggerMap,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n houseRooms: player.houseRooms,\r\n achievements: player.achievements\r\n };\r\n try {\r\n navigator.clipboard.writeText(JSON.stringify(state)).then(() => alert(\"Current set has been copied to clipboard.\"));\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction setPlayerData(playerId, inputElementId) {\r\n const inputElement = document.getElementById(inputElementId);\r\n const value = inputElement ? inputElement.value.trim() : \"\";\r\n\r\n // Only set the value in the map if it's not null, undefined, or empty\r\n if (value) {\r\n playerDataMap[playerId] = value;\r\n return true;\r\n }\r\n return false;\r\n}\r\n\r\nfunction doGroupImport() {\r\n let needUpdateCurrentTab = false;\r\n const value = document.getElementById(\"inputSetGroupCombatAll\")?.value || \"\";\r\n if (!value.trim()) {\r\n for (let i of ['1', '2', '3', '4', '5']) {\r\n if (setPlayerData(i, \"inputSetGroupCombatplayer\" + i) && currentPlayerTabId == i) {\r\n needUpdateCurrentTab = true;\r\n }\r\n }\r\n } else {\r\n playerDataMap = JSON.parse(value);\r\n needUpdateCurrentTab = true;\r\n }\r\n\r\n if (needUpdateCurrentTab) {\r\n updateNextPlayer(currentPlayerTabId);\r\n }\r\n}\r\n\r\nfunction doSoloImport() {\r\n let importSet = document.getElementById(\"inputSetSolo\").value;\r\n importSet = JSON.parse(importSet);\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\r\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\r\n }\r\n levelInput.value = importSet.player[skill + \"Level\"];\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.itemHrid;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\r\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\r\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\r\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\r\n if (mainhandWeapon !== undefined) {\r\n weaponSelect.value = mainhandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\r\n } else if (twohandWeapon !== undefined) {\r\n weaponSelect.value = twohandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\r\n } else {\r\n weaponSelect.value = \"\";\r\n weaponEnhancementLevelInput.value = 0;\r\n }\r\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\r\n importSet.food = importSet.food[\"/action_types/combat\"];\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n if (importSet.drinks[i] != null) {\r\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\r\n } else {\r\n drinkSelect.value = \"\";\r\n }\r\n if (importSet.food[i] != null) {\r\n foodSelect.value = importSet.food[i].itemHrid;\r\n } else {\r\n foodSelect.value = \"\";\r\n }\r\n }\r\n\r\n let hasSpecial = false;\r\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\r\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\r\n }\r\n\r\n if (importSet.abilities[i] != null) {\r\n abilitySelect.value = importSet.abilities[i].abilityHrid;\r\n abilityLevelInput.value = String(importSet.abilities[i].level);\r\n } else {\r\n abilitySelect.value = \"\";\r\n abilityLevelInput.value = \"1\";\r\n }\r\n }\r\n\r\n if (importSet.triggerMap) {\r\n triggerMap = importSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n }\r\n\r\n if (importSet.houseRooms) {\r\n for (const room in importSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (importSet.houseRooms[room]) {\r\n field.value = importSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = importSet.houseRooms;\r\n } else {\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n\r\n if (importSet.achievements) {\r\n for (const achievement in importSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (importSet.achievements[achievement]) {\r\n field.checked = true;\r\n } else {\r\n field.checked = false;\r\n }\r\n player.achievements[achievement] = field.checked;\r\n }\r\n } else {\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n refreshAchievementStatics();\r\n\r\n if (\"zone\" in importSet) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n zoneSelect.value = importSet[\"zone\"];\r\n }\r\n\r\n if (\"simulationTime\" in importSet) {\r\n let simulationDuration = document.getElementById(\"inputSimulationTime\");\r\n simulationDuration.value = importSet[\"simulationTime\"];\r\n }\r\n}\r\n\r\nfunction savePreviousPlayer(playerId) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let equipmentArray = [];\r\n for (const item in player.equipment) {\r\n if (player.equipment[item] != null) {\r\n equipmentArray.push({\r\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\r\n \"itemHrid\": player.equipment[item].hrid,\r\n \"enhancementLevel\": player.equipment[item].enhancementLevel\r\n });\r\n }\r\n }\r\n let playerArray = {\r\n \"attackLevel\": player.attackLevel,\r\n \"magicLevel\": player.magicLevel,\r\n \"meleeLevel\": player.meleeLevel,\r\n \"rangedLevel\": player.rangedLevel,\r\n \"defenseLevel\": player.defenseLevel,\r\n \"staminaLevel\": player.staminaLevel,\r\n \"intelligenceLevel\": player.intelligenceLevel,\r\n \"equipment\": equipmentArray\r\n };\r\n let abilitiesArray = [];\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let abilityName = document.getElementById(\"selectAbility_\" + i);\r\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\r\n }\r\n let drinksArray = [];\r\n for (let i = 0; i < drinks?.length; i++) {\r\n drinksArray.push({ \"itemHrid\": drinks[i] });\r\n }\r\n let foodArray = [];\r\n for (let i = 0; i < food?.length; i++) {\r\n foodArray.push({ \"itemHrid\": food[i] });\r\n }\r\n let state = {\r\n player: playerArray,\r\n food: { \"/action_types/combat\": foodArray },\r\n drinks: { \"/action_types/combat\": drinksArray },\r\n abilities: abilitiesArray,\r\n triggerMap: triggerMap,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n houseRooms: player.houseRooms,\r\n achievements: player.achievements\r\n };\r\n try {\r\n playerDataMap[playerId] = JSON.stringify(state);\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction updateNextPlayer(currentPlayerNumber) {\r\n let playerImportData = playerDataMap[currentPlayerNumber];\r\n let importSet = JSON.parse(playerImportData);\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\r\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\r\n }\r\n levelInput.value = importSet.player[skill + \"Level\"];\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.itemHrid;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\r\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\r\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\r\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\r\n if (mainhandWeapon !== undefined) {\r\n weaponSelect.value = mainhandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\r\n } else if (twohandWeapon !== undefined) {\r\n weaponSelect.value = twohandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\r\n } else {\r\n weaponSelect.value = \"\";\r\n weaponEnhancementLevelInput.value = 0;\r\n }\r\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\r\n importSet.food = importSet.food[\"/action_types/combat\"];\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n if (importSet.drinks[i] != null) {\r\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\r\n } else {\r\n drinkSelect.value = \"\";\r\n }\r\n if (importSet.food[i] != null) {\r\n foodSelect.value = importSet.food[i].itemHrid;\r\n } else {\r\n foodSelect.value = \"\";\r\n }\r\n }\r\n\r\n let hasSpecial = false;\r\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\r\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\r\n }\r\n\r\n if (importSet.abilities[i] != null) {\r\n abilitySelect.value = importSet.abilities[i].abilityHrid;\r\n abilityLevelInput.value = String(importSet.abilities[i].level);\r\n } else {\r\n abilitySelect.value = \"\";\r\n abilityLevelInput.value = \"1\";\r\n }\r\n }\r\n\r\n if (importSet.triggerMap) {\r\n triggerMap = importSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n }\r\n\r\n { // reset all houseRooms\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n if (importSet.houseRooms) {\r\n for (const room in importSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (importSet.houseRooms[room]) {\r\n field.value = importSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = importSet.houseRooms;\r\n }\r\n\r\n { // reset all achievements\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n if (importSet.achievements) {\r\n for (const achievement in importSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (importSet.achievements[achievement]) {\r\n field.checked = true;\r\n player.achievements[achievement] = true;\r\n } else {\r\n field.checked = false;\r\n player.achievements[achievement] = false;\r\n }\r\n }\r\n }\r\n refreshAchievementStatics();\r\n}\r\n\r\nfunction showErrorModal(error) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n\r\n let state = {\r\n error: error,\r\n player: player,\r\n food: food,\r\n drinks: drinks,\r\n abilities: abilities,\r\n triggerMap: triggerMap,\r\n modalTriggers: modalTriggers,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n };\r\n\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n state[\"abilityLevel\" + i] = abilityLevelInput.value;\r\n }\r\n\r\n let errorInput = document.getElementById(\"inputError\");\r\n errorInput.value = JSON.stringify(state);\r\n\r\n let errorModal = new bootstrap.Modal(document.getElementById(\"errorModal\"));\r\n errorModal.show();\r\n}\r\n\r\nwindow.prices;\r\n\r\nasync function fetchPrices() {\r\n let response = null;\r\n try {\r\n response = await fetch('https://www.milkywayidle.com/game_data/marketplace.json'\r\n , {\r\n mode: 'cors'\r\n }\r\n );\r\n if (!response.ok) {\r\n console.log('Error fetching prices');\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n\r\n if (response == null) {\r\n try {\r\n response = await fetch('https://www.milkywayidlecn.com/game_data/marketplace.json'\r\n , {\r\n mode: 'cors'\r\n }\r\n );\r\n if (!response.ok) {\r\n console.log('Error fetching prices');\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n }\r\n\r\n if (!response || !response.ok) {\r\n return;\r\n }\r\n\r\n try {\r\n\r\n let btn = document.querySelector('#buttonGetPrices');\r\n btn.style.backgroundColor = 'green';\r\n\r\n const pricesJson = await response.json();\r\n\r\n const priceTmp = pricesJson['marketData'];\r\n window.prices = {};\r\n for (const item in itemDetailMap) {\r\n const hrid = itemDetailMap[item].hrid;\r\n if (hrid in priceTmp) {\r\n window.prices[hrid] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (priceTmp[hrid]['0']) {\r\n window.prices[hrid].ask = priceTmp[hrid]['0'].a;\r\n window.prices[hrid].bid = priceTmp[hrid]['0'].b;\r\n }\r\n }\r\n } \r\n\r\n window.prices[\"/items/coin\"] = { \"ask\": 1, \"bid\": 1, \"vendor\": 1 };\r\n\r\n window.prices[\"/items/small_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n window.prices[\"/items/medium_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n window.prices[\"/items/large_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n}\r\n\r\ndocument.getElementById(\"buttonGetPrices\").onclick = async () => {\r\n await fetchPrices();\r\n};\r\n\r\ndocument.addEventListener(\"input\", (e) => {\r\n let element = e.target;\r\n if (element.tagName == \"TD\" && element.parentNode.parentNode.parentNode.classList.value.includes('profit-table')) {\r\n let tableId = element.parentNode.parentNode.parentNode.id;\r\n let row = element.parentNode.querySelectorAll('td');\r\n let item = row[0].getAttribute('data-i18n').split('.')[1];\r\n let newPrice = element.innerText;\r\n\r\n let revenueSetting = document.getElementById('selectPrices_drops').value;\r\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\r\n\r\n let expensesDifference = 0;\r\n let revenueDifference = 0;\r\n let noRngRevenueDifference = 0;\r\n\r\n if (tableId == 'expensesTable') {\r\n expensesDifference = updateTable('expensesTable', item, newPrice);\r\n if (revenueSetting == expensesSetting) {\r\n revenueDifference = updateTable('revenueTable', item, newPrice);\r\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\r\n }\r\n if (window.prices) {\r\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (expensesSetting == 'bid') {\r\n window.prices[item]['bid'] = newPrice;\r\n } else {\r\n window.prices[item]['ask'] = newPrice;\r\n }\r\n }\r\n } else {\r\n revenueDifference = updateTable('revenueTable', item, newPrice);\r\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\r\n if (revenueSetting == expensesSetting) {\r\n expensesDifference = updateTable('expensesTable', item, newPrice);\r\n }\r\n if (window.prices) {\r\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (revenueSetting == 'bid') {\r\n window.prices[item]['bid'] = newPrice;\r\n } else {\r\n window.prices[item]['ask'] = newPrice;\r\n }\r\n }\r\n }\r\n\r\n window.expenses += expensesDifference;\r\n document.getElementById('expensesSpan').innerText = window.expenses.toLocaleString();\r\n window.revenue += revenueDifference;\r\n document.getElementById('revenueSpan').innerText = window.revenue.toLocaleString();\r\n window.noRngRevenue += noRngRevenueDifference;\r\n document.getElementById('noRngRevenueSpan').innerText = window.noRngRevenue.toLocaleString();\r\n\r\n window.profit = window.revenue - window.expenses;\r\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\r\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\r\n window.noRngProfit = window.noRngRevenue - window.expenses;\r\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\r\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\r\n }\r\n});\r\n\r\nfunction updateTable(tableId, item, price) {\r\n let row = document.querySelector('#' + tableId + ' .' + CSS.escape(item));\r\n if (row == null) {\r\n return 0;\r\n }\r\n\r\n row = row.querySelectorAll('td');\r\n let priceTd = row[1];\r\n let amountTd = row[2];\r\n let totalTd = row[3];\r\n let oldTotal = totalTd.innerText;\r\n let newTotal = price * amountTd.innerText;\r\n\r\n if (priceTd.innerText != price) {\r\n priceTd.innerText = price;\r\n }\r\n totalTd.innerText = newTotal;\r\n\r\n return newTotal - oldTotal;\r\n}\r\n\r\n// #endregion\r\n\r\nfunction initPatchNotes() {\r\n const patchNotesRows = document.getElementById(\"patchNotes\");\r\n for (const pn in patchNote) {\r\n const patchNoteContainer = document.createElement(\"div\");\r\n patchNotesRows.setAttribute('class', 'col-12 mb-4');\r\n\r\n const patchNoteElement = document.createElement(\"h6\");\r\n patchNoteElement.innerHTML = pn;\r\n const patchNoteList = document.createElement(\"ul\");\r\n for (const note of patchNote[pn]) {\r\n const noteElement = document.createElement(\"li\");\r\n noteElement.innerHTML = note;\r\n patchNoteList.appendChild(noteElement);\r\n }\r\n patchNoteContainer.appendChild(patchNoteElement);\r\n patchNoteContainer.appendChild(patchNoteList);\r\n\r\n patchNotesRows.appendChild(patchNoteContainer);\r\n }\r\n}\r\n\r\nfunction initExtraBuffSection() {\r\n // mooPass\r\n let mooPassToggle = document.getElementById(\"mooPassToggle\");\r\n let mooPass = localStorage.getItem('mooPass');\r\n if (mooPass) {\r\n mooPassToggle.checked = Boolean(mooPass);\r\n }\r\n mooPassToggle.onchange = () => {\r\n localStorage.setItem('mooPass', mooPassToggle.checked);\r\n }\r\n \r\n // comExp\r\n let comExpToggle = document.getElementById(\"comExpToggle\");\r\n let comExpInput = document.getElementById(\"comExpInput\");\r\n let comExp = localStorage.getItem('comExp');\r\n if (comExp) {\r\n let comExpNumber = Number(comExp);\r\n if (comExpNumber > 0) {\r\n comExpToggle.checked = true;\r\n comExpInput.value = comExpNumber;\r\n } else {\r\n comExpToggle.checked = false;\r\n comExpInput.disabled = true;\r\n }\r\n }\r\n const updateComExp = () => {\r\n if (comExpToggle.checked) {\r\n let comExp = Number(comExpInput.value);\r\n localStorage.setItem('comExp', comExp); \r\n comExpInput.disabled = false;\r\n } else {\r\n localStorage.setItem('comExp', 0);\r\n comExpInput.disabled = true;\r\n }\r\n }\r\n comExpToggle.onchange = updateComExp;\r\n comExpInput.onchange = updateComExp;\r\n\r\n // comDrop\r\n let comDropToggle = document.getElementById(\"comDropToggle\");\r\n let comDropInput = document.getElementById(\"comDropInput\");\r\n let comDrop = localStorage.getItem('comDrop');\r\n if (comDrop) {\r\n let comDropNumber = Number(comDrop);\r\n if (comDropNumber > 0) {\r\n comDropToggle.checked = true;\r\n comDropInput.value = comDropNumber;\r\n } else {\r\n comDropToggle.checked = false;\r\n comDropInput.disabled = true;\r\n }\r\n }\r\n const updateComDrop = () => {\r\n if (comDropToggle.checked) {\r\n let comDrop = Number(comDropInput.value);\r\n localStorage.setItem('comDrop', comDrop); \r\n comDropInput.disabled = false;\r\n } else {\r\n localStorage.setItem('comDrop', 0);\r\n comDropInput.disabled = true;\r\n }\r\n }\r\n comDropToggle.onchange = updateComDrop;\r\n comDropInput.onchange = updateComDrop;\r\n}\r\n\r\n\r\nfunction updateState() {\r\n updateEquipmentState();\r\n updateLevels();\r\n updateFoodState();\r\n updateDrinksState();\r\n updateAbilityState();\r\n}\r\n\r\nfunction updateUI() {\r\n updateCombatStatsUI();\r\n updateFoodUI();\r\n updateDrinksUI();\r\n updateAbilityUI();\r\n\r\n updateContent();\r\n}\r\n\r\nconst darkModeToggle = document.getElementById('darkModeToggle');\r\nconst body = document.body;\r\n\r\nif (localStorage.getItem('darkModeEnabled') === 'true') {\r\n body.classList.add('dark-mode');\r\n const tables = document.getElementsByClassName('profit-table');\r\n for (const table of tables) {\r\n table.classList.toggle('table-striped');\r\n }\r\n darkModeToggle.checked = true;\r\n}\r\n\r\ndarkModeToggle.addEventListener('change', () => {\r\n body.classList.toggle('dark-mode');\r\n const tables = document.getElementsByClassName('profit-table');\r\n for (const table of tables) {\r\n table.classList.toggle('table-striped');\r\n }\r\n localStorage.setItem('darkModeEnabled', darkModeToggle.checked);\r\n});\r\n\r\nfunction updateContent() {\r\n document.querySelectorAll('[data-i18n]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n');\r\n if (key) {\r\n element.textContent = i18next.t(key);\r\n }\r\n });\r\n\r\n document.querySelectorAll('[data-i18n-placeholder]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n-placeholder');\r\n if (key) {\r\n element.placeholder = i18next.t(key);\r\n }\r\n });\r\n\r\n document.querySelectorAll('option[data-i18n]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n');\r\n if (key) {\r\n element.textContent = i18next.t(key);\r\n }\r\n });\r\n}\r\n\r\ninitEquipmentSection();\r\ninitHouseRoomsModal();\r\ninitAchievementsModal();\r\ninitLevelSection();\r\ninitFoodSection();\r\ninitDrinksSection();\r\ninitAbilitiesSection();\r\ninitZones();\r\ninitDungeons();\r\ninitTriggerModal();\r\ninitSimulationControls();\r\ninitEquipmentSetsModal();\r\ninitErrorHandling();\r\ninitImportExportModal();\r\ninitDamageDoneTaken();\r\ninitPatchNotes();\r\ninitExtraBuffSection();\r\n\r\nupdateState();\r\nupdateUI();\r\n"],"names":[],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"bundle.js","mappings":";;;;;;;;;;;;;;;;;AAA0B;AACkC;AAC5B;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,wDAAgB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,6CAAI;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;ACxNG;AACkD;AACR;AACpE;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,gEAAwB;AAChE;AACA,0CAA0C,4DAAoB;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,WAAW;;;;;;;;;;;;;;AC1B1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,IAAI,EAAC;;;;;;;;;;;;;;;ACdpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wDAAwD;AACtF,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,mBAAmB;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;;AC/gBA;AAC4B;AACtB;AAChC;AACA;AACA;AACA;AACA;AACA,6BAA6B,qDAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;ACtF4B;AACmD;AACzG;AACA;AACA;AACA;AACA,uBAAuB,qDAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,iFAAoC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS,EAAC;;;;;;;;;;;;;;;;;AC/CC;AACsC;AAChE;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,0DAAkB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS;;;;;;;;;;;;;;;;;;;;AC7BQ;AACM;AACA;AACF;AACA;AACI;AACxC;AACA,qBAAqB,mDAAU;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,kDAAS;AACrD;AACA;AACA,qDAAqD,mDAAU;AAC/D,2DAA2D,mDAAU;AACrE,qEAAqE,gDAAO;AAC5E;AACA;AACA,2CAA2C,kDAAS;AACpD;AACA,SAAS;AACT;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,MAAM,EAAC;;;;;;;;;;;;;;;;ACvLsE;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,wEAAgC;AAC5C;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UCjLvB;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;UAEA;UACA;;;;;WCzBA;WACA;WACA;WACA;WACA,yCAAyC,wCAAwC;WACjF;WACA;WACA;;;;;WCPA;WACA;WACA;WACA;WACA;;;;;WCJA;WACA;WACA;WACA;WACA,GAAG;WACH;WACA;WACA,CAAC;;;;;WCPD;;;;;WCAA;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D;;;;;WCNA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;;;;WClBA;;WAEA;WACA;WACA;WACA;WACA;WACA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrBuD;AACN;AAC2B;AACN;AACU;AAC7B;AACM;AACN;AACyD;AACF;AACE;AACA;AAClC;AACc;AACN;AACE;AACF;AACG;AACF;AACnF;AAC0C;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,2FAA4B;AAC5D,qCAAqC,qHAAiC;AACtE;AACA;AACA;AACA,iBAAiB,kEAAM;AACvB;AACA,IAAI,SAAI;AACR,IAAI,WAAM;AACV,IAAI,cAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA,sCAAsC,qEAAa;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,mCAAmC,0EAAkB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,iFAAkB;AAClD;AACA,4FAA4F,eAAe;AAC3G;AACA;AACA;AACA,uDAAuD,eAAe;AACtE,6BAA6B,KAAK,GAAG,MAAM;AAC3C;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,iFAAkB;AAClD;AACA,sCAAsC,6EAAoB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,eAAe;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oEAAoE,eAAe;AACnF,mCAAmC,eAAe;AAClD;AACA;AACA,gGAAgG,eAAe;AAC/G;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,gCAAgC,eAAe;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,iBAAiB;AACpD;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,iDAAiD,iBAAiB;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,qEAAa;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,qEAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,qEAAS;AACvD,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,6EAAoB;AACvD;AACA;AACA,qBAAqB,4EAAmB;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,sCAAsC,qEAAa;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,SAAI;AACZ,YAAY,SAAI,mBAAmB,SAAI;AACvC,2BAA2B,qEAAa,CAAC,SAAI;AAC7C,uBAAuB,SAAI;AAC3B;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,qFAAqF,SAAI;AACzF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,uCAAuC,qEAAa;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,WAAM;AACd,YAAY,WAAM,mBAAmB,WAAM;AAC3C,2BAA2B,qEAAa,CAAC,WAAM;AAC/C,uBAAuB,WAAM;AAC7B;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,sFAAsF,WAAM;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D,UAAU;AACV,0CAA0C,wEAAgB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,cAAS;AACjB,YAAY,cAAS,mBAAmB,cAAS;AACjD,8BAA8B,wEAAgB,CAAC,cAAS;AACxD,uBAAuB,cAAS;AAChC;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,4DAA4D,yFAAgC;AAC5F,2DAA2D,yFAAgC;AAC3F,4DAA4D,yFAAgC,YAAY,cAAS;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAS;AACjC,IAAI,cAAS,iBAAiB,cAAS;AACvC,IAAI,cAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,cAAS;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,SAAI;AAChC;AACA;AACA,4BAA4B,WAAM;AAClC;AACA;AACA,4BAA4B,cAAS;AACrC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,qEAAa;AACrD,MAAM;AACN,wCAAwC,wEAAgB;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,yFAAgC;AAC5C;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,wFAAgC;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,wFAAgC;AACrD;AACA;AACA;AACA,mCAAmC,uFAA+B;AAClE,MAAM;AACN,mCAAmC,uFAA+B;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uFAA+B;AACnD;AACA,qEAAqE,yFAAgC;AACrG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,wEAAe;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2GAA2G,WAAW;AACtH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,WAAW;AAC5C,yDAAyD,WAAW;AACpE;AACA;AACA;AACA,gEAAgE,WAAW;AAC3E;AACA;AACA,qGAAqG,WAAW;AAChH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qFAAqF,WAAW;AAChG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4GAA4G,WAAW;AACvH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,WAAW;AAC7C,0DAA0D,WAAW;AACrE;AACA;AACA;AACA,iEAAiE,WAAW;AAC5E;AACA;AACA,sGAAsG,WAAW;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sFAAsF,WAAW;AACjG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,OAAO;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uBAAuB;AAC3C,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,+EAAsB;AAClC,+BAA+B,+EAAsB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,iJAAiJ;AAC9L;AACA,gBAAgB,+EAAsB;AACtC,mCAAmC,+EAAsB;AACzD;AACA;AACA;AACA,qDAAqD,uIAAuI;AAC5L;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,+BAA+B;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,UAAU,kCAAkC;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uEAAuE,oDAAoD;AAC3H,iEAAiE,oDAAoD;AACrH,iFAAiF,oDAAoD;AACrI;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,eAAe;AACrC;AACA;AACA;AACA;AACA,0BAA0B,mBAAmB;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,SAAS;AAC3D;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,KAAK;AACL,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,iBAAiB;AACrC;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,+CAA+C,kBAAkB;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,+EAAsB;AACvC;AACA;AACA;AACA,SAAS;AACT;AACA,UAAU,kCAAkC,qEAAqE;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2EAA2E,wEAAgB;AAC3F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa,qEAAa;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,qEAAa;AACjC,iCAAiC,qEAAa;AAC9C;AACA,kBAAkB,SAAS,wEAAgB;AAC3C,iCAAiC,wEAAgB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,qEAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kFAAkF,IAAI;AACtF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,QAAQ;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,QAAQ;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wEAAgB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,6BAA6B;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qEAAqE,UAAU;AAC/E;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,sCAAsC,4BAA4B;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,qCAAqC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC,2FAA4B;AAChE;AACA;AACA;AACA;AACA,yCAAyC,qHAAiC;AAC1E;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,SAAI;AACxB,yCAAyC,sEAAU,CAAC,SAAI,gBAAgB,SAAI;AAC5E;AACA,kBAAkB;AAClB;AACA;AACA;AACA,oBAAoB,WAAM;AAC1B,yCAAyC,sEAAU,CAAC,WAAM,gBAAgB,WAAM;AAChF;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,cAAS,mCAAmC,yFAAgC;AAChG;AACA,sCAAsC,mEAAO,CAAC,cAAS,iDAAiD,cAAS;AACjH;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,oDAAoD;AACxE;AACA;AACA;AACA;AACA;AACA,wCAAwC,qHAAiC;AACzE;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;AACA;AACA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,wCAAwC;AACrF,kCAAkC,uDAAuD;AACzF;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,qHAAiC;AAC9E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,KAAK,SAAS,qEAAS;AAC5E;AACA,KAAK;AACL;AACA;AACA;AACA,yBAAyB,sEAAU;AACnC;AACA;AACA;AACA;AACA,0BAA0B,sEAAU;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,mEAAO;AAC1C;AACA;AACA;AACA,mBAAmB,kEAAM;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wEAAwE,UAAU;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,IAAI;AAC7D;AACA;AACA;AACA;AACA;AACA,sCAAsC,wEAAe;AACrD;AACA;AACA;AACA;AACA;AACA,4CAA4C,0BAA0B;AACtE;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,wCAAwC;AACjG,8CAA8C,uDAAuD;AACrG;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,oDAAoD;AACpF;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,WAAW,QAAQ,KAAK;AACzD;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,KAAK;AACxC,oCAAoC,KAAK,YAAY,UAAU;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,wBAAwB,WAAW,WAAW;AACnF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd,qEAAqE,WAAW;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd,sEAAsE,YAAY;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sEAAsE,cAAc,IAAI,YAAY;AACpG;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,YAAY,IAAI,eAAe,GAAG,WAAW;AACxF;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,kCAAkC;AAClC,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB,qBAAqB;AACrB,gBAAgB;AAChB,kBAAkB;AAClB,qBAAqB;AACrB,sBAAsB;AACtB,sBAAsB;AACtB,wBAAwB;AACxB;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,uFAA+B;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,SAAI;AAClB,gBAAgB,WAAM;AACtB,mBAAmB,cAAS;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,qEAAa;AACxC,yBAAyB,qEAAa;AACtC;AACA,wCAAwC,gCAAgC,qEAAa;AACrF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yCAAyC;AACzC;AACA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;AACA;AACA;AACA,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,sBAAsB,4GAAmD;AACzE;AACA,aAAa;AACb;AACA;AACA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,6CAAS;AAC9B;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,6CAAS;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sources":["webpack://mwicombatsimulator/./src/combatsimulator/ability.js","webpack://mwicombatsimulator/./src/combatsimulator/achievement.js","webpack://mwicombatsimulator/./src/combatsimulator/buff.js","webpack://mwicombatsimulator/./src/combatsimulator/combatUnit.js","webpack://mwicombatsimulator/./src/combatsimulator/consumable.js","webpack://mwicombatsimulator/./src/combatsimulator/equipment.js","webpack://mwicombatsimulator/./src/combatsimulator/houseRoom.js","webpack://mwicombatsimulator/./src/combatsimulator/player.js","webpack://mwicombatsimulator/./src/combatsimulator/trigger.js","webpack://mwicombatsimulator/webpack/bootstrap","webpack://mwicombatsimulator/webpack/runtime/define property getters","webpack://mwicombatsimulator/webpack/runtime/get javascript chunk filename","webpack://mwicombatsimulator/webpack/runtime/global","webpack://mwicombatsimulator/webpack/runtime/hasOwnProperty shorthand","webpack://mwicombatsimulator/webpack/runtime/make namespace object","webpack://mwicombatsimulator/webpack/runtime/publicPath","webpack://mwicombatsimulator/webpack/runtime/jsonp chunk loading","webpack://mwicombatsimulator/./src/main.js"],"sourcesContent":["import Buff from \"./buff\";\r\nimport abilityDetailMap from \"./data/abilityDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nconst abilityFromCombatStat = {\r\n \"blaze\":\r\n {\r\n \"hrid\": \"/abilities/blaze\",\r\n \"name\": \"Blaze\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"allEnemies\",\r\n \"effectType\": \"/ability_effect_types/damage\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"/damage_types/fire\",\r\n \"baseDamageFlat\": 0,\r\n \"baseDamageFlatLevelBonus\": 0.0,\r\n \"baseDamageRatio\": 0.3,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/number_of_active_units\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n },\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/current_hp\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n }\r\n ],\r\n },\r\n \"bloom\":\r\n {\r\n \"hrid\": \"/abilities/bloom\",\r\n \"name\": \"Bloom\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"lowestHpAlly\",\r\n \"effectType\": \"/ability_effect_types/heal\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"\",\r\n \"baseDamageFlat\": 10,\r\n \"baseDamageFlatLevelBonus\": 0,\r\n \"baseDamageRatio\": 0.15,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_allies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/lowest_hp_percentage\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/less_than_equal\",\r\n \"value\": 100\r\n }\r\n ],\r\n }\r\n}\r\n\r\nclass Ability {\r\n constructor(hrid, level = 1, triggers = null) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameAbility = abilityDetailMap[hrid];\r\n if (!gameAbility) {\r\n gameAbility = abilityFromCombatStat[hrid];\r\n }\r\n if (!gameAbility) {\r\n throw new Error(\"No ability found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.manaCost = gameAbility.manaCost;\r\n this.cooldownDuration = gameAbility.cooldownDuration;\r\n this.castDuration = gameAbility.castDuration;\r\n this.isSpecialAbility = gameAbility.isSpecialAbility;\r\n\r\n this.abilityEffects = [];\r\n\r\n for (const effect of gameAbility.abilityEffects) {\r\n let abilityEffect = {\r\n targetType: effect.targetType,\r\n effectType: effect.effectType,\r\n combatStyleHrid: effect.combatStyleHrid,\r\n damageType: effect.damageType,\r\n damageFlat: effect.baseDamageFlat + (this.level - 1) * effect.baseDamageFlatLevelBonus,\r\n damageRatio: effect.baseDamageRatio + (this.level - 1) * effect.baseDamageRatioLevelBonus,\r\n bonusAccuracyRatio: effect.bonusAccuracyRatio + (this.level - 1) * effect.bonusAccuracyRatioLevelBonus,\r\n damageOverTimeRatio: effect.damageOverTimeRatio,\r\n damageOverTimeDuration: effect.damageOverTimeDuration,\r\n armorDamageRatio: effect.armorDamageRatio + (this.level - 1) * effect.armorDamageRatioLevelBonus,\r\n hpDrainRatio: effect.hpDrainRatio,\r\n pierceChance: effect.pierceChance,\r\n blindChance: effect.blindChance,\r\n blindDuration: effect.blindDuration,\r\n silenceChance: effect.silenceChance,\r\n silenceDuration: effect.silenceDuration,\r\n stunChance: effect.stunChance,\r\n stunDuration: effect.stunDuration,\r\n spendHpRatio: effect.spendHpRatio,\r\n buffs: null,\r\n };\r\n if (effect.buffs) {\r\n abilityEffect.buffs = [];\r\n for (const buff of effect.buffs) {\r\n abilityEffect.buffs.push(new Buff(buff, this.level));\r\n }\r\n }\r\n this.abilityEffects.push(abilityEffect);\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameAbility.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let ability = new Ability(dto.hrid, dto.level, triggers);\r\n\r\n return ability;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n\r\n if (source.isSilenced) {\r\n return false;\r\n }\r\n\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = this.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Ability;\r\n","import Buff from \"./buff\";\r\nimport achievementTierDetailMap from \"./data/achievementTierDetailMap.json\";\r\nimport achievementDetailMap from \"./data/achievementDetailMap.json\";\r\n\r\nclass Achievement {\r\n constructor(achievements) {\r\n this.achievements = achievements;\r\n this.buffs = [];\r\n\r\n for(const tier of Object.values(achievementTierDetailMap)) {\r\n let isGetAll = true;\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid)\r\n for(const achievement of Object.values(detailMap)) {\r\n if(!this.achievements[achievement.hrid] || this.achievements[achievement.hrid] == false) {\r\n isGetAll = false;\r\n break;\r\n }\r\n }\r\n if(isGetAll) {\r\n let buff = new Buff(tier.buff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default Achievement;","class Buff {\r\n startTime;\r\n\r\n constructor(buff, level = 1) {\r\n this.uniqueHrid = buff.uniqueHrid;\r\n this.typeHrid = buff.typeHrid;\r\n this.ratioBoost = buff.ratioBoost + (level - 1) * buff.ratioBoostLevelBonus;\r\n this.flatBoost = buff.flatBoost + (level - 1) * buff.flatBoostLevelBonus;\r\n this.duration = buff.duration;\r\n this.multiplierForSkillHrid = buff.multiplierForSkillHrid ?? \"\";\r\n this.multiplierPerSkillLevel = buff.multiplierPerSkillLevel ?? 0;\r\n }\r\n}\r\n\r\nexport default Buff;\r\n","class CombatUnit {\r\n isPlayer;\r\n isStunned = false;\r\n stunExpireTime = null;\r\n isBlinded = false;\r\n blindExpireTime = null;\r\n isSilenced = false;\r\n silenceExpireTime = null;\r\n\r\n isOutOfMana = false;\r\n\r\n // Base levels which don't change after initialization\r\n staminaLevel = 1;\r\n intelligenceLevel = 1;\r\n attackLevel = 1;\r\n meleeLevel = 1;\r\n defenseLevel = 1;\r\n rangedLevel = 1;\r\n magicLevel = 1;\r\n\r\n experience = 0;\r\n experienceRate = 0;\r\n enrageTime = 0;\r\n\r\n abilities = [null, null, null, null];\r\n food = [null, null, null];\r\n drinks = [null, null, null];\r\n houseRooms = [];\r\n achievements = null;\r\n dropTable = [];\r\n rareDropTable = [];\r\n abilityManaCosts = new Map();\r\n\r\n // Calculated combat stats including temporary buffs\r\n combatDetails = {\r\n staminaLevel: 1,\r\n intelligenceLevel: 1,\r\n attackLevel: 1,\r\n meleeLevel: 1,\r\n defenseLevel: 1,\r\n rangedLevel: 1,\r\n magicLevel: 1,\r\n maxHitpoints: 110,\r\n currentHitpoints: 110,\r\n maxManapoints: 110,\r\n currentManapoints: 110,\r\n stabAccuracyRating: 11,\r\n slashAccuracyRating: 11,\r\n smashAccuracyRating: 11,\r\n rangedAccuracyRating: 11,\r\n magicAccuracyRating: 11,\r\n stabMaxDamage: 11,\r\n slashMaxDamage: 11,\r\n smashMaxDamage: 11,\r\n rangedMaxDamage: 11,\r\n magicMaxDamage: 11,\r\n stabEvasionRating: 11,\r\n slashEvasionRating: 11,\r\n smashEvasionRating: 11,\r\n rangedEvasionRating: 11,\r\n magicEvasionRating: 11,\r\n defensiveMaxDamage: 0,\r\n totalArmor: 0.2,\r\n totalWaterResistance: 0.4,\r\n totalNatureResistance: 0.4,\r\n totalFireResistance: 0.4,\r\n abilityHaste: 0,\r\n tenacity: 0,\r\n totalThreat: 100,\r\n combatStats: {\r\n combatStyleHrid: \"/combat_styles/smash\",\r\n damageType: \"/damage_types/physical\",\r\n attackInterval: 3000000000,\r\n autoAttackDamage: 0,\r\n abilityDamage: 0,\r\n criticalRate: 0,\r\n criticalDamage: 0,\r\n stabAccuracy: 0,\r\n slashAccuracy: 0,\r\n smashAccuracy: 0,\r\n rangedAccuracy: 0,\r\n magicAccuracy: 0,\r\n stabDamage: 0,\r\n slashDamage: 0,\r\n smashDamage: 0,\r\n rangedDamage: 0,\r\n magicDamage: 0,\r\n defensiveDamage: 0,\r\n taskDamage: 0,\r\n physicalAmplify: 0,\r\n waterAmplify: 0,\r\n natureAmplify: 0,\r\n fireAmplify: 0,\r\n healingAmplify: 0,\r\n physicalThorns: 0,\r\n elementalThorns: 0,\r\n maxHitpoints: 0,\r\n maxManapoints: 0,\r\n stabEvasion: 0,\r\n slashEvasion: 0,\r\n smashEvasion: 0,\r\n rangedEvasion: 0,\r\n magicEvasion: 0,\r\n armor: 0,\r\n waterResistance: 0,\r\n natureResistance: 0,\r\n fireResistance: 0,\r\n lifeSteal: 0,\r\n hpRegenPer10: 0.01,\r\n mpRegenPer10: 0.01,\r\n combatDropRate: 0,\r\n combatDropQuantity: 0,\r\n combatRareFind: 0,\r\n combatExperience: 0,\r\n foodSlots: 1,\r\n drinkSlots: 1,\r\n armorPenetration: 0,\r\n waterPenetration: 0,\r\n naturePenetration: 0,\r\n firePenetration: 0,\r\n manaLeech: 0,\r\n castSpeed: 0,\r\n threat: 100,\r\n parry: 0,\r\n mayhem: 0,\r\n pierce: 0,\r\n curse: 0,\r\n ripple: 0,\r\n bloom: 0,\r\n blaze: 0,\r\n weaken: 0,\r\n fury: 0,\r\n foodHaste: 0,\r\n drinkConcentration: 0,\r\n damageTaken: 0,\r\n attackSpeed: 0,\r\n armorDamageRatio: 0,\r\n hpDrainRatio: 0,\r\n primaryTraining: \"\",\r\n focusTraining: \"\",\r\n staminaExperience: 0,\r\n intelligenceExperience: 0,\r\n attackExperience: 0,\r\n defenseExperience: 0,\r\n meleeExperience: 0,\r\n rangedExperience: 0,\r\n magicExperience: 0,\r\n retaliation: 0,\r\n },\r\n };\r\n combatBuffs = {};\r\n permanentBuffs = {};\r\n zoneBuffs = {};\r\n extraBuffs = {};\r\n\r\n constructor() { }\r\n\r\n updateCombatDetails() {\r\n if (this.isPlayer) {\r\n if (this.combatDetails.combatStats.hpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01 + this.combatDetails.combatStats.hpRegenPer10;\r\n }\r\n if (this.combatDetails.combatStats.mpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01 + this.combatDetails.combatStats.mpRegenPer10;\r\n }\r\n }\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((stat) => {\r\n this.combatDetails[stat + \"Level\"] = this[stat + \"Level\"];\r\n let boosts = this.getBuffBoosts(\"/buff_types/\" + stat + \"_level\");\r\n boosts.forEach((buff) => {\r\n this.combatDetails[stat + \"Level\"] += (this[stat + \"Level\"] * buff.ratioBoost);\r\n this.combatDetails[stat + \"Level\"] += buff.flatBoost;\r\n });\r\n });\r\n\r\n this.combatDetails.maxHitpoints = Math.floor\r\n (10 * (10 + this.combatDetails.staminaLevel) + this.combatDetails.combatStats.maxHitpoints);\r\n this.combatDetails.maxManapoints = Math.floor\r\n (10 * (10 + this.combatDetails.intelligenceLevel) + this.combatDetails.combatStats.maxManapoints);\r\n\r\n let accuracyRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_accuracy\").ratioBoost;\r\n let damageRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_damage\").ratioBoost;\r\n // if (accuracyRatioBoostFromFury > 0) {\r\n // console.log(\"Fury Boost: \" + accuracyRatioBoostFromFury);\r\n // }\r\n\r\n let accuracyRatioBoost = this.getBuffBoost(\"/buff_types/accuracy\").ratioBoost;\r\n let damageRatioBoost = this.getBuffBoost(\"/buff_types/damage\").ratioBoost;\r\n\r\n [\"stab\", \"slash\", \"smash\"].forEach((style) => {\r\n this.combatDetails[style + \"AccuracyRating\"] =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Accuracy\"]) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails[style + \"MaxDamage\"] =\r\n (10 + this.combatDetails.meleeLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Damage\"]) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n let baseEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats[style + \"Evasion\"]);\r\n this.combatDetails[style + \"EvasionRating\"] = baseEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails[style + \"EvasionRating\"] += boost.flatBoost;\r\n this.combatDetails[style + \"EvasionRating\"] += baseEvasion * boost.ratioBoost;\r\n }\r\n });\r\n\r\n this.combatDetails.defensiveMaxDamage = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.defensiveDamage);\r\n\r\n // when equiped bulwark\r\n if (this.equipment?.['/equipment_types/two_hand']?.hrid.includes(\"bulwark\")) {\r\n this.combatDetails.smashMaxDamage += this.combatDetails.defensiveMaxDamage;\r\n }\r\n\r\n this.combatDetails.rangedAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.rangedAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.rangedMaxDamage =\r\n (10 + this.combatDetails.rangedLevel) *\r\n (1 + this.combatDetails.combatStats.rangedDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseRangedEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.rangedEvasion);\r\n this.combatDetails.rangedEvasionRating = baseRangedEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.rangedEvasionRating += boost.flatBoost;\r\n this.combatDetails.rangedEvasionRating += baseRangedEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.damageTaken = this.getBuffBoost(\"/buff_types/damage_taken\").flatBoost;\r\n // if (this.combatDetails.combatStats.damageTaken > 0) {\r\n // console.log(\"Damage taken: \" + this.combatDetails.combatStats.damageTaken);\r\n // }\r\n\r\n this.combatDetails.magicAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.magicAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.magicMaxDamage =\r\n (10 + this.combatDetails.magicLevel) *\r\n (1 + this.combatDetails.combatStats.magicDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseMagicEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.magicEvasion);\r\n this.combatDetails.magicEvasionRating = baseMagicEvasion;\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.magicEvasionRating += boost.flatBoost;\r\n this.combatDetails.magicEvasionRating += baseMagicEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.physicalAmplify += this.getBuffBoost(\"/buff_types/physical_amplify\").flatBoost;\r\n this.combatDetails.combatStats.waterAmplify += this.getBuffBoost(\"/buff_types/water_amplify\").flatBoost;\r\n this.combatDetails.combatStats.natureAmplify += this.getBuffBoost(\"/buff_types/nature_amplify\").flatBoost;\r\n this.combatDetails.combatStats.fireAmplify += this.getBuffBoost(\"/buff_types/fire_amplify\").flatBoost;\r\n\r\n this.combatDetails.combatStats.attackInterval /= (1 + (this.combatDetails.attackLevel / 2000));\r\n\r\n let baseAttackSpeed = this.combatDetails.combatStats.attackSpeed;\r\n this.combatDetails.combatStats.attackInterval /= (1 + baseAttackSpeed);\r\n let attackIntervalBoosts = this.getBuffBoosts(\"/buff_types/attack_speed\");\r\n let attackIntervalRatioBoost = attackIntervalBoosts\r\n .map((boost) => boost.ratioBoost)\r\n .reduce((prev, cur) => prev + cur, 0);\r\n this.combatDetails.combatStats.attackInterval /= (1 + attackIntervalRatioBoost);\r\n\r\n let baseArmor = 0.2 * this.combatDetails.defenseLevel + this.combatDetails.combatStats.armor;\r\n this.combatDetails.totalArmor = baseArmor;\r\n let armorBoosts = this.getBuffBoosts(\"/buff_types/armor\");\r\n for (const boost of armorBoosts) {\r\n this.combatDetails.totalArmor += boost.flatBoost;\r\n this.combatDetails.totalArmor += baseArmor * boost.ratioBoost;\r\n }\r\n\r\n let baseWaterResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.waterResistance;\r\n this.combatDetails.totalWaterResistance = baseWaterResistance;\r\n let waterResistanceBoosts = this.getBuffBoosts(\"/buff_types/water_resistance\");\r\n for (const boost of waterResistanceBoosts) {\r\n this.combatDetails.totalWaterResistance += boost.flatBoost;\r\n this.combatDetails.totalWaterResistance += baseWaterResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseNatureResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.natureResistance;\r\n this.combatDetails.totalNatureResistance = baseNatureResistance;\r\n let natureResistanceBoosts = this.getBuffBoosts(\"/buff_types/nature_resistance\");\r\n for (const boost of natureResistanceBoosts) {\r\n this.combatDetails.totalNatureResistance += boost.flatBoost;\r\n this.combatDetails.totalNatureResistance += baseNatureResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseFireResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.fireResistance;\r\n this.combatDetails.totalFireResistance = baseFireResistance;\r\n let fireResistanceBoosts = this.getBuffBoosts(\"/buff_types/fire_resistance\");\r\n for (const boost of fireResistanceBoosts) {\r\n this.combatDetails.totalFireResistance += boost.flatBoost;\r\n this.combatDetails.totalFireResistance += baseFireResistance * boost.ratioBoost;\r\n }\r\n\r\n let hpRegenBoosts = this.getBuffBoost(\"/buff_types/hp_regen\");\r\n this.combatDetails.combatStats.hpRegenPer10 += this.combatDetails.combatStats.hpRegenPer10 * hpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.hpRegenPer10 += hpRegenBoosts.flatBoost;\r\n\r\n let mpRegenBoosts = this.getBuffBoost(\"/buff_types/mp_regen\");\r\n this.combatDetails.combatStats.mpRegenPer10 += this.combatDetails.combatStats.mpRegenPer10 * mpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.mpRegenPer10 += mpRegenBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.lifeSteal += this.getBuffBoost(\"/buff_types/life_steal\").flatBoost;\r\n this.combatDetails.combatStats.physicalThorns += this.getBuffBoost(\r\n \"/buff_types/physical_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.elementalThorns += this.getBuffBoost(\r\n \"/buff_types/elemental_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.combatExperience += this.getBuffBoost(\"/buff_types/wisdom\").flatBoost;\r\n this.combatDetails.combatStats.criticalRate += this.getBuffBoost(\"/buff_types/critical_rate\").flatBoost;\r\n this.combatDetails.combatStats.criticalDamage += this.getBuffBoost(\"/buff_types/critical_damage\").flatBoost;\r\n\r\n this.combatDetails.combatStats.castSpeed += this.getBuffBoost(\"/buff_types/cast_speed\").flatBoost;\r\n this.combatDetails.combatStats.castSpeed += this.combatDetails[\"attackLevel\"] / 2000;\r\n\r\n let combatDropRateBoosts = this.getBuffBoost(\"/buff_types/combat_drop_rate\");\r\n this.combatDetails.combatStats.combatDropRate += (1 + this.combatDetails.combatStats.combatDropRate) * combatDropRateBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropRate += combatDropRateBoosts.flatBoost;\r\n let combatRareFindBoosts = this.getBuffBoost(\"/buff_types/rare_find\");\r\n this.combatDetails.combatStats.combatRareFind += (1 + this.combatDetails.combatStats.combatRareFind) * combatRareFindBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatRareFind += combatRareFindBoosts.flatBoost;\r\n let combatDropQuantityBoosts = this.getBuffBoost(\"/buff_types/combat_drop_quantity\");\r\n this.combatDetails.combatStats.combatDropQuantity += (1 + this.combatDetails.combatStats.combatDropQuantity) * combatDropQuantityBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropQuantity += combatDropQuantityBoosts.flatBoost;\r\n\r\n let baseThreat = 100 + this.combatDetails.combatStats.threat;\r\n this.combatDetails.totalThreat = baseThreat;\r\n let threatBoosts = this.getBuffBoost(\"/buff_types/threat\");\r\n if (threatBoosts.ratioBoost !== 0) {\r\n this.combatDetails.combatStats.threat += baseThreat * threatBoosts.ratioBoost;\r\n } else {\r\n this.combatDetails.combatStats.threat = baseThreat;\r\n }\r\n this.combatDetails.combatStats.threat += threatBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.retaliation += this.getBuffBoost(\"/buff_types/retaliation\").flatBoost;\r\n }\r\n\r\n addBuff(buff, currentTime) {\r\n buff.startTime = currentTime;\r\n this.combatBuffs[buff.uniqueHrid] = buff;\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n removeBuff(buff) {\r\n if (!this.combatBuffs[buff.uniqueHrid]) {\r\n return;\r\n }\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n addPermanentBuff(buff) {\r\n if (this.permanentBuffs[buff.typeHrid]) {\r\n this.permanentBuffs[buff.typeHrid].flatBoost += buff.flatBoost;\r\n this.permanentBuffs[buff.typeHrid].ratioBoost += buff.ratioBoost;\r\n } else {\r\n this.permanentBuffs[buff.typeHrid] = buff;\r\n }\r\n }\r\n\r\n generatePermanentBuffs() {\r\n for (let i = 0; i < this.houseRooms.length; i++) {\r\n const houseRoom = this.houseRooms[i];\r\n houseRoom.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n\r\n if (this.achievements) {\r\n this.achievements.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.zoneBuffs) {\r\n this.zoneBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.extraBuffs) {\r\n this.extraBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n }\r\n\r\n removeExpiredBuffs(currentTime) {\r\n let expiredBuffs = Object.values(this.combatBuffs).filter(\r\n (buff) => buff.startTime + buff.duration <= currentTime\r\n );\r\n expiredBuffs.forEach((buff) => {\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n });\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearBuffs() {\r\n this.combatBuffs = structuredClone(this.permanentBuffs);\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearCCs() {\r\n this.isStunned = false;\r\n this.stunExpireTime = null;\r\n this.isSilenced = false;\r\n this.silenceExpireTime = null;\r\n this.isBlinded = false;\r\n this.blindExpireTime = null;\r\n this.combatDetails.combatStats.damageTaken = 0;\r\n }\r\n\r\n getBuffBoosts(type) {\r\n let boosts = [];\r\n Object.values(this.combatBuffs)\r\n .filter((buff) => buff.typeHrid == type)\r\n .forEach((buff) => {\r\n boosts.push({ ratioBoost: buff.ratioBoost, flatBoost: buff.flatBoost });\r\n });\r\n\r\n return boosts;\r\n }\r\n\r\n getBuffBoost(type) {\r\n let boosts = this.getBuffBoosts(type);\r\n\r\n let boost = {\r\n ratioBoost: 0,\r\n flatBoost: 0,\r\n };\r\n\r\n for (let i = 0; i < boosts.length; i++) {\r\n boost.ratioBoost += boosts[i]?.ratioBoost ?? 0;\r\n boost.flatBoost += boosts[i]?.flatBoost ?? 0;\r\n }\r\n\r\n return boost;\r\n }\r\n\r\n reset(currentTime = 0) {\r\n this.clearCCs();\r\n this.clearBuffs();\r\n this.updateCombatDetails();\r\n this.resetCooldowns(currentTime);\r\n\r\n this.combatDetails.currentHitpoints = this.combatDetails.maxHitpoints;\r\n this.combatDetails.currentManapoints = this.combatDetails.maxManapoints;\r\n }\r\n\r\n resetCooldowns(currentTime = 0) {\r\n this.food.filter((food) => food != null).forEach((food) => (food.lastUsed = Number.MIN_SAFE_INTEGER));\r\n this.drinks.filter((drink) => drink != null).forEach((drink) => (drink.lastUsed = Number.MIN_SAFE_INTEGER));\r\n\r\n let haste = this.combatDetails.combatStats.abilityHaste;\r\n\r\n this.abilities\r\n .filter((ability) => ability != null)\r\n .forEach((ability) => {\r\n if (this.isPlayer) {\r\n ability.lastUsed = Number.MIN_SAFE_INTEGER;\r\n } else {\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n ability.lastUsed = currentTime - Math.floor(cooldownDuration * 0.5) + Math.floor(Math.random() * cooldownDuration * 0.5);\r\n }\r\n });\r\n }\r\n\r\n addHitpoints(hitpoints) {\r\n let hitpointsAdded = 0;\r\n\r\n if (this.combatDetails.currentHitpoints >= this.combatDetails.maxHitpoints) {\r\n return hitpointsAdded;\r\n }\r\n\r\n let newHitpoints = Math.min(this.combatDetails.currentHitpoints + hitpoints, this.combatDetails.maxHitpoints);\r\n hitpointsAdded = newHitpoints - this.combatDetails.currentHitpoints;\r\n this.combatDetails.currentHitpoints = newHitpoints;\r\n\r\n return hitpointsAdded;\r\n }\r\n\r\n addManapoints(manapoints) {\r\n let manapointsAdded = 0;\r\n\r\n if (this.combatDetails.currentManapoints >= this.combatDetails.maxManapoints) {\r\n return manapointsAdded;\r\n }\r\n\r\n let newManapoints = Math.min(\r\n this.combatDetails.currentManapoints + manapoints,\r\n this.combatDetails.maxManapoints\r\n );\r\n manapointsAdded = newManapoints - this.combatDetails.currentManapoints;\r\n this.combatDetails.currentManapoints = newManapoints;\r\n\r\n return manapointsAdded;\r\n }\r\n}\r\n\r\nexport default CombatUnit;\r\n","import Buff from \"./buff\";\r\nimport itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nclass Consumable {\r\n constructor(hrid, triggers = null) {\r\n this.hrid = hrid;\r\n\r\n let gameConsumable = itemDetailMap[this.hrid];\r\n if (!gameConsumable) {\r\n throw new Error(\"No consumable found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.cooldownDuration = gameConsumable.consumableDetail.cooldownDuration;\r\n this.hitpointRestore = gameConsumable.consumableDetail.hitpointRestore;\r\n this.manapointRestore = gameConsumable.consumableDetail.manapointRestore;\r\n this.recoveryDuration = gameConsumable.consumableDetail.recoveryDuration;\r\n this.catagoryHrid = gameConsumable.categoryHrid;\r\n\r\n this.buffs = [];\r\n if (gameConsumable.consumableDetail.buffs) {\r\n for (const consumableBuff of gameConsumable.consumableDetail.buffs) {\r\n let buff = new Buff(consumableBuff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameConsumable.consumableDetail.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let consumable = new Consumable(dto.hrid, triggers);\r\n\r\n return consumable;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n let consumableHaste;\r\n if (this.catagoryHrid.includes(\"food\")) {\r\n consumableHaste = source.combatDetails.combatStats.foodHaste\r\n } else {\r\n consumableHaste = source.combatDetails.combatStats.drinkConcentration;\r\n }\r\n let cooldownDuration = this.cooldownDuration;\r\n if (consumableHaste > 0) {\r\n cooldownDuration = cooldownDuration / (1 + consumableHaste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Consumable;\r\n","import itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport enhancementLevelTotalMultiplierTable from \"./data/enhancementLevelTotalBonusMultiplierTable.json\";\r\n\r\nclass Equipment {\r\n constructor(hrid, enhancementLevel) {\r\n this.hrid = hrid;\r\n let gameItem = itemDetailMap[this.hrid];\r\n if (!gameItem) {\r\n throw new Error(\"No equipment found for hrid: \" + this.hrid);\r\n }\r\n this.gameItem = gameItem;\r\n this.enhancementLevel = enhancementLevel;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let equipment = new Equipment(dto.hrid, dto.enhancementLevel);\r\n\r\n return equipment;\r\n }\r\n\r\n getCombatStat(combatStat) {\r\n let multiplier = enhancementLevelTotalMultiplierTable[this.enhancementLevel];\r\n if(this.gameItem.equipmentDetail.combatStats[combatStat]) {\r\n let enhancementBonus = this.gameItem.equipmentDetail.combatEnhancementBonuses[combatStat] || 0;\r\n let stat = this.gameItem.equipmentDetail.combatStats[combatStat] + multiplier * enhancementBonus;\r\n return stat;\r\n }\r\n return 0;\r\n }\r\n\r\n getCombatStyle() {\r\n return this.gameItem.equipmentDetail.combatStats.combatStyleHrids[0];\r\n }\r\n\r\n getDamageType() {\r\n return this.gameItem.equipmentDetail.combatStats.damageType;\r\n }\r\n\r\n getPrimaryTraining() {\r\n return this.gameItem.equipmentDetail.combatStats.primaryTraining;\r\n }\r\n\r\n getFocusTraining(){\r\n return this.gameItem.equipmentDetail.combatStats.focusTraining;\r\n }\r\n}\r\n\r\nexport default Equipment;\r\n","import Buff from \"./buff\";\r\nimport houseRoomDetailMap from \"./data/houseRoomDetailMap.json\";\r\n\r\nclass HouseRoom {\r\n constructor(hrid, level) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameHouseRoom = houseRoomDetailMap[this.hrid];\r\n if (!gameHouseRoom) {\r\n throw new Error(\"No house room found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.buffs = [];\r\n if (gameHouseRoom.actionBuffs) {\r\n for (const actionBuff of gameHouseRoom.actionBuffs) {\r\n let buff = new Buff(actionBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n if (gameHouseRoom.globalBuffs) {\r\n for (const globalBuff of gameHouseRoom.globalBuffs) {\r\n let buff = new Buff(globalBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default HouseRoom;","import Ability from \"./ability\";\r\nimport CombatUnit from \"./combatUnit\";\r\nimport Consumable from \"./consumable\";\r\nimport Equipment from \"./equipment\";\r\nimport HouseRoom from \"./houseRoom\";\r\nimport Achievement from \"./achievement\";\r\n\r\nclass Player extends CombatUnit {\r\n equipment = {\r\n \"/equipment_types/head\": null,\r\n \"/equipment_types/body\": null,\r\n \"/equipment_types/legs\": null,\r\n \"/equipment_types/feet\": null,\r\n \"/equipment_types/hands\": null,\r\n \"/equipment_types/main_hand\": null,\r\n \"/equipment_types/two_hand\": null,\r\n \"/equipment_types/off_hand\": null,\r\n \"/equipment_types/pouch\": null,\r\n \"/equipment_types/back\": null,\r\n };\r\n\r\n constructor() {\r\n super();\r\n\r\n this.isPlayer = true;\r\n this.hrid = \"player\";\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let player = new Player();\r\n\r\n player.staminaLevel = dto.staminaLevel;\r\n player.intelligenceLevel = dto.intelligenceLevel;\r\n player.attackLevel = dto.attackLevel;\r\n player.meleeLevel = dto.meleeLevel;\r\n player.defenseLevel = dto.defenseLevel;\r\n player.rangedLevel = dto.rangedLevel;\r\n player.magicLevel = dto.magicLevel;\r\n\r\n player.hrid = dto.hrid;\r\n\r\n for (const [key, value] of Object.entries(dto.equipment)) {\r\n player.equipment[key] = value ? Equipment.createFromDTO(value) : null;\r\n }\r\n\r\n player.food = dto.food.map((food) => (food ? Consumable.createFromDTO(food) : null));\r\n player.drinks = dto.drinks.map((drink) => (drink ? Consumable.createFromDTO(drink) : null));\r\n player.abilities = dto.abilities.map((ability) => (ability ? Ability.createFromDTO(ability) : null));\r\n Object.entries(dto.houseRooms).forEach(houseRoom => {\r\n if (houseRoom[1] > 0) {\r\n player.houseRooms.push(new HouseRoom(houseRoom[0], houseRoom[1]))\r\n }\r\n });\r\n\r\n player.achievements = new Achievement(dto.achievements);\r\n\r\n player.debuffOnLevelGap = dto.debuffOnLevelGap;\r\n\r\n return player;\r\n }\r\n\r\n updateCombatDetails() {\r\n if (this.equipment[\"/equipment_types/main_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/main_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/main_hand\"].getPrimaryTraining();\r\n } else if (this.equipment[\"/equipment_types/two_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/two_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/two_hand\"].getPrimaryTraining();\r\n } else {\r\n this.combatDetails.combatStats.combatStyleHrid = \"/combat_styles/smash\";\r\n this.combatDetails.combatStats.damageType = \"/damage_types/physical\";\r\n this.combatDetails.combatStats.attackInterval = 3000000000;\r\n this.combatDetails.combatStats.primaryTraining = \"/skills/melee\";\r\n }\r\n\r\n if (this.equipment[\"/equipment_types/charm\"]) {\r\n this.combatDetails.combatStats.focusTraining = this.equipment[\"/equipment_types/charm\"].getFocusTraining();\r\n } else {\r\n this.combatDetails.combatStats.focusTraining = \"\";\r\n }\r\n\r\n [\r\n \"stabAccuracy\",\r\n \"slashAccuracy\",\r\n \"smashAccuracy\",\r\n \"rangedAccuracy\",\r\n \"magicAccuracy\",\r\n \"stabDamage\",\r\n \"slashDamage\",\r\n \"smashDamage\",\r\n \"rangedDamage\",\r\n \"magicDamage\",\r\n \"defensiveDamage\",\r\n \"taskDamage\",\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"stabEvasion\",\r\n \"slashEvasion\",\r\n \"smashEvasion\",\r\n \"rangedEvasion\",\r\n \"magicEvasion\",\r\n \"armor\",\r\n \"waterResistance\",\r\n \"natureResistance\",\r\n \"fireResistance\",\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"combatDropRate\",\r\n \"combatRareFind\",\r\n \"combatDropQuantity\",\r\n \"combatExperience\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"abilityHaste\",\r\n \"tenacity\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"threat\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"foodHaste\",\r\n \"drinkConcentration\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\",\r\n \"retaliation\"\r\n ].forEach((stat) => {\r\n this.combatDetails.combatStats[stat] = Object.values(this.equipment)\r\n .filter((equipment) => equipment != null)\r\n .map((equipment) => equipment.getCombatStat(stat))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n });\r\n\r\n if (this.equipment[\"/equipment_types/pouch\"]) {\r\n this.combatDetails.combatStats.foodSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"foodSlots\");\r\n this.combatDetails.combatStats.drinkSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"drinkSlots\");\r\n } else {\r\n this.combatDetails.combatStats.foodSlots = 1;\r\n this.combatDetails.combatStats.drinkSlots = 1;\r\n }\r\n\r\n super.updateCombatDetails();\r\n }\r\n}\r\n\r\nexport default Player;\r\n","import combatTriggerDependencyDetailMap from \"./data/combatTriggerDependencyDetailMap.json\";\r\n\r\nclass Trigger {\r\n constructor(dependencyHrid, conditionHrid, comparatorHrid, value = 0) {\r\n this.dependencyHrid = dependencyHrid;\r\n this.conditionHrid = conditionHrid;\r\n this.comparatorHrid = comparatorHrid;\r\n this.value = value;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let trigger = new Trigger(dto.dependencyHrid, dto.conditionHrid, dto.comparatorHrid, dto.value);\r\n\r\n return trigger;\r\n }\r\n\r\n isActive(source, target, friendlies, enemies, currentTime) {\r\n if (combatTriggerDependencyDetailMap[this.dependencyHrid].isSingleTarget) {\r\n return this.isActiveSingleTarget(source, target, currentTime);\r\n } else {\r\n return this.isActiveMultiTarget(friendlies, enemies, currentTime);\r\n }\r\n }\r\n\r\n isActiveSingleTarget(source, target, currentTime) {\r\n let dependencyValue;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/self\":\r\n dependencyValue = this.getDependencyValue(source, currentTime);\r\n break;\r\n case \"/combat_trigger_dependencies/targeted_enemy\":\r\n if (!target) {\r\n return false;\r\n }\r\n dependencyValue = this.getDependencyValue(target, currentTime);\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n isActiveMultiTarget(friendlies, enemies, currentTime) {\r\n let dependency;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/all_allies\":\r\n dependency = friendlies;\r\n break;\r\n case \"/combat_trigger_dependencies/all_enemies\":\r\n if (!enemies) {\r\n return false;\r\n }\r\n dependency = enemies;\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n let dependencyValue;\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/number_of_active_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints > 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/number_of_dead_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints <= 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/lowest_hp_percentage\":\r\n dependencyValue = dependency.reduce((prev, curr) => {\r\n let currentHpPercentage = curr.combatDetails.currentHitpoints / curr.combatDetails.maxHitpoints;\r\n return currentHpPercentage < prev ? currentHpPercentage : prev;\r\n }, 2) * 100;\r\n break;\r\n default:\r\n dependencyValue = dependency\r\n .map((unit) => this.getDependencyValue(unit, currentTime))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n break;\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n getDependencyValue(source, currentTime) {\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/berserk\":\r\n case \"/combat_trigger_conditions/frenzy\":\r\n case \"/combat_trigger_conditions/precision\":\r\n case \"/combat_trigger_conditions/vampirism\":\r\n case \"/combat_trigger_conditions/attack_coffee\":\r\n case \"/combat_trigger_conditions/defense_coffee\":\r\n case \"/combat_trigger_conditions/lucky_coffee\":\r\n case \"/combat_trigger_conditions/magic_coffee\":\r\n case \"/combat_trigger_conditions/melee_coffee\":\r\n case \"/combat_trigger_conditions/ranged_coffee\":\r\n case \"/combat_trigger_conditions/swiftness_coffee\":\r\n case \"/combat_trigger_conditions/wisdom_coffee\":\r\n case \"/combat_trigger_conditions/ice_spear\":\r\n case \"/combat_trigger_conditions/puncture\":\r\n case \"/combat_trigger_conditions/frost_surge\":\r\n case \"/combat_trigger_conditions/elusiveness\":\r\n case \"/combat_trigger_conditions/channeling_coffee\":\r\n case \"/combat_trigger_conditions/fierce_aura\":\r\n case \"/combat_trigger_conditions/invincible_armor\":\r\n case \"/combat_trigger_conditions/invincible_fire_resistance\":\r\n case \"/combat_trigger_conditions/invincible_nature_resistance\":\r\n case \"/combat_trigger_conditions/invincible_water_resistance\":\r\n case \"/combat_trigger_conditions/provoke\":\r\n case \"/combat_trigger_conditions/taunt\":\r\n case \"/combat_trigger_conditions/crippling_slash\":\r\n case \"/combat_trigger_conditions/mana_spring\":\r\n case \"/combat_trigger_conditions/retribution\":\r\n case \"/combat_trigger_conditions/fracturing_impact\":\r\n case \"/combat_trigger_conditions/maim\":\r\n case \"/combat_trigger_conditions/curse\":\r\n case \"/combat_trigger_conditions/weaken\":\r\n let buffHrid = \"/buff_uniques\";\r\n buffHrid += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n return source.combatBuffs[buffHrid];\r\n case \"/combat_trigger_conditions/critical_aura\":\r\n case \"/combat_trigger_conditions/critical_coffee\":\r\n case \"/combat_trigger_conditions/intelligence_coffee\":\r\n case \"/combat_trigger_conditions/stamina_coffee\":\r\n case \"/combat_trigger_conditions/elemental_affinity\":\r\n case \"/combat_trigger_conditions/fury\":\r\n case \"/combat_trigger_conditions/guardian_aura\":\r\n case \"/combat_trigger_conditions/insanity\":\r\n case \"/combat_trigger_conditions/spike_shell\":\r\n case \"/combat_trigger_conditions/toxic_pollen\":\r\n case \"/combat_trigger_conditions/invincible\":\r\n case \"/combat_trigger_conditions/mystic_aura\":\r\n case \"/combat_trigger_conditions/pestilent_shot\":\r\n case \"/combat_trigger_conditions/smoke_burst\":\r\n case \"/combat_trigger_conditions/speed_aura\":\r\n case \"/combat_trigger_conditions/toughness\":\r\n case \"/combat_trigger_conditions/enrage\":\r\n let buffPrefix = \"/buff_uniques\";\r\n buffPrefix += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n let buffs = Object.keys(source.combatBuffs).filter(buff => buff.startsWith(buffPrefix));\r\n return source.combatBuffs[buffs?.[0]];\r\n case \"/combat_trigger_conditions/current_hp\":\r\n return source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/current_mp\":\r\n return source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/missing_hp\":\r\n return source.combatDetails.maxHitpoints - source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/missing_mp\":\r\n return source.combatDetails.maxManapoints - source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/stun_status\":\r\n // Replicate the game's behaviour of \"stun status active\" triggers activating\r\n // immediately after the stun has worn off\r\n return source.isStunned || source.stunExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/blind_status\":\r\n return source.isBlinded || source.blindExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/silence_status\":\r\n return source.isSilenced || source.silenceExpireTime == currentTime;\r\n default:\r\n throw new Error(\"Unknown conditionHrid in trigger: \" + this.conditionHrid);\r\n }\r\n }\r\n\r\n compareValue(dependencyValue) {\r\n switch (this.comparatorHrid) {\r\n case \"/combat_trigger_comparators/greater_than_equal\":\r\n return dependencyValue >= this.value;\r\n case \"/combat_trigger_comparators/less_than_equal\":\r\n return dependencyValue <= this.value;\r\n case \"/combat_trigger_comparators/is_active\":\r\n return !!dependencyValue;\r\n case \"/combat_trigger_comparators/is_inactive\":\r\n return !dependencyValue;\r\n default:\r\n throw new Error(\"Unknown comparatorHrid in trigger: \" + this.comparatorHrid);\r\n }\r\n }\r\n}\r\n\r\nexport default Trigger;\r\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","// This function allow to reference async chunks\n__webpack_require__.u = (chunkId) => {\n\t// return url for filenames based on template\n\treturn \"\" + chunkId + \".bundle.js\";\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","var scriptUrl;\nif (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + \"\";\nvar document = __webpack_require__.g.document;\nif (!scriptUrl && document) {\n\tif (document.currentScript)\n\t\tscriptUrl = document.currentScript.src;\n\tif (!scriptUrl) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tif(scripts.length) {\n\t\t\tvar i = scripts.length - 1;\n\t\t\twhile (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;\n\t\t}\n\t}\n}\n// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration\n// or pass an empty string (\"\") and set the __webpack_public_path__ variable from your code to use your own logic.\nif (!scriptUrl) throw new Error(\"Automatic publicPath is not supported in this browser\");\nscriptUrl = scriptUrl.replace(/#.*$/, \"\").replace(/\\?.*$/, \"\").replace(/\\/[^\\/]+$/, \"/\");\n__webpack_require__.p = scriptUrl;","__webpack_require__.b = document.baseURI || self.location.href;\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t\"main\": 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n// no on chunks loaded\n\n// no jsonp function","import Equipment from \"./combatsimulator/equipment.js\";\r\nimport Player from \"./combatsimulator/player.js\";\r\nimport abilityDetailMap from \"./combatsimulator/data/abilityDetailMap.json\";\r\nimport itemDetailMap from \"./combatsimulator/data/itemDetailMap.json\";\r\nimport houseRoomDetailMap from \"./combatsimulator/data/houseRoomDetailMap.json\";\r\nimport Ability from \"./combatsimulator/ability.js\";\r\nimport Consumable from \"./combatsimulator/consumable.js\";\r\nimport HouseRoom from \"./combatsimulator/houseRoom\"\r\nimport combatTriggerDependencyDetailMap from \"./combatsimulator/data/combatTriggerDependencyDetailMap.json\";\r\nimport combatTriggerConditionDetailMap from \"./combatsimulator/data/combatTriggerConditionDetailMap.json\";\r\nimport combatTriggerComparatorDetailMap from \"./combatsimulator/data/combatTriggerComparatorDetailMap.json\";\r\nimport abilitySlotsLevelRequirementList from \"./combatsimulator/data/abilitySlotsLevelRequirementList.json\";\r\nimport actionDetailMap from \"./combatsimulator/data/actionDetailMap.json\";\r\nimport combatMonsterDetailMap from \"./combatsimulator/data/combatMonsterDetailMap.json\";\r\nimport damageTypeDetailMap from \"./combatsimulator/data/damageTypeDetailMap.json\";\r\nimport combatStyleDetailMap from \"./combatsimulator/data/combatStyleDetailMap.json\";\r\nimport openableLootDropMap from \"./combatsimulator/data/openableLootDropMap.json\";\r\nimport achievementTierMap from \"./combatsimulator/data/achievementTierDetailMap.json\"\r\nimport achievementDetailMap from \"./combatsimulator/data/achievementDetailMap.json\"\r\n\r\nimport patchNote from \"../patchNote.json\";\r\n\r\nconst ONE_SECOND = 1e9;\r\nconst ONE_HOUR = 60 * 60 * ONE_SECOND;\r\n\r\nlet buttonStartSimulation = document.getElementById(\"buttonStartSimulation\");\r\nlet buttonStopSimulation = document.getElementById(\"buttonStopSimulation\");\r\nlet progressbar = document.getElementById(\"simulationProgressBar\");\r\nlet simStartTime = 0;\r\n\r\nlet worker = new Worker(new URL(\"worker.js\", import.meta.url));\r\nlet multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\nlet workerPool = [];\r\n\r\n\r\nlet player = new Player();\r\nlet selectedPlayers = [];\r\nlet food = [null, null, null];\r\nlet drinks = [null, null, null];\r\nlet abilities = [null, null, null, null];\r\nlet triggerMap = {};\r\nlet modalTriggers = [];\r\nlet currentSimResults = {};\r\n\r\nlet currentPlayerTabId = '1';\r\nlet playerDataMap = {\r\n \"1\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"2\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"3\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"4\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"5\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\"\r\n};\r\nwindow.revenue = 0;\r\nwindow.noRngRevenue = 0;\r\nwindow.expenses = 0;\r\nwindow.profit = 0;\r\nwindow.noRngProfit = 0;\r\n\r\n// #region Worker\r\n\r\nfunction onWorkerMessage(event) {\r\n switch (event.data.type) {\r\n case \"simulation_result\":\r\n progressbar.style.width = \"100%\";\r\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n //console.log(\"SIM RESULTS: \", event.data.simResult);\r\n showSimulationResult(event.data.simResult);\r\n updateContent();\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n document.getElementById('buttonShowAllSimData').style.display = 'none';\r\n break;\r\n case \"simulation_progress\":\r\n let progress = Math.floor(100 * event.data.progress);\r\n progressbar.style.width = progress + \"%\";\r\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n break;\r\n case \"simulation_error\":\r\n showErrorModal(event.data.error.toString());\r\n break;\r\n }\r\n};\r\n\r\nfunction onMultiWorkerMessage(event) {\r\n switch (event.data.type) {\r\n case \"simulation_result_allZones\":\r\n progressbar.style.width = \"100%\";\r\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n showAllSimulationResults(event.data.simResults);\r\n updateContent();\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n document.getElementById('buttonShowAllSimData').style.display = 'block';\r\n break;\r\n case \"simulation_progress\":\r\n let progress = Math.floor(100 * event.data.progress);\r\n progressbar.style.width = progress + \"%\";\r\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n break;\r\n case \"simulation_error\":\r\n showErrorModal(event.data.error.toString());\r\n break;\r\n }\r\n};\r\n\r\n// #endregion\r\n\r\n// #region Equipment\r\n\r\nfunction initEquipmentSection() {\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n initEquipmentSelect(type);\r\n initEnhancementLevelInput(type);\r\n });\r\n}\r\n\r\nfunction initEquipmentSelect(equipmentType) {\r\n let selectId = \"selectEquipment_\";\r\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\r\n selectId += \"weapon\";\r\n } else {\r\n selectId += equipmentType;\r\n }\r\n let selectElement = document.getElementById(selectId);\r\n\r\n let gameEquipment = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/equipment\")\r\n .filter((item) => item.equipmentDetail.type == \"/equipment_types/\" + equipmentType)\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const equipment of Object.values(gameEquipment)) {\r\n let opt = new Option(equipment.name, equipment.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + equipment.hrid);\r\n selectElement.add(opt);\r\n }\r\n\r\n selectElement.addEventListener(\"change\", (event) => {\r\n equipmentSelectHandler(event, equipmentType);\r\n });\r\n}\r\n\r\nfunction initHouseRoomsModal() {\r\n let houseRoomsList = document.getElementById(\"houseRoomsList\");\r\n let newChildren = [];\r\n let houseRooms = Object.values(houseRoomDetailMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n player.houseRooms = {};\r\n\r\n for (const room of Object.values(houseRooms)) {\r\n player.houseRooms[room.hrid] = 0;\r\n\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let nameCol = createElement(\"div\", \"col-md-4 offset-md-3 align-self-center\", room.name);\r\n nameCol.setAttribute(\"data-i18n\", \"houseRoomNames.\" + room.hrid);\r\n row.appendChild(nameCol);\r\n\r\n let levelCol = createElement(\"div\", \"col-md-2\");\r\n let levelInput = createHouseInput(room.hrid);\r\n\r\n levelInput.addEventListener(\"input\", function (e) {\r\n let inputValue = e.target.value;\r\n const hrid = e.target.dataset.houseHrid;\r\n player.houseRooms[hrid] = parseInt(inputValue);\r\n });\r\n\r\n levelCol.appendChild(levelInput);\r\n row.appendChild(levelCol);\r\n\r\n newChildren.push(row);\r\n }\r\n\r\n houseRoomsList.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction createHouseInput(hrid) {\r\n let levelInput = document.createElement(\"input\");\r\n levelInput.className = \"form-control\";\r\n levelInput.type = \"number\";\r\n levelInput.placeholder = 0;\r\n levelInput.min = 0;\r\n levelInput.max = 8;\r\n levelInput.step = 1;\r\n levelInput.dataset.houseHrid = hrid;\r\n\r\n return levelInput;\r\n}\r\n\r\nfunction refreshAchievementStatics() {\r\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n for(const tier of Object.values(tierMap)) {\r\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\r\n const done = Array.from(checks).filter(cb => cb.checked).length;\r\n const total = checks.length;\r\n\r\n const stat = document.getElementById(`AchTier${tier.sortIndex}Statics`);\r\n stat.innerText = `(${done}/${total})`;\r\n if (done == total) {\r\n // set to green\r\n stat.classList.remove(\"text-secondary\");\r\n stat.classList.add(\"text-success\");\r\n } else {\r\n // set to secondary\r\n stat.classList.remove(\"text-success\");\r\n stat.classList.add(\"text-secondary\");\r\n }\r\n }\r\n}\r\n\r\nfunction initAchievementsModal(){\r\n let achievementsList = document.getElementById(\"achievementsList\");\r\n let newChildren = [];\r\n player.achievements = {};\r\n\r\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n for(const tier of Object.values(tierMap)) {\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid).sort((a, b) => a.sortIndex - b.sortIndex);\r\n let detailMapCount = detailMap.length;\r\n if (detailMapCount <= 0) continue;\r\n\r\n let card = createElement(\"div\", \"card\");\r\n let cardHeader = createElement(\"div\", \"card-header d-flex align-items-center\");\r\n\r\n let cardTitle = createElement(\"a\", \"btn\", tier.name);\r\n cardTitle.setAttribute(\"data-bs-toggle\",\"collapse\");\r\n cardTitle.setAttribute(\"href\", `#AchTier${tier.sortIndex}`);\r\n cardTitle.setAttribute(\"data-i18n\", \"achievementTierNames.\"+tier.hrid);\r\n cardHeader.appendChild(cardTitle);\r\n\r\n let bufDesc = createElement(\"div\", \"small text-secondary\");\r\n let buffName = createElement(\"i\", \"\");\r\n buffName.setAttribute(\"data-i18n\", \"buffTypeNames.\"+tier[\"buff\"].typeHrid);\r\n bufDesc.appendChild(buffName);\r\n let buffValue = createElement(\"i\", \"\");\r\n buffValue.innerText = \":+\" + parseFloat(tier[\"buff\"].ratioBoost==0?tier[\"buff\"].flatBoost:tier[\"buff\"].ratioBoost)*100 + \"%\";\r\n bufDesc.appendChild(buffValue);\r\n cardHeader.appendChild(bufDesc);\r\n\r\n let cardStatics = createElement(\"div\", \"ms-auto btn\", `(0/${detailMapCount})`);\r\n cardStatics.id = `AchTier${tier.sortIndex}Statics`;\r\n cardStatics.dataset.checked = \"true\";\r\n cardStatics.addEventListener(\"click\", function (e) {\r\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\r\n for (const check of checks) {\r\n check.checked = cardStatics.dataset.checked == \"true\";\r\n const hrid = check.dataset.achievementHrid;\r\n player.achievements[hrid] = check.checked;\r\n }\r\n cardStatics.dataset.checked = cardStatics.dataset.checked == \"true\" ? \"false\" : \"true\";\r\n refreshAchievementStatics();\r\n });\r\n cardHeader.appendChild(cardStatics);\r\n\r\n card.appendChild(cardHeader);\r\n\r\n let cardMain = createElement(\"div\", \"collapse\");\r\n cardMain.id = `AchTier${tier.sortIndex}`;\r\n let cardBody = createElement(\"div\", \"card-body\");\r\n\r\n for (const detail of Object.values(detailMap)) {\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let formCheck = createElement(\"div\", \"form-check\");\r\n let input = createElement(\"input\", \"form-check-input\");\r\n input.setAttribute(\"type\", \"checkbox\");\r\n input.setAttribute(\"data-tier\", tier.sortIndex);\r\n input.id = `AchDetail${detail.sortIndex}`;\r\n input.dataset.achievementHrid = detail.hrid;\r\n input.addEventListener(\"change\", function (e) {\r\n const hrid = e.target.dataset.achievementHrid;\r\n player.achievements[hrid] = e.target.checked;\r\n\r\n refreshAchievementStatics();\r\n });\r\n formCheck.appendChild(input);\r\n\r\n let name = createElement(\"label\", \"form-check-label\", detail.name);\r\n name.setAttribute(\"data-i18n\", \"achievementNames.\" + detail.hrid);\r\n name.setAttribute(\"for\", `AchDetail${detail.sortIndex}`);\r\n formCheck.appendChild(name);\r\n row.appendChild(formCheck);\r\n cardBody.appendChild(row);\r\n }\r\n cardMain.appendChild(cardBody);\r\n card.appendChild(cardMain);\r\n\r\n newChildren.push(card);\r\n }\r\n\r\n achievementsList.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction initEnhancementLevelInput(equipmentType) {\r\n let inputId = \"inputEquipmentEnhancementLevel_\";\r\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\r\n inputId += \"weapon\";\r\n } else {\r\n inputId += equipmentType;\r\n }\r\n\r\n let inputElement = document.getElementById(inputId);\r\n inputElement.value = 0;\r\n inputElement.addEventListener(\"change\", enhancementLevelInputHandler);\r\n}\r\n\r\nfunction equipmentSelectHandler(event, type) {\r\n let equipmentType = \"/equipment_types/\" + type;\r\n\r\n if (!event.target.value) {\r\n updateEquipmentState();\r\n updateUI();\r\n return;\r\n }\r\n\r\n let gameItem = itemDetailMap[event.target.value];\r\n\r\n // Weapon select has two handlers because of mainhand and twohand weapons. Ignore the handler with the wrong type\r\n if (gameItem.equipmentDetail.type != equipmentType) {\r\n return;\r\n }\r\n\r\n if (type == \"two_hand\") {\r\n document.getElementById(\"selectEquipment_off_hand\").value = \"\";\r\n document.getElementById(\"inputEquipmentEnhancementLevel_off_hand\").value = 0;\r\n }\r\n if (type == \"off_hand\" && player.equipment[\"/equipment_types/two_hand\"]) {\r\n document.getElementById(\"selectEquipment_weapon\").value = \"\";\r\n document.getElementById(\"inputEquipmentEnhancementLevel_weapon\").value = 0;\r\n }\r\n\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\nfunction enhancementLevelInputHandler() {\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\nfunction updateEquipmentState() {\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentType = \"/equipment_types/\" + type;\r\n let selectType = type;\r\n if (type == \"main_hand\" || type == \"two_hand\") {\r\n selectType = \"weapon\";\r\n }\r\n\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + selectType);\r\n let equipmentHrid = equipmentSelect.value;\r\n\r\n if (!equipmentHrid) {\r\n player.equipment[equipmentType] = null;\r\n return;\r\n }\r\n\r\n let gameItem = itemDetailMap[equipmentHrid];\r\n\r\n // Clear old weapon if a weapon of a different type is equipped\r\n if (gameItem.equipmentDetail.type != equipmentType) {\r\n player.equipment[equipmentType] = null;\r\n return;\r\n }\r\n\r\n let enhancementLevel = Number(document.getElementById(\"inputEquipmentEnhancementLevel_\" + selectType).value);\r\n player.equipment[equipmentType] = new Equipment(gameItem.hrid, enhancementLevel);\r\n });\r\n}\r\n\r\ndocument.getElementById(\"selectEquipment_set\").onchange = changeEquipmentSetListener;\r\n\r\nfunction changeEquipmentSetListener() {\r\n let value = this.value\r\n let optgroupType = this.options[this.selectedIndex].parentNode.label;\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\"].forEach((type) => {\r\n let selectType = type;\r\n\r\n let currentEquipment = document.getElementById(\"selectEquipment_\" + selectType);\r\n if (type === \"feet\") {\r\n type = \"_boots\";\r\n }\r\n if (type === \"hands\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_bracers\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_gloves\";\r\n } else {\r\n type = \"_gauntlets\";\r\n }\r\n }\r\n if (type === \"head\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_hood\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_hat\";\r\n } else {\r\n type = \"_helmet\";\r\n }\r\n }\r\n if (type === \"legs\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_chaps\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_robe_bottoms\";\r\n } else {\r\n type = \"_plate_legs\";\r\n }\r\n }\r\n if (type === \"body\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_tunic\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_robe_top\";\r\n } else {\r\n type = \"_plate_body\";\r\n }\r\n }\r\n currentEquipment.value = \"/items/\" + value.toLowerCase() + type;\r\n });\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Combat Stats\r\n\r\nfunction updateCombatStatsUI() {\r\n player.updateCombatDetails();\r\n\r\n let combatStyleElement = document.getElementById(\"combatStat_combatStyleHrid\");\r\n let combatStyle = player.combatDetails.combatStats.combatStyleHrid;\r\n combatStyleElement.setAttribute(\"data-i18n\", \"combatStyleNames.\" + combatStyle);\r\n combatStyleElement.innerHTML = combatStyleDetailMap[combatStyle].name;\r\n\r\n let damageTypeElement = document.getElementById(\"combatStat_damageType\");\r\n let damageType = damageTypeDetailMap[player.combatDetails.combatStats.damageType];\r\n damageTypeElement.setAttribute(\"data-i18n\", \"damageTypeNames.\" + damageType.hrid);\r\n damageTypeElement.innerHTML = damageType.name;\r\n\r\n let attackIntervalElement = document.getElementById(\"combatStat_attackInterval\");\r\n attackIntervalElement.innerHTML = (player.combatDetails.combatStats.attackInterval / 1e9).toLocaleString() + \"s\";\r\n\r\n let primaryTrainingElement = document.getElementById(\"combatStat_primaryTraining\");\r\n let primaryTraining = player.combatDetails.combatStats.primaryTraining;\r\n primaryTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + primaryTraining);\r\n primaryTrainingElement.innerHTML = primaryTraining;\r\n\r\n let focusTrainingElement = document.getElementById(\"combatStat_focusTraining\");\r\n let focusTraining = player.combatDetails.combatStats.focusTraining;\r\n if (focusTraining) {\r\n focusTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + focusTraining);\r\n } else {\r\n focusTrainingElement.setAttribute(\"data-i18n\", \"characterSelectPage.slots.empty\");\r\n }\r\n focusTrainingElement.innerHTML = focusTraining;\r\n\r\n [\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"stabAccuracyRating\",\r\n \"stabMaxDamage\",\r\n \"slashAccuracyRating\",\r\n \"slashMaxDamage\",\r\n \"smashAccuracyRating\",\r\n \"smashMaxDamage\",\r\n \"rangedAccuracyRating\",\r\n \"rangedMaxDamage\",\r\n \"magicAccuracyRating\",\r\n \"magicMaxDamage\",\r\n \"defensiveMaxDamage\",\r\n \"stabEvasionRating\",\r\n \"slashEvasionRating\",\r\n \"smashEvasionRating\",\r\n \"rangedEvasionRating\",\r\n \"magicEvasionRating\",\r\n \"totalArmor\",\r\n \"totalWaterResistance\",\r\n \"totalNatureResistance\",\r\n \"totalFireResistance\",\r\n \"totalThreat\"\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n element.innerHTML = Math.floor(player.combatDetails[stat]);\r\n });\r\n\r\n [\r\n \"abilityHaste\",\r\n \"tenacity\"\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n element.innerHTML = Math.floor(player.combatDetails.combatStats[stat]);\r\n });\r\n\r\n [\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"combatExperience\",\r\n \"taskDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"drinkConcentration\",\r\n \"foodHaste\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\"\r\n\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n let value = (100 * player.combatDetails.combatStats[stat]).toLocaleString([], {\r\n minimumFractionDigits: 0,\r\n maximumFractionDigits: 4,\r\n });\r\n element.innerHTML = value + \"%\";\r\n });\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Level\r\n\r\nfunction initLevelSection() {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n levelInput.value = 1;\r\n levelInput.addEventListener(\"change\", levelInputHandler);\r\n });\r\n}\r\n\r\nfunction levelInputHandler() {\r\n updateLevels();\r\n updateUI();\r\n}\r\n\r\nfunction updateLevels() {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n player[skill + \"Level\"] = Number(levelInput.value);\r\n });\r\n updateCombatLevel();\r\n}\r\n\r\nfunction calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel) {\r\n return Math.floor(\r\n 0.1 * (staminaLevel + intelligenceLevel + attackLevel + defenseLevel + Math.max(meleeLevel, rangedLevel, magicLevel))\r\n + 0.5 * Math.max(attackLevel, defenseLevel, meleeLevel, rangedLevel, magicLevel)\r\n );\r\n}\r\n\r\n\r\nfunction updateCombatLevel() {\r\n let staminaLevel = player[\"staminaLevel\"];\r\n let intelligenceLevel = player[\"intelligenceLevel\"];\r\n let defenseLevel = player[\"defenseLevel\"];\r\n let attackLevel = player[\"attackLevel\"];\r\n let meleeLevel = player[\"meleeLevel\"];\r\n let rangedLevel = player[\"rangedLevel\"];\r\n let magicLevel = player[\"magicLevel\"];\r\n\r\n let levelInput = document.getElementById(\"inputLevel_combat\");\r\n levelInput.value = calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel);;\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Food\r\n\r\nfunction initFoodSection() {\r\n for (let i = 0; i < 3; i++) {\r\n let element = document.getElementById(\"selectFood_\" + i);\r\n\r\n let gameFoods = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/food\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const food of Object.values(gameFoods)) {\r\n let opt = new Option(food.name, food.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + food.hrid);\r\n element.add(opt);\r\n }\r\n\r\n element.addEventListener(\"change\", foodSelectHandler);\r\n }\r\n}\r\n\r\nfunction foodSelectHandler() {\r\n updateFoodState();\r\n updateUI();\r\n}\r\n\r\nfunction updateFoodState() {\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n food[i] = foodSelect.value;\r\n if (food[i] && !triggerMap[food[i]]) {\r\n let gameItem = itemDetailMap[food[i]];\r\n triggerMap[food[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateFoodUI() {\r\n for (let i = 0; i < 3; i++) {\r\n let selectElement = document.getElementById(\"selectFood_\" + i);\r\n let triggerButton = document.getElementById(\"buttonFoodTrigger_\" + i);\r\n\r\n selectElement.disabled = i >= player.combatDetails.combatStats.foodSlots;\r\n triggerButton.disabled = i >= player.combatDetails.combatStats.foodSlots || !food[i];\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Drinks\r\n\r\nfunction initDrinksSection() {\r\n for (let i = 0; i < 3; i++) {\r\n let element = document.getElementById(\"selectDrink_\" + i);\r\n\r\n let gameDrinks = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/drink\")\r\n .filter((item) => item.consumableDetail.usableInActionTypeMap[\"/action_types/combat\"])\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const drink of Object.values(gameDrinks)) {\r\n let opt = new Option(drink.name, drink.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + drink.hrid);\r\n element.add(opt);\r\n }\r\n\r\n element.addEventListener(\"change\", drinkSelectHandler);\r\n }\r\n}\r\n\r\nfunction drinkSelectHandler() {\r\n updateDrinksState();\r\n updateDrinksUI();\r\n}\r\n\r\nfunction updateDrinksState() {\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n drinks[i] = drinkSelect.value;\r\n if (drinks[i] && !triggerMap[drinks[i]]) {\r\n let gameItem = itemDetailMap[drinks[i]];\r\n triggerMap[drinks[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateDrinksUI() {\r\n for (let i = 0; i < 3; i++) {\r\n let selectElement = document.getElementById(\"selectDrink_\" + i);\r\n let triggerButton = document.getElementById(\"buttonDrinkTrigger_\" + i);\r\n\r\n selectElement.disabled = i >= player.combatDetails.combatStats.drinkSlots;\r\n triggerButton.disabled = i >= player.combatDetails.combatStats.drinkSlots || !drinks[i];\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Abilities\r\n\r\nfunction initAbilitiesSection() {\r\n for (let i = 0; i < 5; i++) {\r\n let selectElement = document.getElementById(\"selectAbility_\" + i);\r\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\r\n\r\n inputElement.value = 1;\r\n\r\n let gameAbilities;\r\n if (i == 0) {\r\n gameAbilities = Object.values(abilityDetailMap).filter(x => x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\r\n } else {\r\n gameAbilities = Object.values(abilityDetailMap).filter(x => !x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\r\n }\r\n\r\n\r\n for (const ability of Object.values(gameAbilities)) {\r\n let opt = new Option(ability.name, ability.hrid);\r\n opt.setAttribute(\"data-i18n\", \"abilityNames.\" + ability.hrid);\r\n selectElement.add(opt);\r\n }\r\n\r\n selectElement.addEventListener(\"change\", abilitySelectHandler);\r\n }\r\n}\r\n\r\nfunction abilitySelectHandler() {\r\n updateAbilityState();\r\n updateAbilityUI();\r\n}\r\n\r\nfunction updateAbilityState() {\r\n for (let i = 0; i < 5; i++) {\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\r\n abilities[i] = abilitySelect.value;\r\n if (abilities[i] && !triggerMap[abilities[i]]) {\r\n let gameAbility = abilityDetailMap[abilities[i]];\r\n triggerMap[abilities[i]] = structuredClone(gameAbility.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateAbilityUI() {\r\n for (let i = 0; i < 5; i++) {\r\n let selectElement = document.getElementById(\"selectAbility_\" + i);\r\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let triggerButton = document.getElementById(\"buttonAbilityTrigger_\" + i);\r\n\r\n selectElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\r\n inputElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\r\n triggerButton.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1] || !abilities[i];\r\n let moveUpButton = document.getElementById(\"selectAbilityMoveUp_\" + i);\r\n moveUpButton.onclick = () => swapAbilityOrder(i, -1);\r\n }\r\n}\r\n\r\nfunction swapAbilityOrder(abilityIndex, step) {\r\n const swapIndex = abilityIndex + step;\r\n if (swapIndex < 0 || swapIndex > 4) {\r\n return;\r\n }\r\n\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilityIndex);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilityIndex);\r\n\r\n const tempAbility = abilities[abilityIndex];\r\n abilities[abilityIndex] = abilities[swapIndex];\r\n abilities[swapIndex] = tempAbility;\r\n\r\n const tempLevel = abilityLevelInput.value;\r\n abilityLevelInput.value = document.getElementById(\"inputAbilityLevel_\" + swapIndex).value;\r\n document.getElementById(\"inputAbilityLevel_\" + swapIndex).value = tempLevel;\r\n\r\n abilitySelect.value = document.getElementById(\"selectAbility_\" + (swapIndex)).value;\r\n document.getElementById(\"selectAbility_\" + swapIndex).value = abilities[swapIndex];\r\n\r\n updateAbilityState();\r\n updateAbilityUI();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Trigger\r\n\r\nfunction initTriggerModal() {\r\n let modal = document.getElementById(\"triggerModal\");\r\n modal.addEventListener(\"show.bs.modal\", (event) => triggerModalShownHandler(event));\r\n\r\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\r\n triggerSaveButton.addEventListener(\"click\", (event) => triggerModalSaveHandler(event));\r\n\r\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\r\n triggerAddButton.addEventListener(\"click\", (event) => triggerAddButtonHandler(event));\r\n\r\n let triggerDefaultButton = document.getElementById(\"buttonDefaultTrigger\");\r\n triggerDefaultButton.addEventListener(\"click\", (event) => triggerDefaultButtonHandler(event));\r\n\r\n for (let i = 0; i < 4; i++) {\r\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\r\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\r\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\r\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\r\n let triggerRemoveButton = document.getElementById(\"buttonRemoveTrigger_\" + i);\r\n\r\n triggerDependencySelect.addEventListener(\"change\", (event) => triggerDependencySelectHandler(event, i));\r\n triggerConditionSelect.addEventListener(\"change\", (event) => triggerConditionSelectHandler(event, i));\r\n triggerComparatorSelect.addEventListener(\"change\", (event) => triggerComparatorSelectHander(event, i));\r\n triggerValueInput.addEventListener(\"change\", (event) => triggerValueInputHandler(event, i));\r\n triggerRemoveButton.addEventListener(\"click\", (event) => triggerRemoveButtonHandler(event, i));\r\n }\r\n}\r\n\r\nfunction triggerModalShownHandler(event) {\r\n let triggerButton = event.relatedTarget;\r\n\r\n let triggerType = triggerButton.getAttribute(\"data-bs-triggertype\");\r\n let triggerIndex = Number(triggerButton.getAttribute(\"data-bs-triggerindex\"));\r\n\r\n let triggerTarget;\r\n switch (triggerType) {\r\n case \"food\":\r\n triggerTarget = food[triggerIndex];\r\n break;\r\n case \"drink\":\r\n triggerTarget = drinks[triggerIndex];\r\n break;\r\n case \"ability\":\r\n triggerTarget = abilities[triggerIndex];\r\n break;\r\n }\r\n\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n triggerTargetnput.value = triggerTarget;\r\n modalTriggers = triggerMap[triggerTarget];\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerModalSaveHandler(event) {\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n let triggerTarget = triggerTargetnput.value;\r\n\r\n triggerMap[triggerTarget] = modalTriggers;\r\n}\r\n\r\nfunction triggerDependencySelectHandler(event, index) {\r\n modalTriggers[index].dependencyHrid = event.target.value;\r\n modalTriggers[index].conditionHrid = \"\";\r\n modalTriggers[index].comparatorHrid = \"\";\r\n modalTriggers[index].value = 0;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerConditionSelectHandler(event, index) {\r\n modalTriggers[index].conditionHrid = event.target.value;\r\n modalTriggers[index].comparatorHrid = \"\";\r\n modalTriggers[index].value = 0;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerComparatorSelectHander(event, index) {\r\n modalTriggers[index].comparatorHrid = event.target.value;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerValueInputHandler(event, index) {\r\n modalTriggers[index].value = Number(event.target.value);\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerRemoveButtonHandler(event, index) {\r\n modalTriggers.splice(index, 1);\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerAddButtonHandler(event) {\r\n if (modalTriggers.length == 4) {\r\n return;\r\n }\r\n\r\n modalTriggers.push({\r\n dependencyHrid: \"\",\r\n conditionHrid: \"\",\r\n comparatorHrid: \"\",\r\n value: 0,\r\n });\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerDefaultButtonHandler(event) {\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n let triggerTarget = triggerTargetnput.value;\r\n\r\n if (triggerTarget.startsWith(\"/items/\")) {\r\n modalTriggers = structuredClone(itemDetailMap[triggerTarget].consumableDetail.defaultCombatTriggers);\r\n } else {\r\n modalTriggers = structuredClone(abilityDetailMap[triggerTarget].defaultCombatTriggers);\r\n }\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction updateTriggerModal() {\r\n let triggerStartTextElement = document.getElementById(\"triggerStartText\");\r\n if (modalTriggers.length == 0) {\r\n triggerStartTextElement.innerHTML = \"Activate as soon as it's off cooldown\";\r\n } else {\r\n triggerStartTextElement.innerHTML = \"Activate when:\";\r\n }\r\n\r\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\r\n triggerAddButton.disabled = modalTriggers.length == 4;\r\n\r\n let triggersValid = true;\r\n\r\n for (let i = 0; i < 4; i++) {\r\n let triggerElement = document.getElementById(\"modalTrigger_\" + i);\r\n\r\n if (!modalTriggers[i]) {\r\n hideElement(triggerElement);\r\n continue;\r\n }\r\n\r\n showElement(triggerElement);\r\n\r\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\r\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\r\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\r\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\r\n\r\n showElement(triggerDependencySelect);\r\n fillTriggerDependencySelect(triggerDependencySelect);\r\n\r\n if (modalTriggers[i].dependencyHrid == \"\") {\r\n hideElement(triggerConditionSelect);\r\n hideElement(triggerComparatorSelect);\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerDependencySelect.value = modalTriggers[i].dependencyHrid;\r\n showElement(triggerConditionSelect);\r\n fillTriggerConditionSelect(triggerConditionSelect, modalTriggers[i].dependencyHrid);\r\n\r\n if (modalTriggers[i].conditionHrid == \"\") {\r\n hideElement(triggerComparatorSelect);\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerConditionSelect.value = modalTriggers[i].conditionHrid;\r\n showElement(triggerComparatorSelect);\r\n fillTriggerComparatorSelect(triggerComparatorSelect, modalTriggers[i].conditionHrid);\r\n\r\n if (modalTriggers[i].comparatorHrid == \"\") {\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerComparatorSelect.value = modalTriggers[i].comparatorHrid;\r\n\r\n if (combatTriggerComparatorDetailMap[modalTriggers[i].comparatorHrid].allowValue) {\r\n showElement(triggerValueInput);\r\n triggerValueInput.value = modalTriggers[i].value;\r\n } else {\r\n hideElement(triggerValueInput);\r\n }\r\n }\r\n\r\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\r\n triggerSaveButton.disabled = !triggersValid;\r\n\r\n updateContent();\r\n}\r\n\r\nfunction fillTriggerDependencySelect(element) {\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const dependency of Object.values(combatTriggerDependencyDetailMap).sort(\r\n (a, b) => a.sortIndex - b.sortIndex\r\n )) {\r\n let opt = new Option(dependency.name, dependency.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerDependencyNames.\" + dependency.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction fillTriggerConditionSelect(element, dependencyHrid) {\r\n let dependency = combatTriggerDependencyDetailMap[dependencyHrid];\r\n\r\n let conditions;\r\n if (dependency.isSingleTarget) {\r\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isSingleTarget);\r\n } else {\r\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isMultiTarget);\r\n }\r\n\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const condition of Object.values(conditions).sort((a, b) => a.sortIndex - b.sortIndex)) {\r\n let opt = new Option(condition.name, condition.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerConditionNames.\" + condition.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction fillTriggerComparatorSelect(element, conditionHrid) {\r\n let condition = combatTriggerConditionDetailMap[conditionHrid];\r\n\r\n let comparators = condition.allowedComparatorHrids.map((hrid) => combatTriggerComparatorDetailMap[hrid]);\r\n\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const comparator of Object.values(comparators).sort((a, b) => a.sortIndex - b.sortIndex)) {\r\n let opt = new Option(comparator.name, comparator.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerComparatorNames.\" + comparator.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction hideElement(element) {\r\n element.classList.remove(\"d-flex\");\r\n element.classList.add(\"d-none\");\r\n}\r\n\r\nfunction showElement(element) {\r\n element.classList.remove(\"d-none\");\r\n element.classList.add(\"d-flex\");\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Zones\r\n\r\nfunction initZones() {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n\r\n // TOOD dungeon wave spawns\r\n let gameZones = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const zone of Object.values(gameZones)) {\r\n let opt = new Option(zone.name, zone.hrid);\r\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + zone.hrid);\r\n zoneSelect.add(opt);\r\n }\r\n\r\n\r\n let zoneCheckBox = document.getElementById(\"zoneCheckBox\");\r\n let checkAllZonesToggle = document.getElementById('checkAllZones');\r\n\r\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\r\n simAllZonesToggle.addEventListener(\"change\", (event) => {\r\n if (simAllZonesToggle.checked) {\r\n zoneCheckBox.classList.remove(\"d-none\");\r\n zoneCheckBox.querySelectorAll(\".zone-checkbox\").forEach(checkbox => checkbox.checked = true);\r\n checkAllZonesToggle.checked = true;\r\n } else {\r\n zoneCheckBox.classList.add(\"d-none\");\r\n }\r\n });\r\n\r\n let zoneHrids = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .flat();\r\n\r\n for (const zoneHrid of zoneHrids) {\r\n const newZone = document.createElement('div');\r\n newZone.classList.add('form-check');\r\n newZone.innerHTML = `\r\n \r\n \r\n `;\r\n zoneCheckBox.append(newZone);\r\n }\r\n\r\n let checkZoneToggles = document.querySelectorAll('.zone-checkbox');\r\n checkAllZonesToggle.addEventListener('change', () => {\r\n checkZoneToggles.forEach(cb => cb.checked = checkAllZonesToggle.checked);\r\n });\r\n\r\n checkZoneToggles.forEach(cb =>\r\n cb.addEventListener('change', () => {\r\n checkAllZonesToggle.checked = [...checkZoneToggles].every(x => x.checked);\r\n })\r\n );\r\n\r\n\r\n let soloCheckBox = document.getElementById(\"soloCheckBox\");\r\n let checkAllSolosToggle = document.getElementById('checkAllSolos');\r\n\r\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\r\n simAllSoloToggle.addEventListener(\"change\", (event) => {\r\n if (simAllSoloToggle.checked) {\r\n soloCheckBox.classList.remove(\"d-none\");\r\n soloCheckBox.querySelectorAll(\".solo-checkbox\").forEach(checkbox => checkbox.checked = true);\r\n checkAllSolosToggle.checked = true;\r\n } else {\r\n soloCheckBox.classList.add(\"d-none\");\r\n }\r\n });\r\n\r\n let soloHrids = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount == 1)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .flat();\r\n\r\n for (const zoneHrid of soloHrids) {\r\n const newZone = document.createElement('div');\r\n newZone.classList.add('form-check');\r\n newZone.innerHTML = `\r\n \r\n \r\n `;\r\n soloCheckBox.append(newZone);\r\n }\r\n\r\n let checkSoloToggles = document.querySelectorAll('.solo-checkbox');\r\n checkAllSolosToggle.addEventListener('change', () => {\r\n checkSoloToggles.forEach(cb => cb.checked = checkAllSolosToggle.checked);\r\n });\r\n\r\n checkSoloToggles.forEach(cb =>\r\n cb.addEventListener('change', () => {\r\n checkAllSolosToggle.checked = [...checkSoloToggles].every(x => x.checked);\r\n })\r\n );\r\n}\r\n\r\nfunction initDungeons() {\r\n let dungeonSelect = document.getElementById(\"selectDungeon\");\r\n\r\n let gameDungeons = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category == \"/action_categories/combat/dungeons\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const dungeon of Object.values(gameDungeons)) {\r\n let opt = new Option(dungeon.name, dungeon.hrid);\r\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + dungeon.hrid);\r\n dungeonSelect.add(opt);\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Simulation Result\r\n\r\nfunction createDamageDoneAccordion(enemyIndex) {\r\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageDoneAccordionEnemy${enemyIndex}`);\r\n\r\n const colDiv = createElement('div', 'col');\r\n const accordionMainDiv = createElement('div', 'accordion');\r\n const accordionItemDiv = createElement('div', 'accordion-item');\r\n\r\n const headerH2 = createElement('h2', 'accordion-header');\r\n const button = createElement('button', 'accordion-button collapsed',\r\n `Damage Done (Enemy ${enemyIndex})`,\r\n `buttonSimulationResultDamageDoneAccordionEnemy${enemyIndex}`\r\n );\r\n button.setAttribute('type', 'button');\r\n button.setAttribute('data-bs-toggle', 'collapse');\r\n button.setAttribute('data-bs-target', `#collapseDamageDone${enemyIndex}`);\r\n button.style.padding = '0.5em';\r\n\r\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageDone${enemyIndex}`);\r\n const accordionBodyDiv = createElement('div', 'accordion-body');\r\n\r\n const headerRow = createElement('div', 'row');\r\n headerRow.innerHTML = `\r\n
Source
\r\n
Hitchance
\r\n
DPS
\r\n
%
\r\n `;\r\n\r\n const resultDiv = createElement('div', '', '', `simulationResultDamageDoneEnemy${enemyIndex}`);\r\n\r\n accordionBodyDiv.appendChild(headerRow);\r\n accordionBodyDiv.appendChild(resultDiv);\r\n collapseDiv.appendChild(accordionBodyDiv);\r\n headerH2.appendChild(button);\r\n accordionItemDiv.appendChild(headerH2);\r\n accordionItemDiv.appendChild(collapseDiv);\r\n accordionMainDiv.appendChild(accordionItemDiv);\r\n colDiv.appendChild(accordionMainDiv);\r\n accordionDiv.appendChild(colDiv);\r\n\r\n return accordionDiv;\r\n}\r\nfunction createDamageTakenAccordion(enemyIndex) {\r\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageTakenAccordionEnemy${enemyIndex}`);\r\n\r\n const colDiv = createElement('div', 'col');\r\n const accordionMainDiv = createElement('div', 'accordion');\r\n const accordionItemDiv = createElement('div', 'accordion-item');\r\n\r\n const headerH2 = createElement('h2', 'accordion-header');\r\n const button = createElement('button', 'accordion-button collapsed',\r\n `Damage Taken (Enemy ${enemyIndex})`,\r\n `buttonSimulationResultDamageTakenAccordionEnemy${enemyIndex}`\r\n );\r\n button.setAttribute('type', 'button');\r\n button.setAttribute('data-bs-toggle', 'collapse');\r\n button.setAttribute('data-bs-target', `#collapseDamageTaken${enemyIndex}`);\r\n button.style.padding = '0.5em';\r\n\r\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageTaken${enemyIndex}`);\r\n const accordionBodyDiv = createElement('div', 'accordion-body');\r\n\r\n const headerRow = createElement('div', 'row');\r\n headerRow.innerHTML = `\r\n
Source
\r\n
Hitchance
\r\n
DPS
\r\n
%
\r\n `;\r\n\r\n const resultDiv = createElement('div', '', '', `simulationResultDamageTakenEnemy${enemyIndex}`);\r\n\r\n accordionBodyDiv.appendChild(headerRow);\r\n accordionBodyDiv.appendChild(resultDiv);\r\n collapseDiv.appendChild(accordionBodyDiv);\r\n headerH2.appendChild(button);\r\n accordionItemDiv.appendChild(headerH2);\r\n accordionItemDiv.appendChild(collapseDiv);\r\n accordionMainDiv.appendChild(accordionItemDiv);\r\n colDiv.appendChild(accordionMainDiv);\r\n accordionDiv.appendChild(colDiv);\r\n\r\n return accordionDiv;\r\n}\r\n\r\n\r\nfunction initDamageDoneTaken() {\r\n for (let i = 64; i > 0; i--) {\r\n document.getElementById(\"simulationResultTotalDamageDone\").insertAdjacentElement('afterend', createDamageDoneAccordion(i));\r\n document.getElementById(\"simulationResultTotalDamageTaken\").insertAdjacentElement('afterend', createDamageTakenAccordion(i));\r\n }\r\n}\r\n\r\nfunction showSimulationResult(simResult) {\r\n currentSimResults = simResult;\r\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\r\n expensesModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\r\n revenueModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let noRngRevenueModalTable = document.querySelector(\"#noRngRevenueTable > tbody\");\r\n noRngRevenueModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let playerToDisplay = \"player1\";\r\n if (selectedPlayers.includes(parseInt(currentPlayerTabId))) {\r\n playerToDisplay = \"player\" + currentPlayerTabId;\r\n }\r\n if (!simResult.dropRateMultiplier[playerToDisplay]) {\r\n return;\r\n }\r\n\r\n showKills(simResult, playerToDisplay);\r\n showDeaths(simResult, playerToDisplay);\r\n showExperienceGained(simResult, playerToDisplay);\r\n showConsumablesUsed(simResult, playerToDisplay);\r\n showHpSpent(simResult, playerToDisplay);\r\n showManaUsed(simResult, playerToDisplay);\r\n showHitpointsGained(simResult, playerToDisplay);\r\n showManapointsGained(simResult, playerToDisplay);\r\n showDamageDone(simResult, playerToDisplay);\r\n showDamageTaken(simResult, playerToDisplay);\r\n renderWipeEvents(simResult);\r\n window.profit = window.revenue - window.expenses;\r\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\r\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\r\n window.noRngProfit = window.noRngRevenue - window.expenses;\r\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\r\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\r\n}\r\n\r\nfunction showAllSimulationResults(simResults) {\r\n let displaySimResults = manipulateSimResultsDataForDisplay(simResults);\r\n updateAllSimsModal(displaySimResults);\r\n}\r\n\r\nfunction manipulateSimResultsDataForDisplay(simResults) {\r\n let displaySimResults = [];\r\n for (let i = 0; i < simResults.length; i++) {\r\n for (let j = 0; j < selectedPlayers.length; j++) {\r\n let playerToDisplay = \"player\" + selectedPlayers[j].toString();\r\n let simResult = simResults[i];\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let zoneName = simResult.zoneName;\r\n let difficultyTier = simResult.difficultyTier;\r\n let encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\r\n let playerDeaths = simResult.deaths[playerToDisplay] ?? 0;\r\n let deathsPerHour = (playerDeaths / hoursSimulated).toFixed(2);\r\n\r\n let totalExperience = 0;\r\n if (simResult.experienceGained[playerToDisplay]) {\r\n totalExperience = Object.values(simResult.experienceGained[playerToDisplay]).reduce((prev, cur) => prev + cur, 0);\r\n }\r\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\r\n\r\n let experiencePerHour = {};\r\n const skills = [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"];\r\n skills.forEach((skill) => {\r\n const skillLower = skill.toLowerCase();\r\n let experience = simResult.experienceGained[playerToDisplay]?.[skillLower] ?? 0;\r\n let experiencePerHourValue = 0;\r\n if (experience != 0) {\r\n experiencePerHourValue = (experience / hoursSimulated).toFixed(0);\r\n }\r\n experiencePerHour[skill] = experiencePerHourValue;\r\n });\r\n getDropProfit(simResult, playerToDisplay);\r\n let noRngRevenue = simResult[\"noRngRevenue\"];\r\n let noRngProfit = simResult[\"noRngProfit\"];\r\n let expenses = simResult[\"expenses\"];\r\n\r\n let displaySimRow = {\r\n \"ZoneName\": zoneName, \"DifficultyTier\": difficultyTier, \"Player\": playerToDisplay, \"Encounters\": encountersPerHour, \"Deaths\": deathsPerHour,\r\n \"TotalExperience\": totalExperiencePerHour, \"Stamina\": experiencePerHour[\"Stamina\"],\r\n \"Intelligence\": experiencePerHour[\"Intelligence\"], \"Attack\": experiencePerHour[\"Attack\"],\r\n \"Magic\": experiencePerHour[\"Magic\"], \"Ranged\": experiencePerHour[\"Ranged\"],\r\n \"Melee\": experiencePerHour[\"Melee\"], \"Defense\": experiencePerHour[\"Defense\"],\r\n \"noRngRevenue\": noRngRevenue,\r\n \"expenses\": expenses,\r\n \"noRngProfit\": noRngProfit\r\n };\r\n displaySimResults.push(displaySimRow);\r\n }\r\n }\r\n return displaySimResults;\r\n}\r\n\r\nfunction fidDropAmount(dropAmount) {\r\n if (Number.isInteger(dropAmount)) return dropAmount;\r\n\r\n const intPart = Math.floor(dropAmount);\r\n const fracPart = dropAmount - intPart;\r\n return Math.random() < fracPart ? intPart + 1 : intPart;\r\n}\r\n\r\nfunction calcDropMaps(simResult, playerToDisplay) {\r\n let dropRateMultiplier = simResult.dropRateMultiplier[playerToDisplay];\r\n let rareFindMultiplier = simResult.rareFindMultiplier[playerToDisplay];\r\n let combatDropQuantity = simResult.combatDropQuantity[playerToDisplay];\r\n let debuffOnLevelGap = simResult.debuffOnLevelGap[playerToDisplay];\r\n\r\n let numberOfPlayers = simResult.numberOfPlayers;\r\n let monsters = Object.keys(simResult.deaths)\r\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\r\n .sort();\r\n\r\n const totalDropMap = new Map();\r\n const noRngTotalDropMap = new Map();\r\n for (const monster of monsters) {\r\n const dropMap = new Map();\r\n const rareDropMap = new Map();\r\n if (combatMonsterDetailMap[monster].dropTable) {\r\n for (const drop of combatMonsterDetailMap[monster].dropTable) {\r\n if (drop.minDifficultyTier > simResult.difficultyTier) {\r\n continue;\r\n }\r\n\r\n let multiplier = 1.0 + 0.1 * simResult.difficultyTier;\r\n let dropRate = Math.min(1.0, multiplier * (drop.dropRate + (drop.dropRatePerDifficultyTier ?? 0) * simResult.difficultyTier));\r\n if (dropRate <= 0) continue;\r\n\r\n dropMap.set(drop.itemHrid, { \"dropRate\": Math.min(1.0, dropRate * dropRateMultiplier), \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\r\n }\r\n if (combatMonsterDetailMap[monster].rareDropTable)\r\n for (const drop of combatMonsterDetailMap[monster].rareDropTable) {\r\n if (drop.minDifficultyTier > simResult.difficultyTier) {\r\n continue;\r\n }\r\n rareDropMap.set(drop.itemHrid, { \"dropRate\": drop.dropRate * rareFindMultiplier, \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\r\n }\r\n\r\n for (let dropObject of dropMap.values()) {\r\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\r\n\r\n }\r\n for (let dropObject of rareDropMap.values()) {\r\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\r\n }\r\n\r\n for (let i = 0; i < simResult.deaths[monster]; i++) {\r\n for (let dropObject of dropMap.values()) {\r\n let chance = Math.random();\r\n if (chance <= dropObject.dropRate / numberOfPlayers) {\r\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\r\n dropObject.number = dropObject.number + fidDropAmount(amount);\r\n }\r\n }\r\n for (let dropObject of rareDropMap.values()) {\r\n let chance = Math.random();\r\n if (chance <= dropObject.dropRate / numberOfPlayers) {\r\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\r\n dropObject.number = dropObject.number + fidDropAmount(amount);\r\n }\r\n }\r\n }\r\n for (let [name, dropObject] of dropMap.entries()) {\r\n if (totalDropMap.has(name)) {\r\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\r\n } else {\r\n totalDropMap.set(name, dropObject.number);\r\n }\r\n if (noRngTotalDropMap.has(name)) {\r\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\r\n } else {\r\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\r\n }\r\n }\r\n for (let [name, dropObject] of rareDropMap.entries()) {\r\n if (totalDropMap.has(name)) {\r\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\r\n } else {\r\n totalDropMap.set(name, dropObject.number);\r\n }\r\n if (noRngTotalDropMap.has(name)) {\r\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\r\n } else {\r\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return { totalDropMap, noRngTotalDropMap };\r\n}\r\n\r\nfunction getDropProfit(simResult, playerToDisplay) {\r\n let { totalDropMap, noRngTotalDropMap } = calcDropMaps(simResult, playerToDisplay);\r\n\r\n let noRngTotal = 0;\r\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\r\n let price = -1;\r\n let revenueSetting = document.getElementById('selectPrices_drops').value;\r\n if (window.prices) {\r\n let item = window.prices[name];\r\n if (item) {\r\n if (revenueSetting == 'bid') {\r\n if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n } else if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n }\r\n } else if (revenueSetting == 'ask') {\r\n if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n } else if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n }\r\n }\r\n if (price == -1) {\r\n price = item['vendor'];\r\n }\r\n }\r\n }\r\n noRngTotal += price * dropAmount;\r\n }\r\n\r\n let consumablesUsed = simResult.consumablesUsed?.[playerToDisplay];\r\n\r\n if (consumablesUsed) {\r\n consumablesUsed = Object.entries(consumablesUsed).sort((a, b) => b[1] - a[1]);\r\n } else {\r\n consumablesUsed = [];\r\n }\r\n\r\n let expenses = 0;\r\n for (const [consumable, amount] of consumablesUsed) {\r\n let price = -1;\r\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\r\n if (window.prices) {\r\n let item = window.prices[consumable];\r\n if (item) {\r\n if (expensesSetting == 'bid') {\r\n if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n } else if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n }\r\n } else if (expensesSetting == 'ask') {\r\n if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n } else if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n }\r\n }\r\n if (price == -1) {\r\n price = item['vendor'];\r\n }\r\n }\r\n }\r\n expenses += price * amount;\r\n }\r\n\r\n simResult[\"noRngRevenue\"] = (noRngTotal).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n simResult[\"expenses\"] = (expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n simResult[\"noRngProfit\"] = (noRngTotal - expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n}\r\n\r\nfunction updateAllSimsModal(data) {\r\n const tableBody = document.getElementById('allZonesData').getElementsByTagName('tbody')[0];\r\n tableBody.innerHTML = '';\r\n data.forEach(item => {\r\n const row = document.createElement('tr');\r\n\r\n Object.keys(item).forEach(key => {\r\n const cell = document.createElement('td');\r\n cell.textContent = item[key];\r\n if (key === 'ZoneName') {\r\n cell.setAttribute(\"data-i18n\", \"actionNames.\" + item[key]);\r\n }\r\n row.appendChild(cell);\r\n });\r\n\r\n tableBody.appendChild(row);\r\n });\r\n\r\n const table = document.getElementById('allZonesData');\r\n const rows = table.getElementsByTagName('tr');\r\n const numCols = rows[0].cells.length;\r\n\r\n // 遍历每一列\r\n for (let col = 5; col < numCols; col++) {\r\n let max = -Infinity;\r\n let maxCell = null;\r\n\r\n // 找到最大值及其单元格\r\n for (let row = 1; row < rows.length; row++) {\r\n const cell = rows[row].cells[col];\r\n const value = parseFloat(cell.textContent.replace(/,/g, ''));\r\n if (value > max) {\r\n max = value;\r\n maxCell = cell;\r\n }\r\n }\r\n\r\n // 将最大值单元格的背景色设置为绿色\r\n if (maxCell && max != 0) {\r\n maxCell.style.backgroundColor = 'green';\r\n maxCell.style.color = 'white'; // 设置文字颜色为白色以提高可读性\r\n }\r\n }\r\n}\r\n\r\nlet currentSortColumn = null;\r\nlet currentSortDirection = 'desc';\r\n\r\nfunction sortTable(tableId, columnIndex, direction) {\r\n const table = document.getElementById(tableId);\r\n const tbody = table.querySelector('tbody');\r\n const rows = Array.from(tbody.querySelectorAll('tr'));\r\n\r\n const sortedRows = rows.sort((rowA, rowB) => {\r\n const cellA = rowA.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\r\n const cellB = rowB.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\r\n\r\n const valueA = parseFloat(cellA.replace(/,/g, ''));\r\n const valueB = parseFloat(cellB.replace(/,/g, ''));\r\n\r\n return direction === 'asc' ? valueA - valueB : valueB - valueA;\r\n });\r\n\r\n sortedRows.forEach(row => tbody.appendChild(row));\r\n updateSortIndicators(tableId, columnIndex, direction);\r\n}\r\n\r\nfunction updateSortIndicators(tableId, columnIndex, direction) {\r\n const headers = document.querySelectorAll(`#${tableId} th`);\r\n headers.forEach((header, index) => {\r\n header.classList.remove('sort-asc', 'sort-desc');\r\n if (index === columnIndex) {\r\n header.classList.add(direction === 'asc' ? 'sort-asc' : 'sort-desc');\r\n }\r\n });\r\n}\r\n\r\ndocument.querySelectorAll('#allZonesData th').forEach((header, index) => {\r\n if (index === 0) return;\r\n if (index === 1) return;\r\n if (index === 2) return;\r\n\r\n header.addEventListener('click', () => {\r\n if (currentSortColumn === index) {\r\n currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';\r\n } else {\r\n currentSortColumn = index;\r\n currentSortDirection = 'desc';\r\n }\r\n sortTable('allZonesData', currentSortColumn, currentSortDirection);\r\n });\r\n});\r\n\r\ndocument.getElementById('buttonExportResults').addEventListener('click', function () {\r\n var table = document.getElementById('allZonesData');\r\n var csv = [];\r\n var rows = table.querySelectorAll('tr');\r\n\r\n for (var i = 0; i < rows.length; i++) {\r\n var row = rows[i];\r\n var cols = row.querySelectorAll('th, td');\r\n var csvRow = [];\r\n\r\n cols.forEach(function (col) {\r\n csvRow.push('\"' + col.innerText.replace(/\"/g, '\"\"') + '\"');\r\n });\r\n\r\n csv.push(csvRow.join(','));\r\n }\r\n\r\n var csvFile = new Blob([csv.join('\\n')], { type: 'text/csv' });\r\n var downloadLink = document.createElement('a');\r\n downloadLink.download = 'simData.csv';\r\n downloadLink.href = URL.createObjectURL(csvFile);\r\n downloadLink.style.display = 'none';\r\n document.body.appendChild(downloadLink);\r\n downloadLink.click();\r\n document.body.removeChild(downloadLink);\r\n});\r\n\r\nfunction showKills(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultKills\");\r\n let dropsResultDiv = document.getElementById(\"simulationResultDrops\");\r\n let noRngDropsResultDiv = document.getElementById(\"noRngDrops\");\r\n let newChildren = [];\r\n let newDropChildren = [];\r\n let newNoRngDropChildren = [];\r\n\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let encountersPerHour = 0;\r\n let encountersRow = null;\r\n if (simResult.isDungeon) {\r\n let wavesCompletedRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Wave Reached\", simResult.maxWaveReached]);\r\n wavesCompletedRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxWaveReached\");\r\n newChildren.push(wavesCompletedRow);\r\n let completedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Completed Dungeons\", simResult.dungeonsCompleted]);\r\n completedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsCompleted\");\r\n newChildren.push(completedDungeonsRow);\r\n if (simResult.dungeonsFailed > 0) {\r\n let failedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Failed Dungeons\", simResult.dungeonsFailed]);\r\n failedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsFailed\");\r\n newChildren.push(failedDungeonsRow);\r\n }\r\n encountersPerHour = (simResult.dungeonsCompleted / hoursSimulated).toFixed(1);\r\n let averageTime = (hoursSimulated * 60 / simResult.dungeonsCompleted).toFixed(1);\r\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Average Time\", averageTime]);\r\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.averageTime\");\r\n if (simResult.minDungenonTime > 0) {\r\n let minimumTime = (simResult.minDungenonTime / ONE_SECOND / 60).toFixed(1);\r\n let minimumTimeRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Minimum Time\", minimumTime]);\r\n minimumTimeRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.minimumTime\");\r\n newChildren.push(minimumTimeRow);\r\n }\r\n } else {\r\n encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\r\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Encounters\", encountersPerHour]);\r\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.encounters\");\r\n }\r\n\r\n if (simResult.maxEnrageStack > 0) {\r\n let enrageRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Enrage Stack\", simResult.maxEnrageStack]);\r\n enrageRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxEnrageStack\");\r\n newChildren.push(enrageRow);\r\n }\r\n\r\n if (simResult.debuffOnLevelGap[playerToDisplay] != 0) {\r\n let debuffOnLevelGapRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Debuff on Level Gap\", Math.round(simResult.debuffOnLevelGap[playerToDisplay] * 100) + \"%\"]);\r\n debuffOnLevelGapRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.debuffOnLevelGap\");\r\n newChildren.push(debuffOnLevelGapRow);\r\n }\r\n\r\n newChildren.push(encountersRow);\r\n\r\n Object.keys(simResult.deaths)\r\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\r\n .sort()\r\n .forEach(monster => {\r\n let killsPerHour = (simResult.deaths[monster] / hoursSimulated).toFixed(1);\r\n let monsterRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [combatMonsterDetailMap[monster].name, killsPerHour]\r\n );\r\n monsterRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + monster);\r\n newChildren.push(monsterRow);\r\n });\r\n\r\n let { totalDropMap, noRngTotalDropMap } = !simResult.isDungeon ? calcDropMaps(simResult, playerToDisplay) : {totalDropMap:new Map(), noRngTotalDropMap:new Map()};\r\n\r\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\r\n let total = 0;\r\n for (let [name, dropAmount] of totalDropMap.entries()) {\r\n let dropRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [name, dropAmount.toLocaleString()]\r\n );\r\n dropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\r\n newDropChildren.push(dropRow);\r\n\r\n let tableRow = ' tbody\");\r\n let noRngTotal = 0;\r\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\r\n let noRngDropRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [name, dropAmount.toLocaleString()]\r\n );\r\n noRngDropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\r\n newNoRngDropChildren.push(noRngDropRow);\r\n\r\n let tableRow = ' prev + cur, 0);\r\n }\r\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\r\n let totalRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Total\", totalExperiencePerHour]);\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"].forEach((skill) => {\r\n let experience = simResult.experienceGained[playerToDisplay]?.[skill.toLowerCase()] ?? 0;\r\n if (experience == 0) {\r\n return;\r\n }\r\n let experiencePerHour = (experience / hoursSimulated).toFixed(0);\r\n let experienceRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [skill, experiencePerHour]);\r\n experienceRow.firstElementChild.setAttribute(\"data-i18n\", \"leaderboardCategoryNames.\" + skill.toLowerCase());\r\n newChildren.push(experienceRow);\r\n });\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showHpSpent(simResult, playerToDisplay) {\r\n let hpSpentHeadingDiv = document.getElementById(\"simulationHpSpentHeading\");\r\n hpSpentHeadingDiv.classList.add(\"d-none\");\r\n let hpSpentDiv = document.getElementById(\"simulationHpSpent\");\r\n hpSpentDiv.classList.add(\"d-none\");\r\n\r\n if (simResult.hitpointsSpent[playerToDisplay]) {\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let hpSpentSources = [];\r\n for (const source of Object.keys(simResult.hitpointsSpent[playerToDisplay])) {\r\n let hpSpentPerHour = (simResult.hitpointsSpent[playerToDisplay][source] / hoursSimulated).toFixed(2);\r\n let hpSpentRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [abilityDetailMap[source].name, hpSpentPerHour]);\r\n hpSpentRow.firstElementChild.setAttribute(\"data-i18n\", \"abilityNames.\" + source);\r\n hpSpentSources.push(hpSpentRow);\r\n }\r\n hpSpentDiv.replaceChildren(...hpSpentSources);\r\n hpSpentHeadingDiv.classList.remove(\"d-none\");\r\n hpSpentDiv.classList.remove(\"d-none\");\r\n }\r\n}\r\n\r\nfunction showConsumablesUsed(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultConsumablesUsed\");\r\n let newChildren = [];\r\n\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n\r\n if (!simResult.consumablesUsed[playerToDisplay]) {\r\n resultDiv.replaceChildren(...newChildren);\r\n window.expenses = 0;\r\n return;\r\n }\r\n\r\n let consumablesUsed = Object.entries(simResult.consumablesUsed[playerToDisplay]).sort((a, b) => b[1] - a[1]);\r\n\r\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\r\n let total = 0;\r\n for (const [consumable, amount] of consumablesUsed) {\r\n let consumablesPerHour = (amount / hoursSimulated).toFixed(0);\r\n let consumableRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [itemDetailMap[consumable].name, consumablesPerHour]\r\n );\r\n consumableRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + consumable);\r\n newChildren.push(consumableRow);\r\n\r\n let tableRow = ' b[1] - a[1]);\r\n\r\n let totalHitpointsGained = hitpointsGained.reduce((prev, cur) => prev + cur[1], 0);\r\n let totalHitpointsPerSecond = (totalHitpointsGained / secondsSimulated).toFixed(2);\r\n let totalRow = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [\"Total\", totalHitpointsPerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [source, amount] of hitpointsGained) {\r\n if (amount == 0) {\r\n continue;\r\n }\r\n\r\n let sourceText;\r\n let sourceFullHrid;\r\n switch (source) {\r\n case \"regen\":\r\n sourceText = \"Regen\";\r\n sourceFullHrid = \"combatStats.hpRegenPer10\";\r\n break;\r\n case \"lifesteal\":\r\n sourceText = \"Life Steal\";\r\n sourceFullHrid = \"combatStats.lifeSteal\";\r\n break;\r\n case \"bloom\":\r\n sourceText = \"Bloom\";\r\n sourceFullHrid = \"combatStats.bloom\";\r\n break;\r\n default:\r\n if (itemDetailMap[source]) {\r\n sourceText = itemDetailMap[source].name;\r\n sourceFullHrid = \"itemNames.\" + source;\r\n } else if (abilityDetailMap[source]) {\r\n sourceText = abilityDetailMap[source].name;\r\n sourceFullHrid = \"abilityNames.\" + source;\r\n }\r\n break;\r\n }\r\n let hitpointsPerSecond = (amount / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * amount) / totalHitpointsGained).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [sourceText, hitpointsPerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showManapointsGained(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultManaRestored\");\r\n let newChildren = [];\r\n\r\n let secondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n if (!simResult.manapointsGained[playerToDisplay]) {\r\n resultDiv.replaceChildren(...newChildren);\r\n return;\r\n }\r\n\r\n let manapointsGained = Object.entries(simResult.manapointsGained[playerToDisplay]).sort((a, b) => b[1] - a[1]);\r\n\r\n let totalManapointsGained = manapointsGained.reduce((prev, cur) => prev + cur[1], 0);\r\n let totalManapointsPerSecond = (totalManapointsGained / secondsSimulated).toFixed(2);\r\n let totalRow = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [\"Total\", totalManapointsPerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [source, amount] of manapointsGained) {\r\n if (amount == 0) {\r\n continue;\r\n }\r\n\r\n let sourceText;\r\n let sourceFullHrid;\r\n switch (source) {\r\n case \"regen\":\r\n sourceText = \"Regen\";\r\n sourceFullHrid = \"combatStats.mpRegenPer10\";\r\n break;\r\n case \"manaLeech\":\r\n sourceText = \"Mana Leech\";\r\n sourceFullHrid = \"combatStats.manaLeech\";\r\n break;\r\n case \"ripple\":\r\n sourceText = \"Ripple\";\r\n sourceFullHrid = \"combatStats.ripple\";\r\n break;\r\n default:\r\n sourceText = itemDetailMap[source].name;\r\n sourceFullHrid = \"itemNames.\" + source;\r\n break;\r\n }\r\n let manapointsPerSecond = (amount / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * amount) / totalManapointsGained).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [sourceText, manapointsPerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n let ranOutOfManaText = simResult.playerRanOutOfMana[playerToDisplay] ? \"Yes\" : \"No\";\r\n let ranOutOfManaRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Ran out of mana\", ranOutOfManaText]);\r\n ranOutOfManaRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfMana\");\r\n ranOutOfManaRow.lastElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.\" + ranOutOfManaText);\r\n newChildren.push(ranOutOfManaRow);\r\n\r\n if (simResult.playerRanOutOfMana[playerToDisplay]) {\r\n let ranOutOfManaStat = simResult.playerRanOutOfManaTime[playerToDisplay]; // {isOutOfMana: false, startTimeForOutOfMana:0, totalTimeForOutOfMana:0};\r\n let totalTimeForOut = ranOutOfManaStat.totalTimeForOutOfMana + (ranOutOfManaStat.isOutOfMana ? (simResult.simulatedTime - ranOutOfManaStat.startTimeForOutOfMana) : 0);\r\n\r\n let ranOutOfManaStatRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [\r\n \"Run Out Ratio\",\r\n (totalTimeForOut / simResult.simulatedTime * 100).toFixed(2) + \"%\"\r\n ]\r\n );\r\n ranOutOfManaStatRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfManaRatio\");\r\n newChildren.push(ranOutOfManaStatRow);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showDamageDone(simResult, playerToDisplay) {\r\n let totalDamageDone = {};\r\n let enemyIndex = 1;\r\n\r\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n for (let i = 1; i < 64; i++) {\r\n let accordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + i);\r\n hideElement(accordion);\r\n }\r\n\r\n let bossTimeHeadingDiv = document.getElementById(\"simulationBossTimeHeading\");\r\n bossTimeHeadingDiv.classList.add(\"d-none\");\r\n let bossTimeDiv = document.getElementById(\"simulationBossTime\");\r\n bossTimeDiv.classList.add(\"d-none\");\r\n\r\n if (!simResult.attacks[playerToDisplay]) {\r\n return;\r\n }\r\n\r\n for (const [target, abilities] of Object.entries(simResult.attacks[playerToDisplay])) {\r\n let targetDamageDone = {};\r\n\r\n const i = simResult.timeSpentAlive.findIndex(e => e.name === target);\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\r\n\r\n for (const [ability, abilityCasts] of Object.entries(abilities)) {\r\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\r\n let misses = abilityCasts[\"miss\"] ?? 0;\r\n let damage = Object.entries(abilityCasts)\r\n .filter((entry) => entry[0] != \"miss\")\r\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\r\n\r\n targetDamageDone[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n if (totalDamageDone[ability]) {\r\n totalDamageDone[ability].casts += casts;\r\n totalDamageDone[ability].misses += misses;\r\n totalDamageDone[ability].damage += damage;\r\n } else {\r\n totalDamageDone[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n }\r\n }\r\n\r\n let resultDiv = document.getElementById(\"simulationResultDamageDoneEnemy\" + enemyIndex);\r\n createDamageTable(resultDiv, targetDamageDone, aliveSecondsSimulated);\r\n\r\n let resultAccordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + enemyIndex);\r\n showElement(resultAccordion);\r\n\r\n let resultAccordionButton = document.getElementById(\r\n \"buttonSimulationResultDamageDoneAccordionEnemy\" + enemyIndex\r\n );\r\n let targetName = combatMonsterDetailMap[target].name;\r\n resultAccordionButton.innerHTML = \"Damage Done (\" + \"\" + targetName + \"\" + \")\";\r\n\r\n if (simResult.bossSpawns.includes(target)) {\r\n let hoursSpentOnBoss = (aliveSecondsSimulated / 60 / 60).toFixed(2);\r\n let percentSpentOnBoss = (aliveSecondsSimulated / totalSecondsSimulated * 100).toFixed(2);\r\n\r\n let bossRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [targetName, hoursSpentOnBoss + \"h(\" + percentSpentOnBoss + \"%)\"]);\r\n bossRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + target);\r\n bossTimeDiv.replaceChildren(bossRow);\r\n\r\n bossTimeHeadingDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.classList.remove(\"d-none\");\r\n }\r\n\r\n enemyIndex++;\r\n }\r\n\r\n if (simResult.isDungeon) {\r\n let newChildren = [];\r\n for (const waveName of simResult.bossSpawns) {\r\n // waveName is something like \"#15,/monsters/jackalope,/monsters/butterjerry\"\r\n let waveNumber = waveName.split(\",\")[0];\r\n const idx = simResult.timeSpentAlive.findIndex(e => e.name === waveNumber);\r\n if (idx == -1 || simResult.timeSpentAlive[idx].count == 0) {\r\n continue;\r\n }\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[idx].timeSpentAlive / ONE_SECOND / simResult.timeSpentAlive[idx].count;\r\n let bossRow = createRow([\"col-md-6\", \"col-md-2\", \"col-md-4 text-end\"], [waveNumber, simResult.timeSpentAlive[idx].count, aliveSecondsSimulated.toFixed(1) + \"s\"]);\r\n newChildren.push(bossRow);\r\n }\r\n if (newChildren.length > 0) {\r\n bossTimeHeadingDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.replaceChildren(...newChildren);\r\n }\r\n }\r\n\r\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageDone\");\r\n createDamageTable(totalResultDiv, totalDamageDone, totalSecondsSimulated);\r\n}\r\n\r\nfunction showDamageTaken(simResult, playerToDisplay) {\r\n let totalDamageTaken = {};\r\n let enemyIndex = 1;\r\n\r\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n for (let i = 1; i < 64; i++) {\r\n let accordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + i);\r\n hideElement(accordion);\r\n }\r\n\r\n for (const [source, targets] of Object.entries(simResult.attacks)) {\r\n const validSources = [\"player1\", \"player2\", \"player3\", \"player4\", \"player5\"];\r\n if (validSources.includes(source)) {\r\n continue;\r\n }\r\n const i = simResult.timeSpentAlive.findIndex(e => e.name === source);\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\r\n let sourceDamageTaken = {};\r\n if (targets[playerToDisplay] && Object.keys(targets[playerToDisplay]).length > 0) {\r\n for (const [ability, abilityCasts] of Object.entries(targets[playerToDisplay])) {\r\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\r\n let misses = abilityCasts[\"miss\"] ?? 0;\r\n let damage = Object.entries(abilityCasts)\r\n .filter((entry) => entry[0] != \"miss\")\r\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\r\n\r\n sourceDamageTaken[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n if (totalDamageTaken[ability]) {\r\n totalDamageTaken[ability].casts += casts;\r\n totalDamageTaken[ability].misses += misses;\r\n totalDamageTaken[ability].damage += damage;\r\n } else {\r\n totalDamageTaken[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n }\r\n }\r\n }\r\n\r\n let resultDiv = document.getElementById(\"simulationResultDamageTakenEnemy\" + enemyIndex);\r\n createDamageTable(resultDiv, sourceDamageTaken, aliveSecondsSimulated);\r\n\r\n let resultAccordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + enemyIndex);\r\n showElement(resultAccordion);\r\n\r\n let resultAccordionButton = document.getElementById(\r\n \"buttonSimulationResultDamageTakenAccordionEnemy\" + enemyIndex\r\n );\r\n let sourceName = combatMonsterDetailMap[source].name;\r\n resultAccordionButton.innerHTML = \"Damage Taken (\" + \"\" + sourceName + \"\" + \")\";\r\n\r\n enemyIndex++;\r\n }\r\n\r\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageTaken\");\r\n createDamageTable(totalResultDiv, totalDamageTaken, totalSecondsSimulated);\r\n}\r\n\r\nfunction createDamageTable(resultDiv, damageDone, secondsSimulated) {\r\n let newChildren = [];\r\n\r\n let sortedDamageDone = Object.entries(damageDone).sort((a, b) => b[1].damage - a[1].damage);\r\n\r\n let totalCasts = sortedDamageDone.reduce((prev, cur) => prev + cur[1].casts, 0);\r\n let totalMisses = sortedDamageDone.reduce((prev, cur) => prev + cur[1].misses, 0);\r\n let totalDamage = sortedDamageDone.reduce((prev, cur) => prev + cur[1].damage, 0);\r\n let totalHitChance = ((100 * (totalCasts - totalMisses)) / totalCasts).toFixed(1);\r\n let totalDamagePerSecond = (totalDamage / secondsSimulated).toFixed(2);\r\n\r\n let totalRow = createRow(\r\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\r\n [\"Total\", totalHitChance + \"%\", totalDamagePerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [ability, damageInfo] of sortedDamageDone) {\r\n let abilityText;\r\n let abilityFullHrid;\r\n switch (ability) {\r\n case \"autoAttack\":\r\n abilityText = \"Auto Attack\";\r\n abilityFullHrid = \"combatUnit.autoAttack\";\r\n break;\r\n case \"parry\":\r\n abilityText = \"Parry Attack\";\r\n abilityFullHrid = \"common:simulationResults.parryAttack\";\r\n break;\r\n case \"damageOverTime\":\r\n abilityText = \"Damage Over Time\";\r\n abilityFullHrid = \"common:simulationResults.damageOverTime\";\r\n break;\r\n case \"physicalThorns\":\r\n abilityText = \"Physical Thorns\";\r\n abilityFullHrid = \"combatStats.physicalThorns\";\r\n break;\r\n case \"elementalThorns\":\r\n abilityText = \"Elemental Thorns\";\r\n abilityFullHrid = \"combatStats.elementalThorns\";\r\n break;\r\n case \"retaliation\":\r\n abilityText = \"Retaliation\";\r\n abilityFullHrid = \"combatStats.retaliation\";\r\n break;\r\n case 'blaze':\r\n abilityText = \"Blaze\";\r\n abilityFullHrid = \"combatStats.blaze\";\r\n break;\r\n default:\r\n abilityText = abilityDetailMap[ability].name;\r\n abilityFullHrid = \"abilityNames.\" + ability;\r\n break;\r\n }\r\n\r\n let hitChance = ((100 * (damageInfo.casts - damageInfo.misses)) / damageInfo.casts).toFixed(1);\r\n let damagePerSecond = (damageInfo.damage / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * damageInfo.damage) / totalDamage).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\r\n [abilityText, hitChance + \"%\", damagePerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", abilityFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction createRow(columnClassNames, columnValues) {\r\n let row = createElement(\"div\", \"row\");\r\n\r\n for (let i = 0; i < columnClassNames.length; i++) {\r\n let column = createElement(\"div\", columnClassNames[i], columnValues[i]);\r\n row.appendChild(column);\r\n }\r\n\r\n return row;\r\n}\r\n\r\nfunction createElement(tagName, className, innerHTML = \"\", id = \"\") {\r\n let element = document.createElement(tagName);\r\n element.className = className;\r\n element.innerHTML = innerHTML;\r\n if (id) element.id = id;\r\n return element;\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Simulation Controls\r\n\r\ndocument.addEventListener('DOMContentLoaded', function () {\r\n const simDungeonToggle = document.getElementById('simDungeonToggle');\r\n const playerContainer = document.getElementById('playerCheckBox');\r\n\r\n function addPlayers() {\r\n const player4 = document.createElement('div');\r\n player4.classList.add('form-check');\r\n player4.innerHTML = `\r\n \r\n \r\n `;\r\n\r\n const player5 = document.createElement('div');\r\n player5.classList.add('form-check');\r\n player5.innerHTML = `\r\n \r\n \r\n `;\r\n\r\n playerContainer.appendChild(player4);\r\n playerContainer.appendChild(player5);\r\n }\r\n\r\n function removePlayers() {\r\n const player4 = document.getElementById('player4');\r\n const player5 = document.getElementById('player5');\r\n if (player4) player4.parentElement.remove();\r\n if (player5) player5.parentElement.remove();\r\n }\r\n\r\n function updatePlayerNames() {\r\n const tabLinks = document.querySelectorAll('#playerTab .nav-link');\r\n tabLinks.forEach((tabLink, index) => {\r\n const label = document.querySelector(`label[for=\"player${index + 1}\"]`);\r\n if (label) {\r\n label.textContent = tabLink.textContent.trim();\r\n }\r\n });\r\n }\r\n\r\n function updatePlayersCheckbox(isCheck) {\r\n const boxes = playerContainer.querySelectorAll('.player-checkbox');\r\n boxes.forEach((checkBox) => { checkBox.checked = isCheck });\r\n }\r\n\r\n function updateDifficultySelect(isCheck) {\r\n const difficultySelect = document.getElementById('selectDifficulty');\r\n // disable last four option\r\n if (isCheck && Number(difficultySelect.value) >= 3) {\r\n difficultySelect.value = 0;\r\n }\r\n for (let i = 3; i < difficultySelect.options.length; i++) {\r\n difficultySelect.options[i].disabled = isCheck;\r\n }\r\n }\r\n\r\n simDungeonToggle.addEventListener('change', function () {\r\n if (simDungeonToggle.checked) {\r\n addPlayers();\r\n updatePlayersCheckbox(true);\r\n updateDifficultySelect(true);\r\n } else {\r\n removePlayers();\r\n updatePlayersCheckbox(false);\r\n updateDifficultySelect(false);\r\n }\r\n updatePlayerNames();\r\n });\r\n\r\n document.getElementById('buttonSimulationSetup').addEventListener('click', function () {\r\n updatePlayerNames();\r\n });\r\n});\r\n\r\nfunction onTabChange(event) {\r\n const nextPlayerTabId = event.target.getAttribute('href').substring(7);\r\n savePreviousPlayer(currentPlayerTabId);\r\n updateNextPlayer(nextPlayerTabId);\r\n currentPlayerTabId = nextPlayerTabId;\r\n updateState();\r\n updateUI();\r\n if (Object.keys(currentSimResults).length !== 0) {\r\n showSimulationResult(currentSimResults);\r\n }\r\n\r\n updateContent();\r\n}\r\n\r\ndocument.querySelectorAll('#playerTab .nav-link').forEach(tab => {\r\n tab.addEventListener('shown.bs.tab', onTabChange);\r\n});\r\n\r\nfunction initSimulationControls() {\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n simulationTimeInput.value = 24;\r\n\r\n buttonStartSimulation.addEventListener(\"click\", (event) => {\r\n let invalidElements = document.querySelectorAll(\":invalid\");\r\n if (invalidElements.length > 0) {\r\n invalidElements.forEach((element) => element.reportValidity());\r\n return;\r\n }\r\n savePreviousPlayer(currentPlayerTabId);\r\n\r\n const simDungeonToggle = document.getElementById(\"simDungeonToggle\");\r\n const checkboxes = document.querySelectorAll('.player-checkbox');\r\n selectedPlayers = [];\r\n checkboxes.forEach(checkbox => {\r\n if (checkbox.checked) {\r\n const playerNumber = parseInt(checkbox.id.replace('player', ''));\r\n selectedPlayers.push(playerNumber);\r\n }\r\n });\r\n\r\n if (selectedPlayers.length === 0) {\r\n alert(\"You need to select at least one player to sim.\");\r\n return;\r\n }\r\n // buttonStartSimulation.disabled = true;\r\n buttonStopSimulation.style.display = 'block';\r\n startSimulation(selectedPlayers);\r\n });\r\n\r\n buttonStopSimulation.style.display = 'none';\r\n buttonStopSimulation.addEventListener(\"click\", (event) => {\r\n progressbar.style.width = \"0%\";\r\n progressbar.innerHTML = \"0%\";\r\n if (worker) {\r\n worker.terminate();\r\n }\r\n worker = new Worker(new URL(\"worker.js\", import.meta.url));\r\n\r\n if (multiWorker) {\r\n multiWorker.terminate();\r\n }\r\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n\r\n for (let worker of workerPool) {\r\n worker.worker.terminate();\r\n }\r\n\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n });\r\n}\r\n\r\nfunction startSimulation(selectedPlayers) {\r\n let playersToSim = [];\r\n for (let j = 1; j < 6; j++) {\r\n if (selectedPlayers.includes(j)) {\r\n updateNextPlayer(j);\r\n updateState();\r\n updateUI();\r\n player.hrid = \"player\" + j.toString();\r\n for (let i = 0; i < 3; i++) {\r\n if (food[i] && i < player.combatDetails.combatStats.foodSlots) {\r\n let consumable = new Consumable(food[i], triggerMap[food[i]]);\r\n player.food[i] = consumable;\r\n } else {\r\n player.food[i] = null;\r\n }\r\n\r\n if (drinks[i] && i < player.combatDetails.combatStats.drinkSlots) {\r\n let consumable = new Consumable(drinks[i], triggerMap[drinks[i]]);\r\n player.drinks[i] = consumable;\r\n } else {\r\n player.drinks[i] = null;\r\n }\r\n }\r\n\r\n for (let i = 0; i < 5; i++) {\r\n if (abilities[i] && player.intelligenceLevel >= abilitySlotsLevelRequirementList[i + 1]) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let ability = new Ability(abilities[i], Number(abilityLevelInput.value), triggerMap[abilities[i]]);\r\n player.abilities[i] = ability;\r\n } else {\r\n player.abilities[i] = null;\r\n }\r\n }\r\n\r\n playersToSim.push(structuredClone(player));\r\n }\r\n }\r\n updateNextPlayer(currentPlayerTabId);\r\n updateState();\r\n updateUI();\r\n\r\n let maxPlayerCombatLevel = 1;\r\n for (let player of playersToSim) {\r\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\r\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\r\n }\r\n\r\n for (let player of playersToSim) {\r\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\r\n const maxDebuffOnLevelGap = 0.9;\r\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\r\n\r\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\r\n\r\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\r\n }\r\n else {\r\n player.debuffOnLevelGap = 0;\r\n }\r\n }\r\n\r\n let extra = {};\r\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\r\n extra.comExp = 0;\r\n if (document.getElementById(\"comExpToggle\").checked) {\r\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\r\n }\r\n extra.comDrop = 0;\r\n if (document.getElementById(\"comDropToggle\").checked) {\r\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\r\n }\r\n\r\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\r\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\r\n let simDungeonToggle = document.getElementById(\"simDungeonToggle\");\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let dungeonSelect = document.getElementById(\"selectDungeon\");\r\n let difficultySelect = document.getElementById(\"selectDifficulty\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let simulationTimeLimit = Number(simulationTimeInput.value) * ONE_HOUR;\r\n buttonStopSimulation.style.display = 'block';\r\n if (!simAllZonesToggle.checked && !simAllSoloToggle.checked) {\r\n let zoneHrid = zoneSelect.value;\r\n let difficultyTier = Number(difficultySelect.value);\r\n if (simDungeonToggle.checked) {\r\n zoneHrid = dungeonSelect.value;\r\n }\r\n let workerMessage = {\r\n type: \"start_simulation\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n simStartTime = Date.now();\r\n if (!worker) {\r\n worker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n }\r\n worker.onmessage = onWorkerMessage;\r\n worker.postMessage(workerMessage);\r\n } else {\r\n let targetHrids = {};\r\n\r\n if (simAllZonesToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1 &&\r\n document.getElementById(a.hrid)?.checked\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n if (simAllSoloToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount === 1 &&\r\n document.getElementById(a.hrid)?.checked\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n let simHrids = Object.values(targetHrids)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .map(action => {\r\n let result = [];\r\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\r\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\r\n }\r\n return result;\r\n })\r\n .flat();\r\n\r\n let workerMessage = {\r\n type: \"start_simulation_all_zones\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zones: simHrids,\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra: extra\r\n };\r\n simStartTime = Date.now();\r\n if (!multiWorker) {\r\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n }\r\n multiWorker.onmessage = onMultiWorkerMessage;\r\n multiWorker.postMessage(workerMessage);\r\n }\r\n}\r\n\r\nfunction parsePlayerJson(playerJson, hrid) {\r\n let playerData = {\r\n hrid: hrid,\r\n food: [],\r\n drinks: [],\r\n abilities: [],\r\n ...playerJson.player,\r\n houseRooms: playerJson.houseRooms,\r\n };\r\n playerData.equipment = {};\r\n const triggerMap = playerJson.triggerMap;\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"main_hand\", \"two_hand\", \"charm\"].forEach((type) => {\r\n let currentEquipment = playerJson.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment){\r\n playerData.equipment[`/equipment_types/${type}`] = new Equipment(currentEquipment.itemHrid, currentEquipment.enhancementLevel);\r\n }\r\n });\r\n\r\n for (const foodHrid of playerJson.food[\"/action_types/combat\"]) {\r\n if (foodHrid.itemHrid === \"\") continue;\r\n const food = new Consumable(foodHrid.itemHrid, triggerMap[foodHrid.itemHrid]);\r\n playerData.food.push(food);\r\n }\r\n for (const drinkHrid of playerJson.drinks[\"/action_types/combat\"]) {\r\n if (drinkHrid.itemHrid === \"\") continue;\r\n const drink = new Consumable(drinkHrid.itemHrid, triggerMap[drinkHrid.itemHrid]);\r\n playerData.drinks.push(drink);\r\n }\r\n for (const ability of playerJson.abilities) {\r\n if (ability.abilityHrid === \"\") continue;\r\n const abilityLevel = Number(ability.level);\r\n const abilityHrid = ability.abilityHrid;\r\n if (abilityLevel > 0) {\r\n const abilityObj = new Ability(abilityHrid, abilityLevel, triggerMap[abilityHrid]);\r\n playerData.abilities.push(abilityObj);\r\n }\r\n }\r\n const player = Player.createFromDTO(playerData)\r\n player.updateCombatDetails();\r\n player.houseRooms = playerJson.houseRooms;\r\n player.achievements = playerJson.achievements ?? {};\r\n return player;\r\n}\r\n// read JSON file to simulate\r\ndocument.getElementById(\"buttonUploadJSONSimulate\").addEventListener(\"click\", (event) => {\r\n let extra = {};\r\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\r\n extra.comExp = 0;\r\n if (document.getElementById(\"comExpToggle\").checked) {\r\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\r\n }\r\n extra.comDrop = 0;\r\n if (document.getElementById(\"comDropToggle\").checked) {\r\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\r\n }\r\n\r\n let fileInput = document.getElementById(\"inputUploadJSONSimulation\");\r\n let file = fileInput.files[0];\r\n if (!file) {\r\n alert(\"Please select a file to upload.\");\r\n return;\r\n }\r\n\r\n let reader = new FileReader();\r\n reader.onload = function (event) {\r\n let fileContent = event.target.result;\r\n const jsonDataList = JSON.parse(fileContent);\r\n try {\r\n const simDataList = [];\r\n for (const key in jsonDataList) {\r\n if (jsonDataList[key].cases) {\r\n const cases = getProductCases(jsonDataList[key], jsonDataList[key].cases);\r\n simDataList.push(...cases);\r\n } else {\r\n simDataList.push(jsonDataList[key]);\r\n }\r\n }\r\n for (const key in simDataList) {\r\n const jsonData = simDataList[key];\r\n if (!jsonData || !jsonData.zone || !jsonData.players) {\r\n alert(\"Invalid JSON file format. Please ensure it contains a 'simulationResult' property.\");\r\n return;\r\n }\r\n const playersToSim = Object.values(jsonData.players).map(\r\n (player, index) => parsePlayerJson(player, `player${index + 1}`)\r\n );\r\n\r\n let maxPlayerCombatLevel = 1;\r\n for (let player of playersToSim) {\r\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\r\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\r\n }\r\n\r\n for (let player of playersToSim) {\r\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\r\n const maxDebuffOnLevelGap = 0.9;\r\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\r\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\r\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\r\n }\r\n else {\r\n player.debuffOnLevelGap = 0;\r\n }\r\n }\r\n\r\n const simulationTimeLimit = (jsonData.simulationTimeLimit || 24) * ONE_HOUR;\r\n const simName = jsonData.name || `Json ${key}`;\r\n const zoneHrid = jsonData.zone;\r\n if (zoneHrid === \"all\") {\r\n let targetHrids = {};\r\n\r\n if (simAllZonesToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n let simHrids = Object.values(targetHrids)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .map(action => {\r\n let result = [];\r\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\r\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\r\n }\r\n return result;\r\n })\r\n .flat();\r\n\r\n let workerMessage = {\r\n simulationName: simName,\r\n type: \"start_simulation_all_zones\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zones: simHrids,\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \r\n worker.onmessage = mainWorkerOnMessage;\r\n worker.postMessage(workerMessage);\r\n customAlert(\"Simulation task Created\", \"info\")\r\n workerPool.push({\r\n workerId: workerMessage.workerId,\r\n worker: worker,\r\n });\r\n } else {\r\n let difficultyTier = jsonData.difficultyTier || 0;\r\n let workerMessage = {\r\n simulationName: simName,\r\n type: \"start_simulation\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \r\n worker.onmessage = mainWorkerOnMessage;\r\n worker.postMessage(workerMessage);\r\n customAlert(\"Simulation task Created\", \"info\")\r\n workerPool.push({\r\n workerId: workerMessage.workerId,\r\n worker: worker,\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n // alert(\"Error parsing JSON file: \" + error.message);\r\n customAlert(\"Error parsing JSON file: \" + error.message, \"danger\");\r\n }\r\n }\r\n reader.readAsText(file);\r\n});\r\n\r\n\r\n// #endregion\r\n\r\n// #region WipeEvents\r\n\r\nfunction renderWipeEvents(simResult) {\r\n const selector = document.getElementById('wipeEventSelector');\r\n const logsContainer = document.getElementById('wipeLogsContainer');\r\n const waveBadge = document.getElementById('wipeWaveBadge');\r\n const timeInfo = document.getElementById('wipeTimeInfo');\r\n\r\n selector.innerHTML = '';\r\n logsContainer.innerHTML = '';\r\n\r\n if (!simResult.wipeEvents || simResult.wipeEvents.length === 0) {\r\n selector.innerHTML = ``;\r\n logsContainer.innerHTML = `
No Wipe Events Detected
`;\r\n waveBadge.textContent = '';\r\n timeInfo.textContent = '';\r\n return;\r\n }\r\n\r\n simResult.wipeEvents.forEach((event, index) => {\r\n const wave = event.wave || '?';\r\n // const time = (event.simulationTime / 1e9).toFixed(2);\r\n // const timestamp = new Date(event.timestamp).toLocaleTimeString();\r\n\r\n const option = document.createElement('option');\r\n option.value = index;\r\n option.textContent = `#${index + 1} - 波次: ${wave}`;\r\n selector.appendChild(option);\r\n });\r\n\r\n selector.value = 0;\r\n renderSelectedWipeEvent(0, simResult);\r\n\r\n selector.addEventListener('change', () => {\r\n renderSelectedWipeEvent(selector.value, simResult);\r\n });\r\n}\r\n\r\n// 渲染选中的团灭事件\r\nfunction renderSelectedWipeEvent(index, simResult) {\r\n const logsContainer = document.getElementById('wipeLogsContainer');\r\n const waveBadge = document.getElementById('wipeWaveBadge');\r\n const timeInfo = document.getElementById('wipeTimeInfo');\r\n\r\n logsContainer.innerHTML = '';\r\n\r\n if (index < 0 || index >= simResult.wipeEvents.length) {\r\n logsContainer.innerHTML = `
No Wipe Events
`;\r\n waveBadge.textContent = '';\r\n timeInfo.textContent = '';\r\n return;\r\n }\r\n\r\n const wipeEvent = simResult.wipeEvents[index];\r\n const wave = wipeEvent.wave || '?';\r\n const time = (wipeEvent.simulationTime / 1e9).toFixed(2);\r\n const timestamp = new Date(wipeEvent.timestamp).toLocaleString();\r\n\r\n waveBadge.textContent = `波次: ${wave}`;\r\n timeInfo.textContent = `模拟时间: ${time}s | 记录时间: ${timestamp}`;\r\n\r\n const logsByTime = groupLogsByTime(wipeEvent.logs);\r\n\r\n const baseTime = logsByTime.length > 0 ? logsByTime[0].time : 0;\r\n\r\n logsByTime.forEach(group => {\r\n const timeGroupElement = document.createElement('div');\r\n timeGroupElement.className = 'log-time-group';\r\n\r\n const relativeTime = (group.time - baseTime) / 1e9;\r\n\r\n // 时间标题\r\n const timeHeader = document.createElement('div');\r\n timeHeader.className = 'log-time-header';\r\n timeHeader.textContent = `[${relativeTime.toFixed(2)}s] [Wave#${group.wave}]`;\r\n timeGroupElement.appendChild(timeHeader);\r\n\r\n // 事件列表\r\n const eventsList = document.createElement('div');\r\n eventsList.className = 'log-events';\r\n\r\n const damagedPlayers = new Set();\r\n\r\n group.logs.forEach(log => {\r\n const eventElement = document.createElement('div');\r\n eventElement.className = 'log-event';\r\n\r\n damagedPlayers.add(log.target);\r\n\r\n const sourceSpan = document.createElement('span');\r\n sourceSpan.className = 'log-source';\r\n if (log.ability === \"damageOverTime\") {\r\n sourceSpan.textContent = log.target;\r\n } else if(log.source == 'UNKNOWN_SOURCE') {\r\n sourceSpan.textContent = 'UNKNOWN';\r\n } else {\r\n sourceSpan.setAttribute('data-i18n', `monsterNames.${log.source}`);\r\n sourceSpan.textContent = log.source;\r\n }\r\n\r\n const castSpan = document.createElement('span');\r\n castSpan.className = 'log-cast';\r\n castSpan.setAttribute('data-i18n', `common:cast`);\r\n castSpan.textContent = ' cast ';\r\n\r\n const abilitySpan = document.createElement('span');\r\n abilitySpan.className = 'log-ability';\r\n if (log.ability === \"autoAttack\") {\r\n abilitySpan.setAttribute('data-i18n', 'combatUnit.autoAttack');\r\n abilitySpan.textContent = 'Auto Attack';\r\n } else if (log.ability === \"physicalThorns\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.physicalThorns`);\r\n abilitySpan.textContent = 'Physical Thorns';\r\n } else if (log.ability === \"elementalThorns\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.elementalThorns`);\r\n abilitySpan.textContent = 'Elemental Thorns';\r\n } else if (log.ability === \"retaliation\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.retaliation`);\r\n abilitySpan.textContent = 'Retaliation';\r\n } else if (log.ability === \"damageOverTime\") {\r\n abilitySpan.setAttribute('data-i18n', `common:simulationResults.damageOverTime`);\r\n abilitySpan.textContent = 'Damage Over Time';\r\n } else {\r\n abilitySpan.setAttribute('data-i18n', `abilityNames.${log.ability}`);\r\n abilitySpan.textContent = log.ability;\r\n }\r\n\r\n const toSpan = document.createElement('span');\r\n toSpan.className = 'log-to';\r\n toSpan.setAttribute('data-i18n', `common:to`);\r\n toSpan.textContent = ' to ';\r\n\r\n const targetSpan = document.createElement('span');\r\n targetSpan.className = 'log-target';\r\n targetSpan.textContent = log.target;\r\n\r\n const dealDamageSpan = document.createElement('span');\r\n dealDamageSpan.className = 'log-deal-damage';\r\n dealDamageSpan.setAttribute('data-i18n', `common:dealDamage`);\r\n dealDamageSpan.textContent = ' deal damage ';\r\n\r\n const damageDoneSpan = document.createElement('span');\r\n damageDoneSpan.className = 'log-damage-done';\r\n damageDoneSpan.textContent = log.damage;\r\n if (log.isCrit) {\r\n damageDoneSpan.style.fontWeight = 'bold';\r\n damageDoneSpan.textContent += '!!!';\r\n }\r\n\r\n eventElement.appendChild(sourceSpan);\r\n eventElement.appendChild(castSpan);\r\n eventElement.appendChild(abilitySpan);\r\n eventElement.appendChild(toSpan);\r\n eventElement.appendChild(targetSpan);\r\n eventElement.appendChild(dealDamageSpan);\r\n eventElement.appendChild(damageDoneSpan);\r\n eventElement.appendChild(document.createTextNode(` , HP ${log.beforeHp} → ${log.afterHp}`));\r\n\r\n eventsList.appendChild(eventElement);\r\n });\r\n\r\n timeGroupElement.appendChild(eventsList);\r\n\r\n const lastLog = group.logs[group.logs.length - 1];\r\n const playersHpElement = document.createElement('div');\r\n\r\n const playerHpTitle = document.createElement('span');\r\n playerHpTitle.className = 'log-players-hp';\r\n playerHpTitle.setAttribute('data-i18n', `common:playersHp`);\r\n playerHpTitle.textContent = 'Players HP: ';\r\n playersHpElement.appendChild(playerHpTitle);\r\n\r\n lastLog.playersHp.forEach((player, idx) => {\r\n const playerElement = document.createElement('span');\r\n playerElement.className = 'log-player-hp';\r\n playerElement.textContent = `${player.hrid}: ${player.current}/${player.max}`;\r\n\r\n if (player.current <= 0) {\r\n playerElement.style.color = darkModeToggle.checked ? '#FF6347' : '#CC0000';\r\n } else if (damagedPlayers.has(player.hrid)) {\r\n playerElement.style.color = darkModeToggle.checked ? '#00BFFF' : '#007BFF';\r\n }\r\n\r\n if (idx > 0) {\r\n playersHpElement.appendChild(document.createTextNode(' | '));\r\n }\r\n playersHpElement.appendChild(playerElement);\r\n });\r\n const spacer = document.createElement('div');\r\n spacer.style.height = '15px';\r\n logsContainer.appendChild(spacer);\r\n timeGroupElement.appendChild(playersHpElement);\r\n logsContainer.appendChild(timeGroupElement);\r\n });\r\n\r\n // 更新汉化\r\n updateContent()\r\n}\r\n\r\n// 按时间分组日志\r\nfunction groupLogsByTime(logs) {\r\n const groups = [];\r\n let currentGroup = null;\r\n\r\n logs.forEach(log => {\r\n if (!currentGroup || currentGroup.time !== log.time) {\r\n currentGroup = {\r\n time: log.time,\r\n wave: log.wave,\r\n logs: [log]\r\n };\r\n groups.push(currentGroup);\r\n } else {\r\n currentGroup.logs.push(log);\r\n }\r\n });\r\n\r\n groups.forEach(group => {\r\n let hpMap = {};\r\n if (group.logs.length > 0) {\r\n group.logs[0].playersHp.forEach(p => {\r\n hpMap[p.hrid] = { current: p.current, max: p.max };\r\n });\r\n }\r\n group.logs.forEach(log => {\r\n if (hpMap[log.target]) {\r\n hpMap[log.target].current = log.afterHp;\r\n }\r\n });\r\n group.logs.forEach(log => {\r\n log.playersHp = Object.entries(hpMap).map(([hrid, val]) => ({\r\n hrid,\r\n current: val.current,\r\n max: val.max\r\n }));\r\n });\r\n });\r\n\r\n return groups;\r\n}\r\n\r\n// #endregion\r\n\r\n\r\n// #region Equipment Sets\r\n\r\nfunction initEquipmentSetsModal() {\r\n let equipmentSetsModal = document.getElementById(\"equipmentSetsModal\");\r\n equipmentSetsModal.addEventListener(\"show.bs.modal\", equipmentSetsModalShownHandler);\r\n\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n equipmentSetNameInput.addEventListener(\"input\", (event) => equipmentSetNameChangedHandler(event));\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.addEventListener(\"click\", createNewEquipmentSetHandler);\r\n}\r\n\r\nfunction equipmentSetsModalShownHandler() {\r\n resetNewEquipmentSetControls();\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction resetNewEquipmentSetControls() {\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n equipmentSetNameInput.value = \"\";\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.disabled = true;\r\n}\r\n\r\nfunction updateEquipmentSetList() {\r\n let newChildren = [];\r\n let equipmentSets = loadEquipmentSets();\r\n\r\n for (const equipmentSetName of Object.keys(equipmentSets)) {\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let nameCol = createElement(\"div\", \"col align-self-center\", equipmentSetName);\r\n row.appendChild(nameCol);\r\n\r\n let loadButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let loadButton = createElement(\"button\", \"btn btn-primary\", \"Load\");\r\n loadButton.setAttribute(\"data-i18n\", \"common:controls.load\");\r\n loadButton.setAttribute(\"type\", \"button\");\r\n loadButton.addEventListener(\"click\", (_) => loadEquipmentSetHandler(equipmentSetName));\r\n loadButtonCol.appendChild(loadButton);\r\n row.appendChild(loadButtonCol);\r\n\r\n let saveButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let saveButton = createElement(\"button\", \"btn btn-primary\", \"Save\");\r\n saveButton.setAttribute(\"data-i18n\", \"common:controls.save\");\r\n saveButton.setAttribute(\"type\", \"button\");\r\n saveButton.addEventListener(\"click\", (_) => updateEquipmentSetHandler(equipmentSetName));\r\n saveButtonCol.appendChild(saveButton);\r\n row.appendChild(saveButtonCol);\r\n\r\n let deleteButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let deleteButton = createElement(\"button\", \"btn btn-danger\", \"Delete\");\r\n deleteButton.setAttribute(\"data-i18n\", \"common:controls.delete\");\r\n deleteButton.setAttribute(\"type\", \"button\");\r\n deleteButton.addEventListener(\"click\", (_) => deleteEquipmentSetHandler(equipmentSetName));\r\n deleteButtonCol.appendChild(deleteButton);\r\n row.appendChild(deleteButtonCol);\r\n\r\n newChildren.push(row);\r\n }\r\n\r\n let equipmentSetList = document.getElementById(\"equipmentSetList\");\r\n equipmentSetList.replaceChildren(...newChildren);\r\n\r\n updateContent();\r\n}\r\n\r\nfunction equipmentSetNameChangedHandler(event) {\r\n let invalid = false;\r\n\r\n if (event.target.value.length == 0) {\r\n invalid = true;\r\n }\r\n\r\n let equipmentSets = loadEquipmentSets();\r\n if (equipmentSets[event.target.value]) {\r\n invalid = true;\r\n }\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.disabled = invalid;\r\n}\r\n\r\nfunction createNewEquipmentSetHandler() {\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n let equipmentSetName = equipmentSetNameInput.value;\r\n\r\n let equipmentSet = getEquipmentSetFromUI();\r\n let equipmentSets = loadEquipmentSets();\r\n equipmentSets[equipmentSetName] = equipmentSet;\r\n saveEquipmentSets(equipmentSets);\r\n\r\n resetNewEquipmentSetControls();\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction loadEquipmentSetHandler(name) {\r\n let equipmentSets = loadEquipmentSets();\r\n loadEquipmentSetIntoUI(equipmentSets[name]);\r\n}\r\n\r\nfunction updateEquipmentSetHandler(name) {\r\n let equipmentSet = getEquipmentSetFromUI();\r\n let equipmentSets = loadEquipmentSets();\r\n equipmentSets[name] = equipmentSet;\r\n saveEquipmentSets(equipmentSets);\r\n}\r\n\r\nfunction deleteEquipmentSetHandler(name) {\r\n let equipmentSets = loadEquipmentSets();\r\n delete equipmentSets[name];\r\n saveEquipmentSets(equipmentSets);\r\n\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction loadEquipmentSets() {\r\n return JSON.parse(localStorage.getItem(\"equipmentSets\")) ?? {};\r\n}\r\n\r\nfunction saveEquipmentSets(equipmentSets) {\r\n localStorage.setItem(\"equipmentSets\", JSON.stringify(equipmentSets));\r\n}\r\n\r\nfunction getEquipmentSetFromUI() {\r\n let equipmentSet = {\r\n levels: {},\r\n equipment: {},\r\n food: {},\r\n drinks: {},\r\n abilities: {},\r\n triggerMap: {},\r\n houseRooms: {},\r\n achievements: {},\r\n };\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n equipmentSet.levels[skill] = Number(levelInput.value);\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n\r\n equipmentSet.equipment[type] = {\r\n equipment: equipmentSelect.value,\r\n enhancementLevel: Number(enhancementLevelInput.value),\r\n };\r\n });\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n equipmentSet.food[i] = foodSelect.value;\r\n }\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n equipmentSet.drinks[i] = drinkSelect.value;\r\n }\r\n\r\n for (let i = 0; i < 5; i++) {\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n equipmentSet.abilities[i] = {\r\n ability: abilitySelect.value,\r\n level: Number(abilityLevelInput.value),\r\n };\r\n }\r\n\r\n equipmentSet.triggerMap = triggerMap;\r\n\r\n equipmentSet.houseRooms = player.houseRooms;\r\n equipmentSet.achievements = player.achievements;\r\n\r\n return equipmentSet;\r\n}\r\n\r\nfunction fixTriggerMap(triggerMap) {\r\n let delKeys = []\r\n for (const key of Object.keys(triggerMap)) {\r\n let err = false;\r\n if (null == triggerMap[key]) {\r\n triggerMap[key] = [];\r\n }\r\n for (const trigger of triggerMap[key]) {\r\n if (!combatTriggerConditionDetailMap[trigger.conditionHrid]) {\r\n err = true;\r\n break;\r\n }\r\n }\r\n if (err) {\r\n delKeys.push(key);\r\n }\r\n }\r\n for (const key of delKeys) {\r\n delete triggerMap[key];\r\n }\r\n}\r\n\r\nfunction loadEquipmentSetIntoUI(equipmentSet) {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !equipmentSet.levels[\"meleeLevel\"] && equipmentSet.levels[\"powerLevel\"]) {\r\n equipmentSet.levels[\"meleeLevel\"] = equipmentSet.levels[\"powerLevel\"];\r\n }\r\n levelInput.value = equipmentSet.levels[skill] ?? 1;\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n\r\n let currentEquipment = equipmentSet.equipment[type];\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.equipment;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n foodSelect.value = equipmentSet.food[i];\r\n }\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n drinkSelect.value = equipmentSet.drinks[i].replace(\"power\", \"melee\");\r\n }\r\n\r\n let hasSpecial = false;\r\n if (equipmentSet.abilities && Object.keys(equipmentSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n equipmentSet.abilities[i].ability == \"/abilities/aqua_aura\" ||\r\n equipmentSet.abilities[i].ability == \"/abilities/flame_aura\" ||\r\n equipmentSet.abilities[i].ability == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n equipmentSet.abilities[i].ability = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (equipmentSet.abilities[i].ability == \"/abilities/arcane_reflection\") {\r\n equipmentSet.abilities[i].ability = \"/abilities/retribution\";\r\n }\r\n\r\n abilitySelect.value = equipmentSet.abilities[i].ability;\r\n abilityLevelInput.value = equipmentSet.abilities[i].level;\r\n }\r\n\r\n triggerMap = equipmentSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n\r\n if (equipmentSet.houseRooms) {\r\n for (const room in equipmentSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (equipmentSet.houseRooms[room]) {\r\n field.value = equipmentSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = equipmentSet.houseRooms;\r\n } else {\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n\r\n if (equipmentSet.achievements) {\r\n for (const achievement in equipmentSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (equipmentSet.achievements[achievement]) {\r\n field.checked = true;\r\n } else {\r\n field.checked = false;\r\n }\r\n player.achievements[achievement] = field.checked;\r\n }\r\n } else {\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n refreshAchievementStatics();\r\n\r\n updateState();\r\n updateUI();\r\n\r\n updateContent();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Error Handling\r\n\r\nfunction initErrorHandling() {\r\n window.addEventListener(\"error\", (event) => {\r\n showErrorModal(event.message);\r\n });\r\n\r\n let copyErrorButton = document.getElementById(\"buttonCopyError\");\r\n copyErrorButton.addEventListener(\"click\", (event) => {\r\n let errorInput = document.getElementById(\"inputError\");\r\n navigator.clipboard.writeText(errorInput.value);\r\n });\r\n}\r\n\r\nfunction initImportExportModal() {\r\n let exportSetButton = document.getElementById(\"buttonExportSet\");\r\n exportSetButton.addEventListener(\"click\", (event) => {\r\n savePreviousPlayer(currentPlayerTabId);\r\n const activeTab = document.querySelector('#importTab .nav-link.active');\r\n if (activeTab.id === 'group-combat-tab') {\r\n doGroupExport();\r\n } else if (activeTab.id === 'solo-tab') {\r\n doSoloExport();\r\n }\r\n });\r\n\r\n let importSetButton = document.getElementById(\"buttonImportSet\");\r\n importSetButton.addEventListener(\"click\", (event) => {\r\n const activeTab = document.querySelector('#importTab .nav-link.active');\r\n if (activeTab.id === 'group-combat-tab') {\r\n doGroupImport();\r\n } else if (activeTab.id === 'solo-tab') {\r\n doSoloImport();\r\n }\r\n updateState();\r\n updateUI();\r\n resetImportInputs();\r\n });\r\n}\r\n\r\nfunction resetImportInputs() {\r\n document.getElementById('inputSetGroupCombatAll').value = '';\r\n document.getElementById('inputSetGroupCombatplayer1').value = '';\r\n document.getElementById('inputSetGroupCombatplayer2').value = '';\r\n document.getElementById('inputSetGroupCombatplayer3').value = '';\r\n document.getElementById('inputSetGroupCombatplayer4').value = '';\r\n document.getElementById('inputSetGroupCombatplayer5').value = '';\r\n document.getElementById('inputSetSolo').value = '';\r\n}\r\n\r\nfunction doGroupExport() {\r\n try {\r\n navigator.clipboard.writeText(JSON.stringify(playerDataMap)).then(() => alert(\"Current Group has been copied to clipboard.\"));\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction doSoloExport() {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let equipmentArray = [];\r\n for (const item in player.equipment) {\r\n if (player.equipment[item] != null) {\r\n equipmentArray.push({\r\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\r\n \"itemHrid\": player.equipment[item].hrid,\r\n \"enhancementLevel\": player.equipment[item].enhancementLevel\r\n });\r\n }\r\n }\r\n let playerArray = {\r\n \"attackLevel\": player.attackLevel,\r\n \"magicLevel\": player.magicLevel,\r\n \"meleeLevel\": player.meleeLevel,\r\n \"rangedLevel\": player.rangedLevel,\r\n \"defenseLevel\": player.defenseLevel,\r\n \"staminaLevel\": player.staminaLevel,\r\n \"intelligenceLevel\": player.intelligenceLevel,\r\n \"equipment\": equipmentArray\r\n };\r\n let abilitiesArray = [];\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let abilityName = document.getElementById(\"selectAbility_\" + i);\r\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\r\n }\r\n let drinksArray = [];\r\n for (let i = 0; i < drinks?.length; i++) {\r\n drinksArray.push({ \"itemHrid\": drinks[i] });\r\n }\r\n let foodArray = [];\r\n for (let i = 0; i < food?.length; i++) {\r\n foodArray.push({ \"itemHrid\": food[i] });\r\n }\r\n let state = {\r\n player: playerArray,\r\n food: { \"/action_types/combat\": foodArray },\r\n drinks: { \"/action_types/combat\": drinksArray },\r\n abilities: abilitiesArray,\r\n triggerMap: triggerMap,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n houseRooms: player.houseRooms,\r\n achievements: player.achievements\r\n };\r\n try {\r\n navigator.clipboard.writeText(JSON.stringify(state)).then(() => alert(\"Current set has been copied to clipboard.\"));\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction setPlayerData(playerId, inputElementId) {\r\n const inputElement = document.getElementById(inputElementId);\r\n const value = inputElement ? inputElement.value.trim() : \"\";\r\n\r\n // Only set the value in the map if it's not null, undefined, or empty\r\n if (value) {\r\n playerDataMap[playerId] = value;\r\n return true;\r\n }\r\n return false;\r\n}\r\n\r\nfunction doGroupImport() {\r\n let needUpdateCurrentTab = false;\r\n const value = document.getElementById(\"inputSetGroupCombatAll\")?.value || \"\";\r\n if (!value.trim()) {\r\n for (let i of ['1', '2', '3', '4', '5']) {\r\n if (setPlayerData(i, \"inputSetGroupCombatplayer\" + i) && currentPlayerTabId == i) {\r\n needUpdateCurrentTab = true;\r\n }\r\n }\r\n } else {\r\n playerDataMap = JSON.parse(value);\r\n needUpdateCurrentTab = true;\r\n }\r\n\r\n if (needUpdateCurrentTab) {\r\n updateNextPlayer(currentPlayerTabId);\r\n }\r\n}\r\n\r\nfunction doSoloImport() {\r\n let importSet = document.getElementById(\"inputSetSolo\").value;\r\n importSet = JSON.parse(importSet);\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\r\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\r\n }\r\n levelInput.value = importSet.player[skill + \"Level\"];\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.itemHrid;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\r\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\r\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\r\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\r\n if (mainhandWeapon !== undefined) {\r\n weaponSelect.value = mainhandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\r\n } else if (twohandWeapon !== undefined) {\r\n weaponSelect.value = twohandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\r\n } else {\r\n weaponSelect.value = \"\";\r\n weaponEnhancementLevelInput.value = 0;\r\n }\r\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\r\n importSet.food = importSet.food[\"/action_types/combat\"];\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n if (importSet.drinks[i] != null) {\r\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\r\n } else {\r\n drinkSelect.value = \"\";\r\n }\r\n if (importSet.food[i] != null) {\r\n foodSelect.value = importSet.food[i].itemHrid;\r\n } else {\r\n foodSelect.value = \"\";\r\n }\r\n }\r\n\r\n let hasSpecial = false;\r\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\r\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\r\n }\r\n\r\n if (importSet.abilities[i] != null) {\r\n abilitySelect.value = importSet.abilities[i].abilityHrid;\r\n abilityLevelInput.value = String(importSet.abilities[i].level);\r\n } else {\r\n abilitySelect.value = \"\";\r\n abilityLevelInput.value = \"1\";\r\n }\r\n }\r\n\r\n if (importSet.triggerMap) {\r\n triggerMap = importSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n }\r\n\r\n if (importSet.houseRooms) {\r\n for (const room in importSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (importSet.houseRooms[room]) {\r\n field.value = importSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = importSet.houseRooms;\r\n } else {\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n\r\n if (importSet.achievements) {\r\n for (const achievement in importSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (importSet.achievements[achievement]) {\r\n field.checked = true;\r\n } else {\r\n field.checked = false;\r\n }\r\n player.achievements[achievement] = field.checked;\r\n }\r\n } else {\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n refreshAchievementStatics();\r\n\r\n if (\"zone\" in importSet) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n zoneSelect.value = importSet[\"zone\"];\r\n }\r\n\r\n if (\"simulationTime\" in importSet) {\r\n let simulationDuration = document.getElementById(\"inputSimulationTime\");\r\n simulationDuration.value = importSet[\"simulationTime\"];\r\n }\r\n}\r\n\r\nfunction savePreviousPlayer(playerId) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let equipmentArray = [];\r\n for (const item in player.equipment) {\r\n if (player.equipment[item] != null) {\r\n equipmentArray.push({\r\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\r\n \"itemHrid\": player.equipment[item].hrid,\r\n \"enhancementLevel\": player.equipment[item].enhancementLevel\r\n });\r\n }\r\n }\r\n let playerArray = {\r\n \"attackLevel\": player.attackLevel,\r\n \"magicLevel\": player.magicLevel,\r\n \"meleeLevel\": player.meleeLevel,\r\n \"rangedLevel\": player.rangedLevel,\r\n \"defenseLevel\": player.defenseLevel,\r\n \"staminaLevel\": player.staminaLevel,\r\n \"intelligenceLevel\": player.intelligenceLevel,\r\n \"equipment\": equipmentArray\r\n };\r\n let abilitiesArray = [];\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let abilityName = document.getElementById(\"selectAbility_\" + i);\r\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\r\n }\r\n let drinksArray = [];\r\n for (let i = 0; i < drinks?.length; i++) {\r\n drinksArray.push({ \"itemHrid\": drinks[i] });\r\n }\r\n let foodArray = [];\r\n for (let i = 0; i < food?.length; i++) {\r\n foodArray.push({ \"itemHrid\": food[i] });\r\n }\r\n let state = {\r\n player: playerArray,\r\n food: { \"/action_types/combat\": foodArray },\r\n drinks: { \"/action_types/combat\": drinksArray },\r\n abilities: abilitiesArray,\r\n triggerMap: triggerMap,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n houseRooms: player.houseRooms,\r\n achievements: player.achievements\r\n };\r\n try {\r\n playerDataMap[playerId] = JSON.stringify(state);\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction updateNextPlayer(currentPlayerNumber) {\r\n let playerImportData = playerDataMap[currentPlayerNumber];\r\n let importSet = JSON.parse(playerImportData);\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\r\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\r\n }\r\n levelInput.value = importSet.player[skill + \"Level\"];\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.itemHrid;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\r\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\r\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\r\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\r\n if (mainhandWeapon !== undefined) {\r\n weaponSelect.value = mainhandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\r\n } else if (twohandWeapon !== undefined) {\r\n weaponSelect.value = twohandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\r\n } else {\r\n weaponSelect.value = \"\";\r\n weaponEnhancementLevelInput.value = 0;\r\n }\r\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\r\n importSet.food = importSet.food[\"/action_types/combat\"];\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n if (importSet.drinks[i] != null) {\r\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\r\n } else {\r\n drinkSelect.value = \"\";\r\n }\r\n if (importSet.food[i] != null) {\r\n foodSelect.value = importSet.food[i].itemHrid;\r\n } else {\r\n foodSelect.value = \"\";\r\n }\r\n }\r\n\r\n let hasSpecial = false;\r\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\r\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\r\n }\r\n\r\n if (importSet.abilities[i] != null) {\r\n abilitySelect.value = importSet.abilities[i].abilityHrid;\r\n abilityLevelInput.value = String(importSet.abilities[i].level);\r\n } else {\r\n abilitySelect.value = \"\";\r\n abilityLevelInput.value = \"1\";\r\n }\r\n }\r\n\r\n if (importSet.triggerMap) {\r\n triggerMap = importSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n }\r\n\r\n { // reset all houseRooms\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n if (importSet.houseRooms) {\r\n for (const room in importSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (importSet.houseRooms[room]) {\r\n field.value = importSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = importSet.houseRooms;\r\n }\r\n\r\n { // reset all achievements\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n if (importSet.achievements) {\r\n for (const achievement in importSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (importSet.achievements[achievement]) {\r\n field.checked = true;\r\n player.achievements[achievement] = true;\r\n } else {\r\n field.checked = false;\r\n player.achievements[achievement] = false;\r\n }\r\n }\r\n }\r\n refreshAchievementStatics();\r\n}\r\n\r\nfunction showErrorModal(error) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n\r\n let state = {\r\n error: error,\r\n player: player,\r\n food: food,\r\n drinks: drinks,\r\n abilities: abilities,\r\n triggerMap: triggerMap,\r\n modalTriggers: modalTriggers,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n };\r\n\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n state[\"abilityLevel\" + i] = abilityLevelInput.value;\r\n }\r\n\r\n let errorInput = document.getElementById(\"inputError\");\r\n errorInput.value = JSON.stringify(state);\r\n\r\n let errorModal = new bootstrap.Modal(document.getElementById(\"errorModal\"));\r\n errorModal.show();\r\n}\r\n\r\nwindow.prices;\r\n\r\nasync function fetchPrices() {\r\n let response = null;\r\n try {\r\n response = await fetch('https://www.milkywayidle.com/game_data/marketplace.json'\r\n , {\r\n mode: 'cors'\r\n }\r\n );\r\n if (!response.ok) {\r\n console.log('Error fetching prices');\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n\r\n if (response == null) {\r\n try {\r\n response = await fetch('https://www.milkywayidlecn.com/game_data/marketplace.json'\r\n , {\r\n mode: 'cors'\r\n }\r\n );\r\n if (!response.ok) {\r\n console.log('Error fetching prices');\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n }\r\n\r\n if (!response || !response.ok) {\r\n return;\r\n }\r\n\r\n try {\r\n\r\n let btn = document.querySelector('#buttonGetPrices');\r\n btn.style.backgroundColor = 'green';\r\n\r\n const pricesJson = await response.json();\r\n\r\n const priceTmp = pricesJson['marketData'];\r\n window.prices = {};\r\n for (const item in itemDetailMap) {\r\n const hrid = itemDetailMap[item].hrid;\r\n if (hrid in priceTmp) {\r\n window.prices[hrid] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (priceTmp[hrid]['0']) {\r\n window.prices[hrid].ask = priceTmp[hrid]['0'].a;\r\n window.prices[hrid].bid = priceTmp[hrid]['0'].b;\r\n }\r\n }\r\n } \r\n\r\n window.prices[\"/items/coin\"] = { \"ask\": 1, \"bid\": 1, \"vendor\": 1 };\r\n\r\n window.prices[\"/items/small_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n window.prices[\"/items/medium_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n window.prices[\"/items/large_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n}\r\n\r\ndocument.getElementById(\"buttonGetPrices\").onclick = async () => {\r\n await fetchPrices();\r\n};\r\n\r\ndocument.addEventListener(\"input\", (e) => {\r\n let element = e.target;\r\n if (element.tagName == \"TD\" && element.parentNode.parentNode.parentNode.classList.value.includes('profit-table')) {\r\n let tableId = element.parentNode.parentNode.parentNode.id;\r\n let row = element.parentNode.querySelectorAll('td');\r\n let item = row[0].getAttribute('data-i18n').split('.')[1];\r\n let newPrice = element.innerText;\r\n\r\n let revenueSetting = document.getElementById('selectPrices_drops').value;\r\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\r\n\r\n let expensesDifference = 0;\r\n let revenueDifference = 0;\r\n let noRngRevenueDifference = 0;\r\n\r\n if (tableId == 'expensesTable') {\r\n expensesDifference = updateTable('expensesTable', item, newPrice);\r\n if (revenueSetting == expensesSetting) {\r\n revenueDifference = updateTable('revenueTable', item, newPrice);\r\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\r\n }\r\n if (window.prices) {\r\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (expensesSetting == 'bid') {\r\n window.prices[item]['bid'] = newPrice;\r\n } else {\r\n window.prices[item]['ask'] = newPrice;\r\n }\r\n }\r\n } else {\r\n revenueDifference = updateTable('revenueTable', item, newPrice);\r\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\r\n if (revenueSetting == expensesSetting) {\r\n expensesDifference = updateTable('expensesTable', item, newPrice);\r\n }\r\n if (window.prices) {\r\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (revenueSetting == 'bid') {\r\n window.prices[item]['bid'] = newPrice;\r\n } else {\r\n window.prices[item]['ask'] = newPrice;\r\n }\r\n }\r\n }\r\n\r\n window.expenses += expensesDifference;\r\n document.getElementById('expensesSpan').innerText = window.expenses.toLocaleString();\r\n window.revenue += revenueDifference;\r\n document.getElementById('revenueSpan').innerText = window.revenue.toLocaleString();\r\n window.noRngRevenue += noRngRevenueDifference;\r\n document.getElementById('noRngRevenueSpan').innerText = window.noRngRevenue.toLocaleString();\r\n\r\n window.profit = window.revenue - window.expenses;\r\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\r\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\r\n window.noRngProfit = window.noRngRevenue - window.expenses;\r\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\r\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\r\n }\r\n});\r\n\r\nfunction updateTable(tableId, item, price) {\r\n let row = document.querySelector('#' + tableId + ' .' + CSS.escape(item));\r\n if (row == null) {\r\n return 0;\r\n }\r\n\r\n row = row.querySelectorAll('td');\r\n let priceTd = row[1];\r\n let amountTd = row[2];\r\n let totalTd = row[3];\r\n let oldTotal = totalTd.innerText;\r\n let newTotal = price * amountTd.innerText;\r\n\r\n if (priceTd.innerText != price) {\r\n priceTd.innerText = price;\r\n }\r\n totalTd.innerText = newTotal;\r\n\r\n return newTotal - oldTotal;\r\n}\r\n\r\n// #endregion\r\n\r\nfunction initPatchNotes() {\r\n const patchNotesRows = document.getElementById(\"patchNotes\");\r\n for (const pn in patchNote) {\r\n const patchNoteContainer = document.createElement(\"div\");\r\n patchNotesRows.setAttribute('class', 'col-12 mb-4');\r\n\r\n const patchNoteElement = document.createElement(\"h6\");\r\n patchNoteElement.innerHTML = pn;\r\n const patchNoteList = document.createElement(\"ul\");\r\n for (const note of patchNote[pn]) {\r\n const noteElement = document.createElement(\"li\");\r\n noteElement.innerHTML = note;\r\n patchNoteList.appendChild(noteElement);\r\n }\r\n patchNoteContainer.appendChild(patchNoteElement);\r\n patchNoteContainer.appendChild(patchNoteList);\r\n\r\n patchNotesRows.appendChild(patchNoteContainer);\r\n }\r\n}\r\n\r\nfunction initExtraBuffSection() {\r\n // mooPass\r\n let mooPassToggle = document.getElementById(\"mooPassToggle\");\r\n let mooPass = localStorage.getItem('mooPass');\r\n if (mooPass) {\r\n mooPassToggle.checked = Boolean(mooPass);\r\n }\r\n mooPassToggle.onchange = () => {\r\n localStorage.setItem('mooPass', mooPassToggle.checked);\r\n }\r\n \r\n // comExp\r\n let comExpToggle = document.getElementById(\"comExpToggle\");\r\n let comExpInput = document.getElementById(\"comExpInput\");\r\n let comExp = localStorage.getItem('comExp');\r\n if (comExp) {\r\n let comExpNumber = Number(comExp);\r\n if (comExpNumber > 0) {\r\n comExpToggle.checked = true;\r\n comExpInput.value = comExpNumber;\r\n } else {\r\n comExpToggle.checked = false;\r\n comExpInput.disabled = true;\r\n }\r\n }\r\n const updateComExp = () => {\r\n if (comExpToggle.checked) {\r\n let comExp = Number(comExpInput.value);\r\n localStorage.setItem('comExp', comExp); \r\n comExpInput.disabled = false;\r\n } else {\r\n localStorage.setItem('comExp', 0);\r\n comExpInput.disabled = true;\r\n }\r\n }\r\n comExpToggle.onchange = updateComExp;\r\n comExpInput.onchange = updateComExp;\r\n\r\n // comDrop\r\n let comDropToggle = document.getElementById(\"comDropToggle\");\r\n let comDropInput = document.getElementById(\"comDropInput\");\r\n let comDrop = localStorage.getItem('comDrop');\r\n if (comDrop) {\r\n let comDropNumber = Number(comDrop);\r\n if (comDropNumber > 0) {\r\n comDropToggle.checked = true;\r\n comDropInput.value = comDropNumber;\r\n } else {\r\n comDropToggle.checked = false;\r\n comDropInput.disabled = true;\r\n }\r\n }\r\n const updateComDrop = () => {\r\n if (comDropToggle.checked) {\r\n let comDrop = Number(comDropInput.value);\r\n localStorage.setItem('comDrop', comDrop); \r\n comDropInput.disabled = false;\r\n } else {\r\n localStorage.setItem('comDrop', 0);\r\n comDropInput.disabled = true;\r\n }\r\n }\r\n comDropToggle.onchange = updateComDrop;\r\n comDropInput.onchange = updateComDrop;\r\n}\r\n\r\n\r\nfunction updateState() {\r\n updateEquipmentState();\r\n updateLevels();\r\n updateFoodState();\r\n updateDrinksState();\r\n updateAbilityState();\r\n}\r\n\r\nfunction updateUI() {\r\n updateCombatStatsUI();\r\n updateFoodUI();\r\n updateDrinksUI();\r\n updateAbilityUI();\r\n\r\n updateContent();\r\n}\r\n\r\nconst darkModeToggle = document.getElementById('darkModeToggle');\r\nconst body = document.body;\r\n\r\nif (localStorage.getItem('darkModeEnabled') === 'true') {\r\n body.classList.add('dark-mode');\r\n const tables = document.getElementsByClassName('profit-table');\r\n for (const table of tables) {\r\n table.classList.toggle('table-striped');\r\n }\r\n darkModeToggle.checked = true;\r\n}\r\n\r\ndarkModeToggle.addEventListener('change', () => {\r\n body.classList.toggle('dark-mode');\r\n const tables = document.getElementsByClassName('profit-table');\r\n for (const table of tables) {\r\n table.classList.toggle('table-striped');\r\n }\r\n localStorage.setItem('darkModeEnabled', darkModeToggle.checked);\r\n});\r\n\r\nfunction updateContent() {\r\n document.querySelectorAll('[data-i18n]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n');\r\n if (key) {\r\n element.textContent = i18next.t(key);\r\n }\r\n });\r\n\r\n document.querySelectorAll('[data-i18n-placeholder]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n-placeholder');\r\n if (key) {\r\n element.placeholder = i18next.t(key);\r\n }\r\n });\r\n\r\n document.querySelectorAll('option[data-i18n]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n');\r\n if (key) {\r\n element.textContent = i18next.t(key);\r\n }\r\n });\r\n}\r\n\r\ninitEquipmentSection();\r\ninitHouseRoomsModal();\r\ninitAchievementsModal();\r\ninitLevelSection();\r\ninitFoodSection();\r\ninitDrinksSection();\r\ninitAbilitiesSection();\r\ninitZones();\r\ninitDungeons();\r\ninitTriggerModal();\r\ninitSimulationControls();\r\ninitEquipmentSetsModal();\r\ninitErrorHandling();\r\ninitImportExportModal();\r\ninitDamageDoneTaken();\r\ninitPatchNotes();\r\ninitExtraBuffSection();\r\n\r\nupdateState();\r\nupdateUI();\r\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/dist/locales/en/common.json b/dist/locales/en/common.json index b8137743..0c8280db 100644 --- a/dist/locales/en/common.json +++ b/dist/locales/en/common.json @@ -71,6 +71,7 @@ "dungeonsCompleted": "Dungeons Completed", "dungeonsFailed": "Dungeons Failed", "averageTime": "Average Time", + "minimumTime": "Minimum Time", "maxEnrageStack": "Max Stack of Enrage", "debuffOnLevelGap": "Debuff on Level Gap" }, diff --git a/dist/locales/zh/common.json b/dist/locales/zh/common.json index 668cb56f..1e709046 100644 --- a/dist/locales/zh/common.json +++ b/dist/locales/zh/common.json @@ -70,7 +70,8 @@ "maxWaveReached" : "已完成最大波次", "dungeonsCompleted": "地下城的完成次数", "dungeonsFailed": "地下城的团灭次数", - "averageTime": "完成平均时间", + "averageTime": "平均完成时间", + "minimumTime": "最短完成时间", "maxEnrageStack": "最大狂暴层数", "debuffOnLevelGap": "等级差距惩罚" }, diff --git a/dist/patchNote.json b/dist/patchNote.json index c8160696..8396b351 100644 --- a/dist/patchNote.json +++ b/dist/patchNote.json @@ -1,4 +1,8 @@ { + "2025年12月30日": + [ + "地下城增加最短完成时间记录" + ], "2025年12月24日": [ "修复技能释放选择的缺陷,之前可能存在异常缺蓝等情况" diff --git a/dist/src_worker_js.bundle.js b/dist/src_worker_js.bundle.js index b8e2c673..3e7078a6 100644 --- a/dist/src_worker_js.bundle.js +++ b/dist/src_worker_js.bundle.js @@ -1010,6 +1010,9 @@ class CombatSimulator extends EventTarget { if (this.zone.isDungeon) { this.simResult.updateTimeSpentAlive("#" + (this.zone.encountersKilled - 1).toString(), false, this.simulationTime); + if (this.zone.encountersKilled > this.zone.dungeonSpawnInfo.maxWaves) { + this.simResult.updateDungenonFinish("#1", this.simulationTime); + } } this.simResult.addEncounterEnd(); // console.log("All enemies died"); @@ -4109,6 +4112,7 @@ class SimResult { this.maxWaveReached = 0; this.numberOfPlayers = numberOfPlayers; this.maxEnrageStack = 0; + this.minDungenonTime = 0; this.wipeEvents = []; } @@ -4147,6 +4151,19 @@ class SimResult { } } + updateDungenonFinish(beginFlag, finishTime) { + const i = this.timeSpentAlive.findIndex(e => e.name === beginFlag); + if (i == -1) { + return; + } + + const currentDungenonTime = finishTime - this.timeSpentAlive[i].spawnedAt; + + if (this.minDungenonTime == 0 || this.minDungenonTime > currentDungenonTime) { + this.minDungenonTime = currentDungenonTime; + } + } + addExperienceGain(unit, experience) { if (!unit.isPlayer) { return; diff --git a/dist/src_worker_js.bundle.js.map b/dist/src_worker_js.bundle.js.map index 1bc291e8..b4b65d6e 100644 --- a/dist/src_worker_js.bundle.js.map +++ b/dist/src_worker_js.bundle.js.map @@ -1 +1 @@ -{"version":3,"file":"src_worker_js.bundle.js","mappings":";;;;;;;;;;;;;;;;AAA0B;AACkC;AAC5B;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,wDAAgB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,6CAAI;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;ACxNG;AACkD;AACR;AACpE;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,gEAAwB;AAChE;AACA,0CAA0C,4DAAoB;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,WAAW;;;;;;;;;;;;;;AC1B1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,IAAI,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACd4B;AACO;AACQ;AACU;AAChB;AACM;AACF;AACF;AACd;AACgB;AACR;AACU;AACE;AACI;AACJ;AACE;AACJ;AACR;AACnB;AAC2B;AACF;AAC7B;AACA;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,0DAAU;AACxC,6BAA6B,mDAAS;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,kBAAkB;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,aAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,KAAK,MAAM,WAAW,OAAO,YAAY;AAChE,wBAAwB,YAAY,KAAK,YAAY;AACrD,yBAAyB,cAAc,IAAI,YAAY;AACvD,4BAA4B,0BAA0B,OAAO,IAAI,UAAU,GAAG,MAAM,eAAe;AACnG;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA,mCAAmC,UAAU;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA,mCAAmC,UAAU;AAC7C;AACA;AACA;AACA;AACA;AACA,gBAAgB,yBAAyB;AACzC;AACA;AACA,wBAAwB,WAAW;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,gEAAgB;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,iBAAiB;AACjB;AACA;AACA;AACA;AACA,2BAA2B,0CAA0C;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0CAA0C;AAC1E;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA,wBAAwB,yBAAyB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,mDAAS;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,gEAAgB;AACjC;AACA;AACA,iBAAiB,kEAAkB;AACnC;AACA;AACA,iBAAiB,iEAAiB;AAClC;AACA;AACA,iBAAiB,+DAAe;AAChC;AACA;AACA,iBAAiB,mEAAmB;AACpC;AACA;AACA,iBAAiB,mEAAmB;AACpC;AACA;AACA,iBAAiB,wEAAwB;AACzC;AACA;AACA,iBAAiB,+DAAc;AAC/B;AACA;AACA,iBAAiB,oEAAmB;AACpC;AACA;AACA,iBAAiB,qEAAoB;AACrC;AACA;AACA,iBAAiB,uEAAsB;AACvC;AACA;AACA,iBAAiB,qEAAoB;AACrC;AACA;AACA,iBAAiB,sEAAqB;AACtC;AACA;AACA,iBAAiB,oEAAmB;AACpC;AACA;AACA,iBAAiB,gEAAe;AAChC;AACA;AACA,iBAAiB,oEAAmB;AACpC;AACA;AACA,iBAAiB,mEAAkB;AACnC;AACA;AACA;AACA,iBAAiB,kEAAkB;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,yBAAyB;AACjD,mCAAmC;AACnC;AACA;AACA;AACA;AACA,iCAAiC,+DAAc;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,yBAAyB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,0CAA0C,gEAAe;AACzD,kCAAkC,gEAAe;AACjD;AACA;AACA;AACA,0CAA0C,oEAAmB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,yBAAyB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,wDAAe;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6FAA6F,qEAAoB;AACjH;AACA;AACA,uEAAuE,qEAAoB;AAC3F;AACA,+CAA+C,qEAAoB;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4FAA4F,oEAAmB;AAC/G,uEAAuE,oEAAmB;AAC1F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,oEAAmB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8FAA8F,sEAAqB;AACnH;AACA;AACA;AACA,uEAAuE,sEAAqB;AAC5F,gDAAgD,sEAAqB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,+DAAe;AAC7D;AACA,wCAAwC,iEAAiB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4GAA4G,kEAAkB;AAC9H;AACA,iDAAiD,kEAAkB;AACnE;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,gEAAgB;AAC3D;AACA,cAAc;AACd,kDAAkD,+DAAe;AACjE,kDAAkD,oEAAmB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,oEAAmB,uBAAuB,+DAAe;AAC3H;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,wDAAe;AACpC;AACA;AACA,UAAU;AACV,qBAAqB,wDAAe;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD,oEAAmB;AACzE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sCAAsC,+DAAe;AACrD;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,wDAAe;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,wDAAe;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,mEAAkB;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,mEAAmB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,wDAAe;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,mEAAmB;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,mEAAkB;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,+DAAc;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,kCAAkC,gEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,wDAAe;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,qCAAqC,kEAAkB;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,mEAAkB;AACnE;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV,0CAA0C,mEAAmB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+CAA+C,wEAAwB;AACvE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,mCAAmC,iDAAO;AAC1C;AACA;AACA;AACA,mCAAmC,iDAAO;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,uDAAuD,wEAAwB;AAC/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+CAA+C,wEAAwB;AACvE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC;AACpC;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,wDAAe;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,wDAAe;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2DAA2D,wEAAwB;AACnF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,mEAAmB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4EAA4E,+DAAe,uBAAuB,oEAAmB,uBAAuB,oEAAmB;AAC/K,kDAAkD,oEAAmB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA,2EAA2E,qEAAoB;AAC/F,+EAA+E,+DAAe;AAC9F;AACA;AACA;AACA,mDAAmD,qEAAoB;AACvE;AACA;AACA;AACA;AACA;AACA;AACA,2EAA2E,uEAAsB;AACjG,+EAA+E,oEAAmB;AAClG;AACA;AACA;AACA,qDAAqD,uEAAsB;AAC3E;AACA;AACA;AACA;AACA;AACA,iGAAiG,qEAAoB;AACrH;AACA;AACA,2EAA2E,qEAAoB;AAC/F;AACA,mDAAmD,qEAAoB;AACvE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kGAAkG,sEAAqB;AACvH;AACA;AACA;AACA,2EAA2E,sEAAqB;AAChG,oDAAoD,sEAAqB;AACzE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,wDAAe;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,wDAAe;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,wDAAe;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mEAAmE,kEAAkB;AACrF;AACA;AACA;AACA,+BAA+B,wDAAe;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,iDAAO;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,wDAAe;AACrC;AACA;AACA;AACA;AACA;AACA,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;;AC3jD/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wDAAwD;AACtF,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,mBAAmB;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;AC/gB1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;;;;;AC9VL;AAC4B;AACtB;AAChC;AACA;AACA;AACA;AACA;AACA,6BAA6B,qDAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;ACtF1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,KAAK,EAAC;;;;;;;;;;;;;;;;;ACXiC;AACmD;AACzG;AACA;AACA;AACA;AACA,uBAAuB,qDAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,iFAAoC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS,EAAC;;;;;;;;;;;;;;;;AC/Ce;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB;;;;;;;;;;;;;;;ACbM;AACxC;AACA,8BAA8B,oDAAW;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;;;ACZS;AACxC;AACA,iCAAiC,oDAAW;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,kBAAkB;;;;;;;;;;;;;;;ACZO;AACxC;AACA,mCAAmC,oDAAW;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,oBAAoB;;;;;;;;;;;;;;;ACZK;AACxC;AACA,uCAAuC,oDAAW;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,wBAAwB,EAAC;;;;;;;;;;;;;;;ACZxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,WAAW,EAAC;;;;;;;;;;;;;;;;ACPa;AACxC;AACA,+BAA+B,oDAAW;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,gBAAgB,EAAC;;;;;;;;;;;;;;;;ACVQ;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB,EAAC;;;;;;;;;;;;;;;;ACfK;AACxC;AACA,iCAAiC,oDAAW;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,kBAAkB,EAAC;;;;;;;;;;;;;;;;ACVM;AACxC;AACA,mCAAmC,oDAAW;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,oBAAoB;;;;;;;;;;;;;;;ACfK;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB,EAAC;;;;;;;;;;;;;;;;AClBK;AACxC;AACA,gCAAgC,oDAAW;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,iBAAiB,EAAC;;;;;;;;;;;;;;;;ACVO;AACxC;AACA,8BAA8B,oDAAW;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;;;ACbJ;AAC3B;AACA;AACA;AACA,2BAA2B,+CAAI;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,+CAAI;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;AChEc;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB;;;;;;;;;;;;;;;ACbM;AACxC;AACA,iCAAiC,oDAAW;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,kBAAkB,EAAC;;;;;;;;;;;;;;;;ACXM;AACxC;AACA,6BAA6B,oDAAW;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,cAAc,EAAC;;;;;;;;;;;;;;;;ACVU;AACxC;AACA,qCAAqC,oDAAW;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,sBAAsB;;;;;;;;;;;;;;;ACZG;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB;;;;;;;;;;;;;;;ACZM;AACxC;AACA,oCAAoC,oDAAW;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,qBAAqB;;;;;;;;;;;;;;;;AChBV;AACsC;AAChE;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,0DAAkB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS;;;;;;;;;;;;;;;;;;AC7BQ;AACM;AACkC;AAC5C;AAC5B;AACA,sBAAsB,mDAAU;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,8DAAsB;AAChD;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,kCAAkC;AAC1D;AACA;AACA;AACA,oCAAoC,gDAAO;AAC3C;AACA;AACA,wBAAwB,kCAAkC;AAC1D,oCAAoC,8CAAK;AACzC;AACA,wBAAwB,sCAAsC;AAC9D;AACA;AACA;AACA,wCAAwC,8CAAK;AAC7C;AACA;AACA;AACA;AACA,0BAA0B,8DAAsB;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;;;;AClJS;AACM;AACA;AACF;AACA;AACI;AACxC;AACA,qBAAqB,mDAAU;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,kDAAS;AACrD;AACA;AACA,qDAAqD,mDAAU;AAC/D,2DAA2D,mDAAU;AACrE,qEAAqE,gDAAO;AAC5E;AACA;AACA,2CAA2C,kDAAS;AACpD;AACA,SAAS;AACT;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,MAAM,EAAC;;;;;;;;;;;;;;;;ACvL6C;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd,2CAA2C,uEAAuE;AAClH;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,4DAAoB;AAChD;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD;AACtD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS,EAAC;;;;;;;;;;;;;;;;ACtPmE;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,wEAAgC;AAC5C;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;ACjLmC;AAC1B;AAChC;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,uDAAe;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yEAAyE,gDAAO;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,yDAAyD;AACxF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,wEAAwE;AACtH;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gDAAgD,gDAAO;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wDAAwD,gDAAO;AAC/D,UAAU;AACV;AACA;AACA;AACA;AACA,cAAc;AACd,gCAAgC,yBAAyB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,iCAAiC;AACpE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,wEAAwE;AAC1H;AACA,0BAA0B;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oDAAoD,gDAAO;AAC3D;AACA;AACA;AACA;AACA,iEAAe,IAAI,EAAC;;;;;;;;;;;;;;;ACjH4C;AAClB;AACJ;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,6DAAI;AAC/B,4BAA4B,wBAAwB;AACpD,oCAAoC,+DAAM;AAC1C;AACA;AACA;AACA;AACA;AACA,sCAAsC,wEAAe;AACrD;AACA,mCAAmC,oIAAoI;AACvK,aAAa;AACb;AACA;AACA;AACA,mCAAmC,iDAAiD;AACpF,cAAc;AACd;AACA,mCAAmC,oCAAoC;AACvE;AACA;AACA;AACA","sources":["webpack://mwicombatsimulator/./src/combatsimulator/ability.js","webpack://mwicombatsimulator/./src/combatsimulator/achievement.js","webpack://mwicombatsimulator/./src/combatsimulator/buff.js","webpack://mwicombatsimulator/./src/combatsimulator/combatSimulator.js","webpack://mwicombatsimulator/./src/combatsimulator/combatUnit.js","webpack://mwicombatsimulator/./src/combatsimulator/combatUtilities.js","webpack://mwicombatsimulator/./src/combatsimulator/consumable.js","webpack://mwicombatsimulator/./src/combatsimulator/drops.js","webpack://mwicombatsimulator/./src/combatsimulator/equipment.js","webpack://mwicombatsimulator/./src/combatsimulator/events/abilityCastEndEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/autoAttackEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/awaitCooldownEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/blindExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/checkBuffExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/combatEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/combatStartEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/consumableTickEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/cooldownReadyEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/curseExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/damageOverTimeEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/enemyRespawnEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/enrageTickEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/eventQueue.js","webpack://mwicombatsimulator/./src/combatsimulator/events/furyExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/playerRespawnEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/regenTickEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/silenceExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/stunExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/weakenExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/houseRoom.js","webpack://mwicombatsimulator/./src/combatsimulator/monster.js","webpack://mwicombatsimulator/./src/combatsimulator/player.js","webpack://mwicombatsimulator/./src/combatsimulator/simResult.js","webpack://mwicombatsimulator/./src/combatsimulator/trigger.js","webpack://mwicombatsimulator/./src/combatsimulator/zone.js","webpack://mwicombatsimulator/./src/worker.js"],"sourcesContent":["import Buff from \"./buff\";\r\nimport abilityDetailMap from \"./data/abilityDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nconst abilityFromCombatStat = {\r\n \"blaze\":\r\n {\r\n \"hrid\": \"/abilities/blaze\",\r\n \"name\": \"Blaze\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"allEnemies\",\r\n \"effectType\": \"/ability_effect_types/damage\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"/damage_types/fire\",\r\n \"baseDamageFlat\": 0,\r\n \"baseDamageFlatLevelBonus\": 0.0,\r\n \"baseDamageRatio\": 0.3,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/number_of_active_units\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n },\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/current_hp\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n }\r\n ],\r\n },\r\n \"bloom\":\r\n {\r\n \"hrid\": \"/abilities/bloom\",\r\n \"name\": \"Bloom\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"lowestHpAlly\",\r\n \"effectType\": \"/ability_effect_types/heal\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"\",\r\n \"baseDamageFlat\": 10,\r\n \"baseDamageFlatLevelBonus\": 0,\r\n \"baseDamageRatio\": 0.15,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_allies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/lowest_hp_percentage\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/less_than_equal\",\r\n \"value\": 100\r\n }\r\n ],\r\n }\r\n}\r\n\r\nclass Ability {\r\n constructor(hrid, level = 1, triggers = null) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameAbility = abilityDetailMap[hrid];\r\n if (!gameAbility) {\r\n gameAbility = abilityFromCombatStat[hrid];\r\n }\r\n if (!gameAbility) {\r\n throw new Error(\"No ability found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.manaCost = gameAbility.manaCost;\r\n this.cooldownDuration = gameAbility.cooldownDuration;\r\n this.castDuration = gameAbility.castDuration;\r\n this.isSpecialAbility = gameAbility.isSpecialAbility;\r\n\r\n this.abilityEffects = [];\r\n\r\n for (const effect of gameAbility.abilityEffects) {\r\n let abilityEffect = {\r\n targetType: effect.targetType,\r\n effectType: effect.effectType,\r\n combatStyleHrid: effect.combatStyleHrid,\r\n damageType: effect.damageType,\r\n damageFlat: effect.baseDamageFlat + (this.level - 1) * effect.baseDamageFlatLevelBonus,\r\n damageRatio: effect.baseDamageRatio + (this.level - 1) * effect.baseDamageRatioLevelBonus,\r\n bonusAccuracyRatio: effect.bonusAccuracyRatio + (this.level - 1) * effect.bonusAccuracyRatioLevelBonus,\r\n damageOverTimeRatio: effect.damageOverTimeRatio,\r\n damageOverTimeDuration: effect.damageOverTimeDuration,\r\n armorDamageRatio: effect.armorDamageRatio + (this.level - 1) * effect.armorDamageRatioLevelBonus,\r\n hpDrainRatio: effect.hpDrainRatio,\r\n pierceChance: effect.pierceChance,\r\n blindChance: effect.blindChance,\r\n blindDuration: effect.blindDuration,\r\n silenceChance: effect.silenceChance,\r\n silenceDuration: effect.silenceDuration,\r\n stunChance: effect.stunChance,\r\n stunDuration: effect.stunDuration,\r\n spendHpRatio: effect.spendHpRatio,\r\n buffs: null,\r\n };\r\n if (effect.buffs) {\r\n abilityEffect.buffs = [];\r\n for (const buff of effect.buffs) {\r\n abilityEffect.buffs.push(new Buff(buff, this.level));\r\n }\r\n }\r\n this.abilityEffects.push(abilityEffect);\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameAbility.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let ability = new Ability(dto.hrid, dto.level, triggers);\r\n\r\n return ability;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n\r\n if (source.isSilenced) {\r\n return false;\r\n }\r\n\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = this.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Ability;\r\n","import Buff from \"./buff\";\r\nimport achievementTierDetailMap from \"./data/achievementTierDetailMap.json\";\r\nimport achievementDetailMap from \"./data/achievementDetailMap.json\";\r\n\r\nclass Achievement {\r\n constructor(achievements) {\r\n this.achievements = achievements;\r\n this.buffs = [];\r\n\r\n for(const tier of Object.values(achievementTierDetailMap)) {\r\n let isGetAll = true;\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid)\r\n for(const achievement of Object.values(detailMap)) {\r\n if(!this.achievements[achievement.hrid] || this.achievements[achievement.hrid] == false) {\r\n isGetAll = false;\r\n break;\r\n }\r\n }\r\n if(isGetAll) {\r\n let buff = new Buff(tier.buff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default Achievement;","class Buff {\r\n startTime;\r\n\r\n constructor(buff, level = 1) {\r\n this.uniqueHrid = buff.uniqueHrid;\r\n this.typeHrid = buff.typeHrid;\r\n this.ratioBoost = buff.ratioBoost + (level - 1) * buff.ratioBoostLevelBonus;\r\n this.flatBoost = buff.flatBoost + (level - 1) * buff.flatBoostLevelBonus;\r\n this.duration = buff.duration;\r\n this.multiplierForSkillHrid = buff.multiplierForSkillHrid ?? \"\";\r\n this.multiplierPerSkillLevel = buff.multiplierPerSkillLevel ?? 0;\r\n }\r\n}\r\n\r\nexport default Buff;\r\n","import CombatUtilities from \"./combatUtilities\";\r\nimport AutoAttackEvent from \"./events/autoAttackEvent\";\r\nimport DamageOverTimeEvent from \"./events/damageOverTimeEvent\";\r\nimport CheckBuffExpirationEvent from \"./events/checkBuffExpirationEvent\";\r\nimport CombatStartEvent from \"./events/combatStartEvent\";\r\nimport ConsumableTickEvent from \"./events/consumableTickEvent\";\r\nimport CooldownReadyEvent from \"./events/cooldownReadyEvent\";\r\nimport EnemyRespawnEvent from \"./events/enemyRespawnEvent\";\r\nimport EventQueue from \"./events/eventQueue\";\r\nimport PlayerRespawnEvent from \"./events/playerRespawnEvent\";\r\nimport RegenTickEvent from \"./events/regenTickEvent\";\r\nimport StunExpirationEvent from \"./events/stunExpirationEvent\";\r\nimport BlindExpirationEvent from \"./events/blindExpirationEvent\";\r\nimport SilenceExpirationEvent from \"./events/silenceExpirationEvent\";\r\nimport CurseExpirationEvent from \"./events/curseExpirationEvent\";\r\nimport WeakenExpirationEvent from \"./events/weakenExpirationEvent\";\r\nimport FuryExpirationEvent from \"./events/furyExpirationEvent\";\r\nimport EnrageTickEvent from \"./events/enrageTickEvent\";\r\nimport SimResult from \"./simResult\";\r\nimport AbilityCastEndEvent from \"./events/abilityCastEndEvent\";\r\nimport AwaitCooldownEvent from \"./events/awaitCooldownEvent\";\r\nimport Monster from \"./monster\";\r\nimport Ability from \"./ability\";\r\n\r\nconst ONE_SECOND = 1e9;\r\nconst HOT_TICK_INTERVAL = 5 * ONE_SECOND;\r\nconst DOT_TICK_INTERVAL = 3 * ONE_SECOND;\r\nconst REGEN_TICK_INTERVAL = 10 * ONE_SECOND;\r\nconst ENEMY_RESPAWN_INTERVAL = 3 * ONE_SECOND;\r\nconst PLAYER_RESPAWN_INTERVAL = 150 * ONE_SECOND;\r\nconst RESTART_INTERVAL = 15 * ONE_SECOND;\r\nconst ENRAGE_TICK_INTERVAL = 60 * ONE_SECOND;\r\n\r\nclass CombatSimulator extends EventTarget {\r\n constructor(players, zone) {\r\n super();\r\n this.players = players;\r\n this.zone = zone;\r\n this.eventQueue = new EventQueue();\r\n this.simResult = new SimResult(zone, players.length);\r\n this.allPlayersDead = false;\r\n\r\n this.wipeLogs = {\r\n buffer: new Array(200),\r\n index: 0,\r\n count: 0,\r\n maxSize: 200\r\n };\r\n }\r\n\r\n addToWipeLogs(logEntry) {\r\n const { buffer, maxSize } = this.wipeLogs;\r\n\r\n buffer[this.wipeLogs.index] = logEntry;\r\n this.wipeLogs.index = (this.wipeLogs.index + 1) % maxSize;\r\n this.wipeLogs.count = Math.min(this.wipeLogs.count + 1, maxSize);\r\n }\r\n\r\n logAndResetWipeLogs() {\r\n const logs = this.getOrderedWipeLogs();\r\n \r\n // console.log(\"===== 团灭日志 =====\");\r\n // console.log(`最后 ${logs.length} 条战斗日志:`);\r\n \r\n logs.forEach(log => {\r\n if (log.error) {\r\n console.log(log.error);\r\n return;\r\n }\r\n \r\n const time = (log.time / 1e9).toFixed(2);\r\n // console.log(\r\n // `[${time}s] [${log.source}] 用 [${log.ability}] ` +\r\n // `对 ${log.target} 造成 ${log.damage} 伤害,` +\r\n // `HP ${log.beforeHp} → ${log.afterHp}。` +\r\n // `队伍生命值:${log.playersHp.map(p => `${p.hrid}: ${p.current}/${p.max}`).join(\" | \")}`\r\n // );\r\n });\r\n\r\n this.wipeLogs.index = 0;\r\n this.wipeLogs.count = 0;\r\n // console.log(\"===== 团灭日志结束 =====\");\r\n }\r\n \r\n buildCombatLog(source, ability, target, damageDone) {\r\n try {\r\n const sourceHrid = source?.hrid || \"UNKNOWN_SOURCE\";\r\n const targetHrid = target?.hrid || \"UNKNOWN_TARGET\";\r\n \r\n const afterHp = target?.combatDetails?.currentHitpoints || 0;\r\n const beforeHp = Math.max(0, afterHp + damageDone);\r\n\r\n const playersHp = this.players.map(p => ({\r\n hrid: p.hrid || \"UNKNOWN_PLAYER\",\r\n current: p.combatDetails?.currentHitpoints ?? 0,\r\n max: p.combatDetails?.maxHitpoints ?? 0\r\n }));\r\n \r\n return {\r\n time: this.simulationTime,\r\n wave: (this.zone.encountersKilled - 1),\r\n source: sourceHrid,\r\n ability: ability,\r\n target: targetHrid,\r\n damage: damageDone,\r\n beforeHp: beforeHp,\r\n afterHp: afterHp,\r\n playersHp: playersHp,\r\n // enemiesHp: enemiesHp,\r\n isCrit: false,\r\n };\r\n } catch (e) {\r\n return {\r\n error: `[日志生成错误] ${e.message}`\r\n };\r\n }\r\n }\r\n\r\n generateCombatLog(source, ability, target, attackResult) {\r\n try {\r\n const sourceHrid = source?.hrid || \"UNKNOWN_SOURCE\";\r\n const targetHrid = target?.hrid || \"UNKNOWN_TARGET\";\r\n const damage = attackResult?.damageDone || 0;\r\n \r\n const afterHp = target?.combatDetails?.currentHitpoints || 0;\r\n const beforeHp = Math.max(0, afterHp + damage);\r\n\r\n const playersHp = this.players.map(p => ({\r\n hrid: p.hrid || \"UNKNOWN_PLAYER\",\r\n current: p.combatDetails?.currentHitpoints ?? 0,\r\n max: p.combatDetails?.maxHitpoints ?? 0\r\n }));\r\n \r\n return {\r\n time: this.simulationTime,\r\n wave: (this.zone.encountersKilled - 1),\r\n source: sourceHrid,\r\n ability: ability,\r\n target: targetHrid,\r\n damage: damage,\r\n beforeHp: beforeHp,\r\n afterHp: afterHp,\r\n playersHp: playersHp,\r\n // enemiesHp: enemiesHp,\r\n isCrit: attackResult?.isCrit || false,\r\n };\r\n } catch (e) {\r\n return {\r\n error: `[日志生成错误] ${e.message}`\r\n };\r\n }\r\n }\r\n \r\n getOrderedWipeLogs() {\r\n const { buffer, maxSize, count } = this.wipeLogs;\r\n const logs = [];\r\n \r\n for (let i = 0; i < count; i++) {\r\n const idx = (this.wipeLogs.index - count + maxSize + i) % maxSize;\r\n logs.push(buffer[idx]);\r\n }\r\n \r\n return logs;\r\n }\r\n\r\n saveWipeLogsToSimResult(wave) {\r\n const logs = this.getOrderedWipeLogs();\r\n this.simResult.addWipeEvent(logs, this.simulationTime, wave);\r\n }\r\n\r\n async simulate(simulationTimeLimit) {\r\n this.reset();\r\n\r\n let ticks = 0;\r\n\r\n let combatStartEvent = new CombatStartEvent(0);\r\n this.eventQueue.addEvent(combatStartEvent);\r\n\r\n while (this.simulationTime < simulationTimeLimit) {\r\n let nextEvent = this.eventQueue.getNextEvent();\r\n await this.processEvent(nextEvent);\r\n\r\n ticks++;\r\n if (ticks == 1000) {\r\n ticks = 0;\r\n let progressEvent = new CustomEvent(\"progress\", {\r\n detail: {\r\n zone: this.zone.hrid,\r\n difficultyTier: this.zone.difficultyTier,\r\n progress: Math.min(this.simulationTime / simulationTimeLimit, 1)\r\n },\r\n });\r\n this.dispatchEvent(progressEvent);\r\n }\r\n }\r\n\r\n // for (let i = 0; i < this.simResult.timeSpentAlive.length; i++) {\r\n // if (this.simResult.timeSpentAlive[i].alive == true) {\r\n // this.simResult.updateTimeSpentAlive(this.simResult.timeSpentAlive[i].name, false, simulationTimeLimit);\r\n // }\r\n // }\r\n\r\n this.simResult.isDungeon = this.zone.isDungeon;\r\n if (this.simResult.isDungeon) {\r\n console.log(\"Timeout now at wave #\" + (this.zone.encountersKilled - 1));\r\n\r\n this.simResult.dungeonsCompleted = this.zone.dungeonsCompleted;\r\n this.simResult.dungeonsFailed = this.zone.dungeonsFailed;\r\n if (this.simResult.dungeonsCompleted < 1) {\r\n this.simResult.maxWaveReached = 0;\r\n for (let i = 0; i <= this.zone.dungeonSpawnInfo.maxWaves; i++) {\r\n let waveName = \"#\" + i.toString();\r\n const idx = this.simResult.timeSpentAlive.findIndex(e => e.name === waveName);\r\n if (idx == -1 || this.simResult.timeSpentAlive[idx].count == 0) {\r\n break;\r\n }\r\n this.simResult.maxWaveReached = i;\r\n }\r\n } else {\r\n this.simResult.maxWaveReached = this.zone.dungeonSpawnInfo.maxWaves;\r\n }\r\n }\r\n this.simResult.simulatedTime = this.simulationTime;\r\n \r\n for (let i = 0; i < this.players.length; i++) {\r\n this.simResult.setDropRateMultipliers(this.players[i]);\r\n this.simResult.setManaUsed(this.players[i]);\r\n }\r\n\r\n if (this.zone.isDungeon) {\r\n Object.entries(this.zone.dungeonSpawnInfo.fixedSpawnsMap).forEach(([wave, monsters]) => {\r\n let waveName = \"#\" + wave.toString();\r\n monsters.forEach(monster => {\r\n waveName += ',' + monster.combatMonsterHrid;\r\n });\r\n this.simResult.bossSpawns.push(waveName);\r\n });\r\n\r\n }\r\n if (this.zone.monsterSpawnInfo.bossSpawns) {\r\n for (const boss of this.zone.monsterSpawnInfo.bossSpawns) {\r\n this.simResult.bossSpawns.push(boss.combatMonsterHrid);\r\n }\r\n }\r\n\r\n return this.simResult;\r\n }\r\n\r\n reset() {\r\n this.tempDungeonCount = 0;\r\n this.simulationTime = 0;\r\n this.eventQueue.clear();\r\n this.simResult = new SimResult(this.zone, this.players.length);\r\n }\r\n\r\n async processEvent(event) {\r\n this.simulationTime = event.time;\r\n\r\n // console.log(this.simulationTime / 1e9, event.type, event);\r\n\r\n switch (event.type) {\r\n case CombatStartEvent.type:\r\n this.processCombatStartEvent(event);\r\n break;\r\n case PlayerRespawnEvent.type:\r\n this.processPlayerRespawnEvent(event);\r\n break;\r\n case EnemyRespawnEvent.type:\r\n this.processEnemyRespawnEvent(event);\r\n break;\r\n case AutoAttackEvent.type:\r\n this.processAutoAttackEvent(event);\r\n break;\r\n case ConsumableTickEvent.type:\r\n this.processConsumableTickEvent(event);\r\n break;\r\n case DamageOverTimeEvent.type:\r\n this.processDamageOverTimeTickEvent(event);\r\n break;\r\n case CheckBuffExpirationEvent.type:\r\n this.processCheckBuffExpirationEvent(event);\r\n break;\r\n case RegenTickEvent.type:\r\n this.processRegenTickEvent(event);\r\n break;\r\n case StunExpirationEvent.type:\r\n this.processStunExpirationEvent(event);\r\n break;\r\n case BlindExpirationEvent.type:\r\n this.processBlindExpirationEvent(event);\r\n break;\r\n case SilenceExpirationEvent.type:\r\n this.processSilenceExpirationEvent(event);\r\n break;\r\n case CurseExpirationEvent.type:\r\n this.processCurseExpirationEvent(event);\r\n break;\r\n case WeakenExpirationEvent.type:\r\n this.processWeakenExpirationEvent(event);\r\n break;\r\n case FuryExpirationEvent.type:\r\n this.processFuryExpirationEvent(event);\r\n break;\r\n case EnrageTickEvent.type:\r\n this.processEnrageTickEvent(event);\r\n break;\r\n case AbilityCastEndEvent.type:\r\n this.tryUseAbility(event.source, event.ability);\r\n break;\r\n case AwaitCooldownEvent.type:\r\n // console.log(\"Await CD \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(event.source);\r\n break;\r\n case CooldownReadyEvent.type:\r\n // Only used to check triggers\r\n break;\r\n }\r\n\r\n this.checkTriggers();\r\n }\r\n\r\n processCombatStartEvent(event) {\r\n // console.log(\"Combat Start \" + (this.simulationTime / 1000000000));\r\n for (let i = 0; i < this.players.length; i++) {\r\n if (event.time == 0) { // First combat start event\r\n this.players[i].generatePermanentBuffs();\r\n }\r\n this.players[i].reset(this.simulationTime);\r\n }\r\n let regenTickEvent = new RegenTickEvent(this.simulationTime + REGEN_TICK_INTERVAL);\r\n this.eventQueue.addEvent(regenTickEvent);\r\n\r\n this.startNewEncounter();\r\n }\r\n\r\n processPlayerRespawnEvent(event) {\r\n // console.log(\"Player \" + event.hrid + \" respawn at \" + + (this.simulationTime / 1000000000));\r\n let respawningPlayer = this.players.find(player => player.hrid === event.hrid);\r\n respawningPlayer.combatDetails.currentHitpoints = respawningPlayer.combatDetails.maxHitpoints;\r\n respawningPlayer.combatDetails.currentManapoints = respawningPlayer.combatDetails.maxManapoints;\r\n respawningPlayer.clearBuffs();\r\n respawningPlayer.clearCCs();\r\n if (this.allPlayersDead) {\r\n this.allPlayersDead = false;\r\n this.startAttacks();\r\n } else {\r\n this.addNextAttackEvent(respawningPlayer);\r\n }\r\n }\r\n\r\n processEnemyRespawnEvent(event) {\r\n this.startNewEncounter();\r\n }\r\n\r\n startNewEncounter() {\r\n if (this.allPlayersDead) {\r\n this.allPlayersDead = false;\r\n this.zone.failWave();\r\n }\r\n\r\n if (!this.zone.isDungeon) {\r\n this.enemies = this.zone.getRandomEncounter();\r\n } else {\r\n this.enemies = this.zone.getNextWave();\r\n this.simResult.updateTimeSpentAlive(\"#\" + (this.zone.encountersKilled - 1).toString(), true, this.simulationTime);\r\n let currentDungeonCount = this.zone.dungeonsCompleted;\r\n // console.log('wave at #' + (this.zone.encountersKilled - 1) +' completed:' + this.zone.dungeonsCompleted + ' failed:'+ this.zone.dungeonsFailed + ' temp:'+ this.tempDungeonCount);\r\n if (currentDungeonCount > this.tempDungeonCount) {\r\n this.tempDungeonCount = currentDungeonCount;\r\n for (let i = 0; i < this.players.length; i++) {\r\n this.players[i].combatDetails.currentHitpoints = this.players[i].combatDetails.maxHitpoints;\r\n this.players[i].combatDetails.currentManapoints = this.players[i].combatDetails.maxManapoints;\r\n // this.simResult.playerRanOutOfMana[this.players[i].hrid] = false;\r\n }\r\n }\r\n }\r\n\r\n this.enemies.forEach((enemy) => {\r\n enemy.reset(this.simulationTime);\r\n this.simResult.updateTimeSpentAlive(enemy.hrid, true, this.simulationTime);\r\n //console.log(enemy.hrid, \"spawned\");\r\n });\r\n\r\n this.eventQueue.clearEventsOfType(EnrageTickEvent.type);\r\n let enrageTickEvent = new EnrageTickEvent(this.simulationTime + ENRAGE_TICK_INTERVAL, ENRAGE_TICK_INTERVAL);\r\n this.eventQueue.addEvent(enrageTickEvent);\r\n this.enrageBeginTime = this.simulationTime;\r\n\r\n this.eventQueue.clearEventsOfType(AbilityCastEndEvent.type);\r\n\r\n this.startAttacks();\r\n }\r\n\r\n startAttacks() {\r\n let units = [...this.players];\r\n if (this.enemies) {\r\n units.push(...this.enemies);\r\n }\r\n\r\n for (const unit of units) {\r\n if (unit.combatDetails.currentHitpoints <= 0) {\r\n continue;\r\n }\r\n\r\n /*-if (unit.isPlayer) {\r\n // console.log(\"Start Attacks \" + (this.simulationTime / 1000000000));\r\n }*/\r\n this.addNextAttackEvent(unit);\r\n }\r\n }\r\n\r\n checkParry(targets) {\r\n let parryUnits = targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0 && unit.combatDetails.combatStats.parry > 0);\r\n if (parryUnits.length <= 0) {\r\n return undefined;\r\n }\r\n let randomIndex = Math.floor(Math.random() * parryUnits.length);\r\n if (parryUnits[randomIndex].combatDetails.combatStats.parry > Math.random()) {\r\n return parryUnits[randomIndex];\r\n }\r\n return undefined;\r\n }\r\n\r\n processAutoAttackEvent(event) {\r\n // console.log(\"source:\", event.source.hrid);\r\n // console.log(\"aa \" + (this.simulationTime / 1000000000));\r\n\r\n let targets = event.source.isPlayer ? this.enemies : this.players;\r\n\r\n if (!targets) {\r\n return;\r\n }\r\n\r\n const aliveTargets = targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0);\r\n\r\n for (let i = 0; i < aliveTargets.length; i++) {\r\n let target = aliveTargets[i];\r\n if (!event.source.isPlayer && aliveTargets.length > 1) {\r\n let cumulativeThreat = 0;\r\n let cumulativeRanges = [];\r\n aliveTargets.forEach(player => {\r\n let playerThreat = player.combatDetails.combatStats.threat;\r\n cumulativeThreat += playerThreat;\r\n cumulativeRanges.push({\r\n player: player,\r\n rangeStart: cumulativeThreat - playerThreat,\r\n rangeEnd: cumulativeThreat\r\n });\r\n });\r\n let randomValueHit = Math.random() * cumulativeThreat;\r\n target = cumulativeRanges.find(range => randomValueHit >= range.rangeStart && randomValueHit < range.rangeEnd).player;\r\n }\r\n let source = event.source;\r\n\r\n let parryTarget = this.checkParry(targets);\r\n if (parryTarget) {\r\n target = source;\r\n source = parryTarget;\r\n }\r\n\r\n let attackResult = CombatUtilities.processAttack(source, target);\r\n if (this.zone.isDungeon && target.isPlayer && attackResult.didHit && attackResult.damageDone > 0) {\r\n const log = this.generateCombatLog(source, \"autoAttack\", target, attackResult);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n let mayhem = source.combatDetails.combatStats.mayhem > Math.random();\r\n\r\n if (attackResult.didHit && source.combatDetails.combatStats.curse > 0) {\r\n const curseExpireTime = 15000000000;\r\n let currentCurseEvent = this.eventQueue.getMatching((event) => event.type == CurseExpirationEvent.type && event.source == target);\r\n let currentCurseAmount = 0;\r\n if (currentCurseEvent) currentCurseAmount = currentCurseEvent.curseAmount;\r\n this.eventQueue.clearMatching((event) => event.type == CurseExpirationEvent.type && event.source == target);\r\n\r\n let curseExpirationEvent = new CurseExpirationEvent(this.simulationTime + curseExpireTime, currentCurseAmount, target);\r\n const curseBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/curse\",\r\n \"typeHrid\": \"/buff_types/damage_taken\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": source.combatDetails.combatStats.curse * curseExpirationEvent.curseAmount,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": curseExpireTime\r\n };\r\n target.addBuff(curseBuff);\r\n this.eventQueue.addEvent(curseExpirationEvent);\r\n }\r\n\r\n if (source.combatDetails.combatStats.fury > 0) {\r\n let currentFuryEvent = this.eventQueue.getMatching((event) => event.type == FuryExpirationEvent.type && event.source == source);\r\n this.eventQueue.clearMatching((event) => event.type == FuryExpirationEvent.type && event.source == source);\r\n\r\n const furyExpireTime = 15000000000;\r\n const maxFuryStack = 5;\r\n\r\n let furyAmount = 0;\r\n if (currentFuryEvent) furyAmount = currentFuryEvent.furyAmount;\r\n\r\n if (attackResult.didHit) {\r\n furyAmount = Math.min(furyAmount + 1, maxFuryStack);\r\n } else {\r\n furyAmount = Math.floor(furyAmount / 2);\r\n }\r\n\r\n const furyAccuracyBuf = {\r\n \"uniqueHrid\": \"/buff_uniques/fury_accuracy\",\r\n \"typeHrid\": \"/buff_types/fury_accuracy\",\r\n \"ratioBoost\": furyAmount * source.combatDetails.combatStats.fury,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": furyExpireTime\r\n };\r\n const furyDamageBuf = {\r\n \"uniqueHrid\": \"/buff_uniques/fury_damage\",\r\n \"typeHrid\": \"/buff_types/fury_damage\",\r\n \"ratioBoost\": furyAmount * source.combatDetails.combatStats.fury,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": furyExpireTime\r\n };\r\n\r\n if (furyAmount > 0) {\r\n let furyExpirationEvent = new FuryExpirationEvent(this.simulationTime + furyExpireTime, furyAmount, source);\r\n this.eventQueue.addEvent(furyExpirationEvent);\r\n\r\n source.addBuff(furyAccuracyBuf, this.simulationTime);\r\n source.addBuff(furyDamageBuf, this.simulationTime);\r\n }\r\n else {\r\n source.removeBuff(furyAccuracyBuf);\r\n source.removeBuff(furyDamageBuf);\r\n }\r\n }\r\n\r\n if (target.combatDetails.combatStats.weaken > 0) {\r\n const weakenExpireTime = 15000000000;\r\n let currentWeakenEvent = this.eventQueue.getMatching((event) => event.type == WeakenExpirationEvent.type && event.source == source);\r\n let weakenAmount = 0;\r\n if (currentWeakenEvent)\r\n weakenAmount = currentWeakenEvent.weakenAmount;\r\n this.eventQueue.clearMatching((event) => event.type == WeakenExpirationEvent.type && event.source == source);\r\n let weakenExpirationEvent = new WeakenExpirationEvent(this.simulationTime + 15000000000, weakenAmount, source);\r\n const weakenBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/weaken\",\r\n \"typeHrid\": \"/buff_types/damage\",\r\n \"ratioBoost\": -1 * target.combatDetails.combatStats.weaken * weakenExpirationEvent.weakenAmount,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": weakenExpireTime\r\n };\r\n source.addBuff(weakenBuff);\r\n this.eventQueue.addEvent(weakenExpirationEvent);\r\n }\r\n\r\n if (!mayhem || (mayhem && attackResult.didHit) || (mayhem && i == (aliveTargets.length - 1))) {\r\n let attackType = \"autoAttack\";\r\n if (parryTarget) attackType = \"parry\";\r\n this.simResult.addAttack(\r\n source,\r\n target,\r\n \"autoAttack\",\r\n attackResult.didHit ? attackResult.damageDone : \"miss\"\r\n );\r\n }\r\n\r\n if (attackResult.lifeStealHeal > 0) {\r\n this.simResult.addHitpointsGained(source, \"lifesteal\", attackResult.lifeStealHeal);\r\n }\r\n\r\n if (attackResult.manaLeechMana > 0) {\r\n this.simResult.addManapointsGained(source, \"manaLeech\", attackResult.manaLeechMana);\r\n }\r\n\r\n if (attackResult.thornDamageDone > 0) {\r\n this.simResult.addAttack(target, source, attackResult.thornType, attackResult.thornDamageDone);\r\n }\r\n if (this.zone.isDungeon && attackResult.thornDamageDone > 0 && source.isPlayer) {\r\n const log = this.buildCombatLog(target, attackResult.thornType, source, attackResult.thornDamageDone);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (target.combatDetails.combatStats.retaliation > 0) {\r\n this.simResult.addAttack(target, source, \"retaliation\", attackResult.retaliationDamageDone > 0?attackResult.retaliationDamageDone:\"miss\");\r\n }\r\n if (this.zone.isDungeon && attackResult.retaliationDamageDone > 0 && source.isPlayer) {\r\n const log = this.buildCombatLog(target, \"retaliation\", source, attackResult.retaliationDamageDone);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (target.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(target);\r\n this.simResult.addDeath(target);\r\n if (!target.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(target.hrid, false, this.simulationTime);\r\n }\r\n // console.log(target.hrid, \"died\");\r\n }\r\n\r\n // Could die from reflect damage\r\n if (source.combatDetails.currentHitpoints == 0 && \r\n (attackResult.thornDamageDone != 0 || attackResult.retaliationDamageDone != 0)\r\n ) {\r\n this.eventQueue.clearEventsForUnit(source);\r\n this.simResult.addDeath(source);\r\n if (!source.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(source.hrid, false, this.simulationTime);\r\n }\r\n break;\r\n }\r\n\r\n if (mayhem && !attackResult.didHit) {\r\n continue;\r\n }\r\n\r\n if (!attackResult.didHit || parryTarget || source.combatDetails.combatStats.pierce <= Math.random()) {\r\n break;\r\n }\r\n }\r\n\r\n if (!this.checkEncounterEnd()) {\r\n // console.log(\"!EncounterEnd \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(event.source);\r\n }\r\n }\r\n\r\n checkEncounterEnd() {\r\n if (this.enemies) {\r\n let deadEnemies = this.enemies.filter((enemy) => enemy.combatDetails.currentHitpoints <= 0 && enemy.experienceRate == 0);\r\n if (deadEnemies.length > 0) {\r\n deadEnemies.forEach(enemy => {\r\n let aliveDuration = this.simulationTime - this.enrageBeginTime;\r\n if (aliveDuration > enemy.enrageTime) {\r\n aliveDuration = enemy.enrageTime;\r\n }\r\n enemy.experienceRate = 1.0 + aliveDuration / enemy.enrageTime;\r\n // console.log(enemy.hrid, \"alive duration\", aliveDuration, \"exp rate\", enemy.experienceRate);\r\n })\r\n }\r\n }\r\n\r\n let encounterEnded = false;\r\n\r\n if (this.enemies && !this.enemies.some((enemy) => enemy.combatDetails.currentHitpoints > 0)) {\r\n this.eventQueue.clearEventsOfType(AutoAttackEvent.type);\r\n // this.eventQueue.clearEventsOfType(AbilityCastEndEvent.type);\r\n let enemyRespawnEvent = new EnemyRespawnEvent(this.simulationTime + ENEMY_RESPAWN_INTERVAL);\r\n this.eventQueue.addEvent(enemyRespawnEvent);\r\n\r\n //calc exp before clear\r\n if (this.enemies.some(enemy => enemy.experienceRate <= 0)) {\r\n console.log(\"WARN: Some enemies have no experience rate\");\r\n }\r\n\r\n let totalExp = this.enemies.map(enemy => enemy.experience * enemy.experienceRate).reduce((a, b) => a + b, 0);\r\n this.players.forEach(player => {\r\n this.simResult.addExperienceGain(player, totalExp / this.players.length);\r\n });\r\n\r\n this.enemies = null;\r\n\r\n if (this.zone.isDungeon) {\r\n this.simResult.updateTimeSpentAlive(\"#\" + (this.zone.encountersKilled - 1).toString(), false, this.simulationTime);\r\n }\r\n this.simResult.addEncounterEnd();\r\n // console.log(\"All enemies died\");\r\n\r\n encounterEnded = true;\r\n // console.log(\"encounter end \" + (this.simulationTime / 1000000000))\r\n }\r\n\r\n this.players.forEach(player => {\r\n if ((player.combatDetails.currentHitpoints <= 0) && !this.eventQueue.containsEventOfTypeAndHrid(PlayerRespawnEvent.type, player.hrid)) {\r\n if (!this.zone.isDungeon) {\r\n let playerRespawnEvent = new PlayerRespawnEvent(this.simulationTime + PLAYER_RESPAWN_INTERVAL, player.hrid);\r\n this.eventQueue.addEvent(playerRespawnEvent);\r\n }\r\n this.simResult.addRanOutOfManaCount(player, false, this.simulationTime);\r\n // console.log(player.hrid + \" died at \" + (this.simulationTime / 1000000000) + 'in wave #' + (this.zone.encountersKilled - 1) + ' with ememies: ' + this.enemies?.map(enemy => (enemy.hrid+\"(\"+(enemy.combatDetails.currentHitpoints*100/enemy.combatDetails.maxHitpoints).toFixed(2)+\"%)\")).join(\", \"));\r\n }\r\n });\r\n\r\n if (\r\n !this.players.some((player) => player.combatDetails.currentHitpoints > 0)\r\n ) {\r\n if (this.zone.isDungeon) {\r\n console.log(\"All Players died at wave #\" + (this.zone.encountersKilled - 1) + \" with ememies: \" + this.enemies.map(enemy => (enemy.hrid+\"(\"+(enemy.combatDetails.currentHitpoints*100/enemy.combatDetails.maxHitpoints).toFixed(2)+\"%)\")).join(\", \"));\r\n\r\n this.saveWipeLogsToSimResult(this.zone.encountersKilled - 1);\r\n // console.log(this.simResult)\r\n this.wipeLogs.index = 0;\r\n this.wipeLogs.count = 0;\r\n\r\n this.eventQueue.clear();\r\n this.enemies = null;\r\n\r\n let combatStartEvent = new CombatStartEvent(this.simulationTime + RESTART_INTERVAL);\r\n this.eventQueue.addEvent(combatStartEvent);\r\n } else {\r\n this.eventQueue.clearEventsOfType(AutoAttackEvent.type);\r\n this.eventQueue.clearEventsOfType(AbilityCastEndEvent.type);\r\n }\r\n // console.log(\"All Players died\");\r\n encounterEnded = true;\r\n this.allPlayersDead = true;\r\n }\r\n\r\n return encounterEnded;\r\n }\r\n\r\n addNextAttackEvent(source) {\r\n if (this.eventQueue.getMatching((event) => (event.type == AbilityCastEndEvent.type || event.type == AutoAttackEvent.type)&& event.source == source)) {\r\n return;\r\n }\r\n\r\n let target;\r\n let friendlies;\r\n let enemies;\r\n if (source.isPlayer) {\r\n target = CombatUtilities.getTarget(this.enemies);\r\n friendlies = this.players;\r\n enemies = this.enemies;\r\n } else {\r\n target = CombatUtilities.getTarget(this.players);\r\n friendlies = this.enemies;\r\n enemies = this.players;\r\n }\r\n\r\n let usedAbility = false;\r\n let skipNextAbility = false; \r\n\r\n source.abilities\r\n .filter((ability) => ability != null)\r\n .forEach((ability) => {\r\n if (!usedAbility && !skipNextAbility && ability.shouldTrigger(this.simulationTime, source, target, friendlies, enemies)) {\r\n if (!this.canUseAbility(source, ability, true)) {\r\n skipNextAbility = true;\r\n }\r\n\r\n if (!skipNextAbility) {\r\n let castDuration = ability.castDuration;\r\n castDuration /= (1 + source.combatDetails.combatStats.castSpeed)\r\n let abilityCastEndEvent = new AbilityCastEndEvent(this.simulationTime + castDuration, source, ability);\r\n this.eventQueue.addEvent(abilityCastEndEvent);\r\n /*-if (source.isPlayer) {\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n // console.log((this.simulationTime / 1000000000) + \" Casting \" + ability.hrid + \" Cast time \" + (castDuration / 1e9) + \" Off CD at \" + ((this.simulationTime + cooldownDuration + castDuration) / 1e9) + \" CD \" + ((cooldownDuration) / 1e9));\r\n }*/\r\n usedAbility = true;\r\n }\r\n }\r\n });\r\n\r\n if (usedAbility) {\r\n source.isOutOfMana = false;\r\n return;\r\n }\r\n\r\n if (!enemies) {\r\n return;\r\n }\r\n\r\n if (!source.isBlinded) {\r\n let autoAttackEvent = new AutoAttackEvent(\r\n this.simulationTime + source.combatDetails.combatStats.attackInterval,\r\n source\r\n );\r\n /*-if (source.isPlayer) {\r\n // console.log(\"next attack \" + ((this.simulationTime + source.combatDetails.combatStats.attackInterval) / 1e9))\r\n }*/\r\n this.eventQueue.addEvent(autoAttackEvent);\r\n } else {\r\n source.isOutOfMana = true;\r\n }\r\n }\r\n\r\n processConsumableTickEvent(event) {\r\n if (event.consumable.hitpointRestore > 0) {\r\n let tickValue = CombatUtilities.calculateTickValue(\r\n event.consumable.hitpointRestore,\r\n event.totalTicks,\r\n event.currentTick\r\n );\r\n let hitpointsAdded = event.source.addHitpoints(tickValue);\r\n this.simResult.addHitpointsGained(event.source, event.consumable.hrid, hitpointsAdded);\r\n // console.log(\"Added hitpoints:\", hitpointsAdded);\r\n }\r\n\r\n if (event.consumable.manapointRestore > 0) {\r\n let tickValue = CombatUtilities.calculateTickValue(\r\n event.consumable.manapointRestore,\r\n event.totalTicks,\r\n event.currentTick\r\n );\r\n let manapointsAdded = event.source.addManapoints(tickValue);\r\n this.simResult.addManapointsGained(event.source, event.consumable.hrid, manapointsAdded);\r\n // console.log(\"Added manapoints:\", manapointsAdded);\r\n\r\n // when oom check ability trigger\r\n if (event.source.isOutOfMana) {\r\n let awaitCooldownEvent = new AwaitCooldownEvent(\r\n this.simulationTime,\r\n event.source\r\n );\r\n this.eventQueue.addEvent(awaitCooldownEvent);\r\n }\r\n }\r\n\r\n if (event.currentTick < event.totalTicks) {\r\n let consumableTickEvent = new ConsumableTickEvent(\r\n this.simulationTime + HOT_TICK_INTERVAL,\r\n event.source,\r\n event.consumable,\r\n event.totalTicks,\r\n event.currentTick + 1\r\n );\r\n this.eventQueue.addEvent(consumableTickEvent);\r\n }\r\n }\r\n\r\n processDamageOverTimeTickEvent(event) {\r\n let tickDamage = CombatUtilities.calculateTickValue(event.damage, event.totalTicks, event.currentTick);\r\n let damage = Math.min(tickDamage, event.target.combatDetails.currentHitpoints);\r\n\r\n event.target.combatDetails.currentHitpoints -= damage;\r\n this.simResult.addAttack(event.sourceRef, event.target, \"damageOverTime\", damage);\r\n\r\n const log = this.buildCombatLog(\"\", \"damageOverTime\", event.target, damage);\r\n this.addToWipeLogs(log);\r\n\r\n // console.log(event.target.hrid, \"bleed for\", damage);\r\n\r\n if (event.currentTick < event.totalTicks) {\r\n let damageOverTimeTickEvent = new DamageOverTimeEvent(\r\n this.simulationTime + DOT_TICK_INTERVAL,\r\n event.sourceRef,\r\n event.target,\r\n event.damage,\r\n event.totalTicks,\r\n event.currentTick + 1,\r\n event.combatStyleHrid\r\n );\r\n this.eventQueue.addEvent(damageOverTimeTickEvent);\r\n }\r\n\r\n if (event.target.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(event.target);\r\n this.simResult.addDeath(event.target);\r\n if (!event.target.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(event.target.hrid, false, this.simulationTime);\r\n }\r\n }\r\n\r\n this.checkEncounterEnd();\r\n }\r\n\r\n processRegenTickEvent(event) {\r\n let units = [...this.players];\r\n\r\n // regen of emeny always set to 0, ingore the proc time\r\n // if (this.enemies) {\r\n // units.push(...this.enemies);\r\n // }\r\n\r\n for (const unit of units) {\r\n if (unit.combatDetails.currentHitpoints <= 0) {\r\n continue;\r\n }\r\n\r\n let hitpointRegen = Math.floor(unit.combatDetails.maxHitpoints * unit.combatDetails.combatStats.hpRegenPer10);\r\n let hitpointsAdded = unit.addHitpoints(hitpointRegen);\r\n this.simResult.addHitpointsGained(unit, \"regen\", hitpointsAdded);\r\n // console.log(\"Added hitpoints:\", hitpointsAdded);\r\n\r\n let manapointRegen = Math.floor(unit.combatDetails.maxManapoints * unit.combatDetails.combatStats.mpRegenPer10);\r\n let manapointsAdded = unit.addManapoints(manapointRegen);\r\n this.simResult.addManapointsGained(unit, \"regen\", manapointsAdded);\r\n // console.log(\"Added manapoints:\", manapointsAdded);\r\n\r\n // when oom check ability trigger\r\n if (unit.isOutOfMana) {\r\n let awaitCooldownEvent = new AwaitCooldownEvent(\r\n this.simulationTime,\r\n unit\r\n );\r\n this.eventQueue.addEvent(awaitCooldownEvent);\r\n }\r\n }\r\n\r\n let regenTickEvent = new RegenTickEvent(this.simulationTime + REGEN_TICK_INTERVAL);\r\n this.eventQueue.addEvent(regenTickEvent);\r\n }\r\n\r\n processCheckBuffExpirationEvent(event) {\r\n event.source.removeExpiredBuffs(this.simulationTime);\r\n }\r\n\r\n processStunExpirationEvent(event) {\r\n event.source.isStunned = false;\r\n // console.log(\"Stun \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(event.source);\r\n }\r\n\r\n processBlindExpirationEvent(event) {\r\n event.source.isBlinded = false;\r\n this.addNextAttackEvent(event.source);\r\n }\r\n\r\n processSilenceExpirationEvent(event) {\r\n event.source.isSilenced = false;\r\n }\r\n\r\n processCurseExpirationEvent(event) {\r\n event.source.removeExpiredBuffs(this.simulationTime);\r\n }\r\n\r\n processWeakenExpirationEvent(event) {\r\n event.source.removeExpiredBuffs(this.simulationTime);\r\n }\r\n\r\n processFuryExpirationEvent(event) {\r\n event.source.removeExpiredBuffs(this.simulationTime);\r\n console.log(\"Fury Timeout\");\r\n }\r\n\r\n processEnrageTickEvent(event) {\r\n if (!this.enemies) return;\r\n const maxEnrageStack = 10;\r\n this.enemies.filter((enemy) => enemy.combatDetails.currentHitpoints > 0).forEach((enemy) => {\r\n let nowStack = Math.min(maxEnrageStack, Math.floor(event.encounterTime / enemy.enrageTime));\r\n\r\n if (nowStack <= 0) {\r\n return;\r\n }\r\n\r\n console.log(enemy.hrid, nowStack, \" stack Enrage at \", (event.encounterTime / ONE_SECOND));\r\n\r\n const enrageDamageBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/enrage_damage\",\r\n \"typeHrid\": \"/buff_types/damage\",\r\n \"ratioBoost\": nowStack * 0.1,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": ENRAGE_TICK_INTERVAL\r\n };\r\n const enrageAccuracyBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/enrage_accuracy\",\r\n \"typeHrid\": \"/buff_types/accuracy\",\r\n \"ratioBoost\": nowStack * 0.1,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": ENRAGE_TICK_INTERVAL\r\n };\r\n enemy.addBuff(enrageDamageBuff);\r\n enemy.addBuff(enrageAccuracyBuff);\r\n \r\n this.simResult.maxEnrageStack = Math.max(this.simResult.maxEnrageStack, nowStack);\r\n });\r\n\r\n let enrageTickEvent = new EnrageTickEvent(this.simulationTime + ENRAGE_TICK_INTERVAL, event.encounterTime + ENRAGE_TICK_INTERVAL);\r\n this.eventQueue.addEvent(enrageTickEvent);\r\n }\r\n\r\n checkTriggers() {\r\n let triggeredSomething;\r\n\r\n do {\r\n triggeredSomething = false;\r\n\r\n this.players\r\n .filter((player) => player.combatDetails.currentHitpoints > 0)\r\n .forEach((player) => {\r\n if (this.checkTriggersForUnit(player, this.players, this.enemies)) {\r\n triggeredSomething = true;\r\n }\r\n });\r\n\r\n if (this.enemies) {\r\n this.enemies\r\n .filter((enemy) => enemy.combatDetails.currentHitpoints > 0)\r\n .forEach((enemy) => {\r\n if (this.checkTriggersForUnit(enemy, this.enemies, this.players)) {\r\n triggeredSomething = true;\r\n }\r\n });\r\n }\r\n } while (triggeredSomething);\r\n }\r\n\r\n checkTriggersForUnit(unit, friendlies, enemies) {\r\n if (unit.combatDetails.currentHitpoints <= 0) {\r\n throw new Error(\"Checking triggers for a dead unit\");\r\n }\r\n\r\n let triggeredSomething = false;\r\n let target = CombatUtilities.getTarget(enemies);\r\n\r\n for (const food of unit.food) {\r\n if (food && food.shouldTrigger(this.simulationTime, unit, target, friendlies, enemies)) {\r\n let result = this.tryUseConsumable(unit, food);\r\n if (result) {\r\n triggeredSomething = true;\r\n }\r\n }\r\n }\r\n\r\n for (const drink of unit.drinks) {\r\n if (drink && drink.shouldTrigger(this.simulationTime, unit, target, friendlies, enemies)) {\r\n let result = this.tryUseConsumable(unit, drink);\r\n if (result) {\r\n triggeredSomething = true;\r\n }\r\n }\r\n }\r\n\r\n return triggeredSomething;\r\n }\r\n\r\n tryUseConsumable(source, consumable) {\r\n // console.log(\"Consuming:\", consumable);\r\n\r\n if (source.combatDetails.currentHitpoints <= 0) {\r\n return false;\r\n }\r\n\r\n consumable.lastUsed = this.simulationTime;\r\n let consumeCooldown = consumable.cooldownDuration;\r\n if (source.combatDetails.combatStats.drinkConcentration > 0 && consumable.catagoryHrid.includes(\"drink\")) {\r\n consumeCooldown = consumeCooldown / (1 + source.combatDetails.combatStats.drinkConcentration);\r\n } else if (source.combatDetails.combatStats.foodHaste > 0 && consumable.catagoryHrid.includes(\"food\")) {\r\n consumeCooldown = consumeCooldown / (1 + source.combatDetails.combatStats.foodHaste);\r\n }\r\n let cooldownReadyEvent = new CooldownReadyEvent(this.simulationTime + consumeCooldown);\r\n this.eventQueue.addEvent(cooldownReadyEvent);\r\n\r\n this.simResult.addConsumableUse(source, consumable);\r\n\r\n if (consumable.recoveryDuration == 0) {\r\n if (consumable.hitpointRestore > 0) {\r\n let hitpointsAdded = source.addHitpoints(consumable.hitpointRestore);\r\n this.simResult.addHitpointsGained(source, consumable.hrid, hitpointsAdded);\r\n // console.log(\"Added hitpoints:\", hitpointsAdded);\r\n }\r\n\r\n if (consumable.manapointRestore > 0) {\r\n let manapointsAdded = source.addManapoints(consumable.manapointRestore);\r\n this.simResult.addManapointsGained(source, consumable.hrid, manapointsAdded);\r\n // console.log(\"Added manapoints:\", manapointsAdded);\r\n\r\n // when oom check ability trigger\r\n if (source.isOutOfMana) {\r\n let awaitCooldownEvent = new AwaitCooldownEvent(\r\n this.simulationTime,\r\n source\r\n );\r\n this.eventQueue.addEvent(awaitCooldownEvent);\r\n }\r\n }\r\n } else {\r\n let consumableTickEvent = new ConsumableTickEvent(\r\n this.simulationTime + HOT_TICK_INTERVAL,\r\n source,\r\n consumable,\r\n consumable.recoveryDuration / HOT_TICK_INTERVAL,\r\n 1\r\n );\r\n this.eventQueue.addEvent(consumableTickEvent);\r\n }\r\n\r\n for (const buff of consumable.buffs) {\r\n let currentBuff = structuredClone(buff);\r\n if (source.combatDetails.combatStats.drinkConcentration > 0 && consumable.catagoryHrid.includes(\"drink\")) {\r\n currentBuff.ratioBoost *= (1 + source.combatDetails.combatStats.drinkConcentration);\r\n currentBuff.flatBoost *= (1 + source.combatDetails.combatStats.drinkConcentration);\r\n currentBuff.duration = currentBuff.duration / (1 + source.combatDetails.combatStats.drinkConcentration);\r\n }\r\n source.addBuff(currentBuff, this.simulationTime);\r\n // console.log(\"Added buff:\", currentBuff);\r\n let checkBuffExpirationEvent = new CheckBuffExpirationEvent(this.simulationTime + currentBuff.duration, source);\r\n this.eventQueue.addEvent(checkBuffExpirationEvent);\r\n }\r\n\r\n return true;\r\n }\r\n\r\n canUseAbility(source, ability, oomCheck) {\r\n if (source.combatDetails.currentHitpoints <= 0) {\r\n return false;\r\n }\r\n\r\n if (source.combatDetails.currentManapoints < ability.manaCost) {\r\n if (source.isPlayer && oomCheck) {\r\n // if (this.simResult.playerRanOutOfMana[source.hrid] == false) {\r\n // console.log(source.hrid + \" ran out of mana\" + ' at wave #' + (this.zone.encountersKilled - 1) + ' at time ' + this.simulationTime / 1000000000 + 's');\r\n // }\r\n this.simResult.addRanOutOfManaCount(source, true, this.simulationTime);\r\n }\r\n return false;\r\n }\r\n if (source.isPlayer && oomCheck) {\r\n this.simResult.addRanOutOfManaCount(source, false, this.simulationTime);\r\n }\r\n return true;\r\n }\r\n\r\n tryUseAbility(source, ability) {\r\n\r\n if (!this.canUseAbility(source, ability, true)) {\r\n // console.log(\"Falseeeeeee\");\r\n return false;\r\n }\r\n\r\n // console.log(\"Casting:\", ability);\r\n\r\n if (source.isPlayer) {\r\n if (source.abilityManaCosts.has(ability.hrid)) {\r\n source.abilityManaCosts.set(ability.hrid, source.abilityManaCosts.get(ability.hrid) + ability.manaCost);\r\n } else {\r\n source.abilityManaCosts.set(ability.hrid, ability.manaCost);\r\n }\r\n }\r\n\r\n source.combatDetails.currentManapoints -= ability.manaCost;\r\n\r\n ability.lastUsed = this.simulationTime;\r\n\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n\r\n /*-if (source.isPlayer) {\r\n let castDuration = ability.castDuration;\r\n castDuration /= (1 + source.combatDetails.combatStats.castSpeed)\r\n // console.log((this.simulationTime / 1000000000) + \" Used ability \" + ability.hrid + \" Cast time \" + (castDuration / 1e9));\r\n }*/\r\n\r\n let todoAbilities = [ability];\r\n\r\n if (source.combatDetails.combatStats.blaze > 0 && Math.random() < source.combatDetails.combatStats.blaze) {\r\n todoAbilities.push(new Ability(\"blaze\"));\r\n }\r\n\r\n if (source.combatDetails.combatStats.bloom > 0 && Math.random() < source.combatDetails.combatStats.bloom) {\r\n todoAbilities.push(new Ability(\"bloom\"));\r\n }\r\n\r\n for (const todoAbility of todoAbilities) {\r\n for (const abilityEffect of todoAbility.abilityEffects) {\r\n switch (abilityEffect.effectType) {\r\n case \"/ability_effect_types/buff\":\r\n this.processAbilityBuffEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/damage\":\r\n this.processAbilityDamageEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/heal\":\r\n this.processAbilityHealEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/spend_hp\":\r\n this.processAbilitySpendHpEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/revive\":\r\n this.processAbilityReviveEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/promote\":\r\n this.eventQueue.clearEventsForUnit(source);\r\n source = this.processAbilityPromoteEffect(source, todoAbility, abilityEffect);\r\n this.addNextAttackEvent(source);\r\n break;\r\n default:\r\n throw new Error(\"Unsupported effect type for ability: \" + todoAbility.hrid + \" effectType: \" + abilityEffect.effectType);\r\n }\r\n }\r\n }\r\n\r\n if (source.combatDetails.combatStats.ripple > 0 && Math.random() < source.combatDetails.combatStats.ripple) {\r\n let manapointsAdded = source.addManapoints(10);\r\n this.simResult.addManapointsGained(source, \"ripple\", manapointsAdded);\r\n for (const ability of source.abilities) {\r\n if (ability && ability.lastUsed) {\r\n const remainingCooldown = ability.lastUsed + ability.cooldownDuration - this.simulationTime;\r\n if (remainingCooldown > 0) {\r\n ability.lastUsed = Math.max(ability.lastUsed - ONE_SECOND * 2, this.simulationTime - ability.cooldownDuration);\r\n }\r\n }\r\n }\r\n }\r\n\r\n this.addNextAttackEvent(source);\r\n\r\n // Could die from reflect damage\r\n if (source.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(source);\r\n this.simResult.addDeath(source);\r\n if (!source.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(source.hrid, false, this.simulationTime);\r\n }\r\n }\r\n\r\n this.checkEncounterEnd();\r\n\r\n return true;\r\n }\r\n\r\n processAbilityBuffEffect(source, ability, abilityEffect) {\r\n if (abilityEffect.targetType == \"allAllies\") {\r\n let targets = source.isPlayer ? this.players : this.enemies;\r\n for (const target of targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0)) {\r\n for (const buff of abilityEffect.buffs) {\r\n if (ability.isSpecialAbility && buff.multiplierForSkillHrid && buff.multiplierPerSkillLevel > 0) {\r\n let multiplier = 1.0 + source.combatDetails[buff.multiplierForSkillHrid.split('/')[2] + 'Level'] * buff.multiplierPerSkillLevel;\r\n let currentBuff = structuredClone(buff);\r\n currentBuff.flatBoost *= multiplier;\r\n target.addBuff(currentBuff, this.simulationTime);\r\n } else {\r\n target.addBuff(buff, this.simulationTime);\r\n }\r\n let checkBuffExpirationEvent = new CheckBuffExpirationEvent(this.simulationTime + buff.duration, target);\r\n this.eventQueue.addEvent(checkBuffExpirationEvent);\r\n }\r\n }\r\n return;\r\n }\r\n\r\n if (abilityEffect.targetType != \"self\") {\r\n throw new Error(\"Unsupported target type for buff ability effect: \" + ability.hrid);\r\n }\r\n\r\n for (const buff of abilityEffect.buffs) {\r\n source.addBuff(buff, this.simulationTime);\r\n // console.log(\"Added buff:\", abilityEffect.buff);\r\n let checkBuffExpirationEvent = new CheckBuffExpirationEvent(this.simulationTime + buff.duration, source);\r\n this.eventQueue.addEvent(checkBuffExpirationEvent);\r\n }\r\n }\r\n\r\n processAbilityDamageEffect(source, ability, abilityEffect) {\r\n let targets;\r\n switch (abilityEffect.targetType) {\r\n case \"enemy\":\r\n case \"allEnemies\":\r\n targets = source.isPlayer ? this.enemies : this.players;\r\n break;\r\n default:\r\n throw new Error(\"Unsupported target type for damage ability effect: \" + ability.hrid);\r\n }\r\n\r\n if (!targets) {\r\n return;\r\n }\r\n\r\n let avoidTarget = [];\r\n\r\n let isSkipParry = false;\r\n\r\n for (let target of targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0)) {\r\n let parryTarget = undefined;\r\n if (!isSkipParry) {\r\n parryTarget = this.checkParry(targets);\r\n isSkipParry = true; // parry check only once on first target\r\n }\r\n \r\n if (parryTarget) {\r\n let tempTarget = source;\r\n let tempSource = parryTarget;\r\n\r\n let attackResult = CombatUtilities.processAttack(tempSource, tempTarget);\r\n\r\n this.simResult.addAttack(\r\n tempSource,\r\n tempTarget,\r\n \"parry\",\r\n attackResult.didHit ? attackResult.damageDone : \"miss\"\r\n );\r\n\r\n if (attackResult.lifeStealHeal > 0) {\r\n this.simResult.addHitpointsGained(tempSource, \"lifesteal\", attackResult.lifeStealHeal);\r\n }\r\n\r\n if (attackResult.manaLeechMana > 0) {\r\n this.simResult.addManapointsGained(tempSource, \"manaLeech\", attackResult.manaLeechMana);\r\n }\r\n\r\n if (attackResult.thornDamageDone > 0) {\r\n this.simResult.addAttack(tempTarget, tempSource, attackResult.thornType, attackResult.thornDamageDone);\r\n }\r\n if (tempTarget.combatDetails.combatStats.retaliation > 0) {\r\n this.simResult.addAttack(tempTarget, tempSource, \"retaliation\", attackResult.retaliationDamageDone > 0 ? attackResult.retaliationDamageDone : \"miss\");\r\n }\r\n\r\n if (tempTarget.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(tempTarget);\r\n this.simResult.addDeath(tempTarget);\r\n if (!tempTarget.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(tempTarget.hrid, false, this.simulationTime);\r\n }\r\n // console.log(tempTarget.hrid, \"died\");\r\n }\r\n\r\n // Could die from reflect damage\r\n if (tempSource.combatDetails.currentHitpoints == 0 && \r\n (attackResult.thornDamageDone != 0 || attackResult.retaliationDamageDone != 0)\r\n ) {\r\n this.eventQueue.clearEventsForUnit(tempSource);\r\n this.simResult.addDeath(tempSource);\r\n if (!tempSource.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(tempSource.hrid, false, this.simulationTime);\r\n }\r\n }\r\n } else {\r\n targets = targets.filter((unit) => unit && !avoidTarget.includes(unit.hrid) && unit.combatDetails.currentHitpoints > 0);\r\n if (!source.isPlayer && targets.length > 0 && abilityEffect.targetType == \"enemy\") {\r\n let cumulativeThreat = 0;\r\n let cumulativeRanges = [];\r\n targets.forEach(player => {\r\n let playerThreat = player.combatDetails.combatStats.threat;\r\n cumulativeThreat += playerThreat;\r\n cumulativeRanges.push({\r\n player: player,\r\n rangeStart: cumulativeThreat - playerThreat,\r\n rangeEnd: cumulativeThreat\r\n });\r\n });\r\n let randomValueHit = Math.random() * cumulativeThreat;\r\n target = cumulativeRanges.find(range => randomValueHit >= range.rangeStart && randomValueHit < range.rangeEnd).player;\r\n avoidTarget.push(target.hrid);\r\n }\r\n if (targets.length <= 0) {\r\n break;\r\n }\r\n\r\n let attackResult = CombatUtilities.processAttack(source, target, abilityEffect);\r\n\r\n if (this.zone.isDungeon && target.isPlayer && attackResult.didHit && attackResult.damageDone > 0) {\r\n const log = this.generateCombatLog(source, ability.hrid, target, attackResult);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (attackResult.hpDrain > 0) {\r\n this.simResult.addHitpointsGained(source, ability.hrid, attackResult.hpDrain);\r\n }\r\n\r\n if (attackResult.didHit && abilityEffect.buffs) {\r\n for (const buff of abilityEffect.buffs) {\r\n target.addBuff(buff, this.simulationTime);\r\n let checkBuffExpirationEvent = new CheckBuffExpirationEvent(\r\n this.simulationTime + buff.duration,\r\n target\r\n );\r\n this.eventQueue.addEvent(checkBuffExpirationEvent);\r\n }\r\n }\r\n\r\n if (abilityEffect.damageOverTimeRatio > 0 && attackResult.damageDone > 0) {\r\n let damageOverTimeEvent = new DamageOverTimeEvent(\r\n this.simulationTime + DOT_TICK_INTERVAL,\r\n source,\r\n target,\r\n attackResult.damageDone * abilityEffect.damageOverTimeRatio,\r\n abilityEffect.damageOverTimeDuration / DOT_TICK_INTERVAL,\r\n 1, abilityEffect.combatStyleHrid\r\n );\r\n this.eventQueue.addEvent(damageOverTimeEvent);\r\n }\r\n\r\n if (attackResult.didHit && abilityEffect.stunChance > 0 && Math.random() < (abilityEffect.stunChance * 100 / (100 + target.combatDetails.combatStats.tenacity))) {\r\n target.isStunned = true;\r\n target.stunExpireTime = this.simulationTime + abilityEffect.stunDuration;\r\n this.eventQueue.clearMatching((event) => (event.type == AutoAttackEvent.type || event.type == AbilityCastEndEvent.type || event.type == StunExpirationEvent.type) && event.source == target);\r\n let stunExpirationEvent = new StunExpirationEvent(target.stunExpireTime, target);\r\n this.eventQueue.addEvent(stunExpirationEvent);\r\n }\r\n\r\n if (attackResult.didHit && abilityEffect.blindChance > 0 && Math.random() < (abilityEffect.blindChance * 100 / (100 + target.combatDetails.combatStats.tenacity))) {\r\n target.isBlinded = true;\r\n target.blindExpireTime = this.simulationTime + abilityEffect.blindDuration;\r\n this.eventQueue.clearMatching((event) => event.type == BlindExpirationEvent.type && event.source == target)\r\n if (this.eventQueue.clearMatching((event) => event.type == AutoAttackEvent.type && event.source == target)) {\r\n // console.log(\"Blind \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(target);\r\n }\r\n let blindExpirationEvent = new BlindExpirationEvent(target.blindExpireTime, target);\r\n this.eventQueue.addEvent(blindExpirationEvent);\r\n }\r\n\r\n if (attackResult.didHit && abilityEffect.silenceChance > 0 && Math.random() < (abilityEffect.silenceChance * 100 / (100 + target.combatDetails.combatStats.tenacity))) {\r\n target.isSilenced = true;\r\n target.silenceExpireTime = this.simulationTime + abilityEffect.silenceDuration;\r\n this.eventQueue.clearMatching((event) => event.type == SilenceExpirationEvent.type && event.source == target)\r\n if (this.eventQueue.clearMatching((event) => event.type == AbilityCastEndEvent.type && event.source == target)) {\r\n // console.log(\"Silence \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(target);\r\n }\r\n let silenceExpirationEvent = new SilenceExpirationEvent(target.silenceExpireTime, target);\r\n this.eventQueue.addEvent(silenceExpirationEvent);\r\n }\r\n\r\n if (attackResult.didHit && source.combatDetails.combatStats.curse > 0 && Math.random() < (100 / (100 + target.combatDetails.combatStats.tenacity))) {\r\n const curseExpireTime = 15000000000;\r\n let currentCurseEvent = this.eventQueue.getMatching((event) => event.type == CurseExpirationEvent.type && event.source == target);\r\n let currentCurseAmount = 0;\r\n if (currentCurseEvent) currentCurseAmount = currentCurseEvent.curseAmount;\r\n this.eventQueue.clearMatching((event) => event.type == CurseExpirationEvent.type && event.source == target);\r\n\r\n let curseExpirationEvent = new CurseExpirationEvent(this.simulationTime + curseExpireTime, currentCurseAmount, target);\r\n const curseBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/curse\",\r\n \"typeHrid\": \"/buff_types/damage_taken\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": source.combatDetails.combatStats.curse * curseExpirationEvent.curseAmount,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": curseExpireTime\r\n };\r\n target.addBuff(curseBuff);\r\n this.eventQueue.addEvent(curseExpirationEvent);\r\n }\r\n\r\n if (target.combatDetails.combatStats.weaken > 0) {\r\n const weakenExpireTime = 15000000000;\r\n source.weakenExpireTime = this.simulationTime + weakenExpireTime;\r\n let currentWeakenEvent = this.eventQueue.getMatching((event) => event.type == WeakenExpirationEvent.type && event.source == source);\r\n let weakenAmount = 0;\r\n if (currentWeakenEvent)\r\n weakenAmount = currentWeakenEvent.weakenAmount;\r\n this.eventQueue.clearMatching((event) => event.type == WeakenExpirationEvent.type && event.source == source);\r\n let weakenExpirationEvent = new WeakenExpirationEvent(this.simulationTime + weakenExpireTime, weakenAmount, source);\r\n const weakenBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/weaken\",\r\n \"typeHrid\": \"/buff_types/damage\",\r\n \"ratioBoost\": -1 * target.combatDetails.combatStats.weaken * weakenExpirationEvent.weakenAmount,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": weakenExpireTime\r\n };\r\n source.addBuff(weakenBuff);\r\n this.eventQueue.addEvent(weakenExpirationEvent);\r\n }\r\n\r\n this.simResult.addAttack(\r\n source,\r\n target,\r\n ability.hrid,\r\n attackResult.didHit ? attackResult.damageDone : \"miss\"\r\n );\r\n\r\n if (attackResult.thornDamageDone > 0) {\r\n this.simResult.addAttack(target, source, attackResult.thornType, attackResult.thornDamageDone);\r\n }\r\n if (this.zone.isDungeon && attackResult.thornDamageDone > 0 && source.isPlayer) {\r\n const log = this.buildCombatLog(target, attackResult.thornType, source, attackResult.thornDamageDone);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (target.combatDetails.combatStats.retaliation > 0) {\r\n this.simResult.addAttack(target, source, \"retaliation\", attackResult.retaliationDamageDone > 0 ? attackResult.retaliationDamageDone : \"miss\");\r\n }\r\n if (this.zone.isDungeon && attackResult.retaliationDamageDone > 0 && source.isPlayer) {\r\n const log = this.buildCombatLog(target, \"retaliation\", source, attackResult.retaliationDamageDone);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (target.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(target);\r\n this.simResult.addDeath(target);\r\n if (!target.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(target.hrid, false, this.simulationTime);\r\n }\r\n // console.log(target.hrid, \"died\");\r\n }\r\n\r\n\r\n if (attackResult.didHit && abilityEffect.pierceChance > Math.random()) {\r\n continue;\r\n }\r\n }\r\n \r\n if (parryTarget)\r\n {\r\n break;\r\n }\r\n\r\n if (abilityEffect.targetType == \"enemy\") {\r\n break;\r\n }\r\n }\r\n }\r\n\r\n processAbilityHealEffect(source, ability, abilityEffect) {\r\n\r\n if (abilityEffect.targetType == \"allAllies\") {\r\n let targets = source.isPlayer ? this.players : this.enemies;\r\n for (const target of targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0)) {\r\n let amountHealed = CombatUtilities.processHeal(source, abilityEffect, target);\r\n\r\n this.simResult.addHitpointsGained(target, ability.hrid, amountHealed);\r\n }\r\n return;\r\n }\r\n\r\n if (abilityEffect.targetType == \"lowestHpAlly\") {\r\n let targets = source.isPlayer ? this.players : this.enemies;\r\n let healTarget;\r\n for (const target of targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0)) {\r\n if (!healTarget) {\r\n healTarget = target;\r\n continue;\r\n }\r\n if (target.combatDetails.currentHitpoints < healTarget.combatDetails.currentHitpoints) {\r\n healTarget = target;\r\n }\r\n }\r\n\r\n if (healTarget) {\r\n let amountHealed = CombatUtilities.processHeal(source, abilityEffect, healTarget);\r\n\r\n this.simResult.addHitpointsGained(healTarget, ability.hrid, amountHealed);\r\n }\r\n return;\r\n }\r\n\r\n if (abilityEffect.targetType != \"self\") {\r\n throw new Error(\"Unsupported target type for heal ability effect: \" + ability.hrid);\r\n }\r\n\r\n let amountHealed = CombatUtilities.processHeal(source, abilityEffect, source);\r\n\r\n this.simResult.addHitpointsGained(source, ability.hrid, amountHealed);\r\n }\r\n\r\n processAbilityReviveEffect(source, ability, abilityEffect) {\r\n if (abilityEffect.targetType != \"deadAlly\") {\r\n throw new Error(\"Unsupported target type for revive ability effect: \" + ability.hrid);\r\n }\r\n\r\n let targets = source.isPlayer ? this.players : this.enemies;\r\n let reviveTarget = targets.find((unit) => unit && unit.combatDetails.currentHitpoints <= 0);\r\n\r\n if (reviveTarget) {\r\n this.eventQueue.clearMatching((event) => event.type == PlayerRespawnEvent.type && event.hrid == reviveTarget.hrid);\r\n\r\n reviveTarget.removeExpiredBuffs(this.simulationTime);\r\n\r\n let amountHealed = CombatUtilities.processRevive(source, abilityEffect, reviveTarget);\r\n\r\n this.simResult.addHitpointsGained(reviveTarget, ability.hrid, amountHealed);\r\n\r\n this.addNextAttackEvent(reviveTarget);\r\n\r\n if (!source.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(reviveTarget.hrid, true, this.simulationTime);\r\n }\r\n\r\n // console.log(source.hrid + \" revived \" + reviveTarget.hrid + \" with \" + amountHealed + \" HP.\" + ' at wave #' + (this.zone.encountersKilled - 1) + ' at time ' + this.simulationTime / 1000000000 + 's');\r\n }\r\n return;\r\n }\r\n\r\n processAbilityPromoteEffect(source, ability, abilityEffect) {\r\n const promotionHrids = [\"/monsters/enchanted_rook\", \"/monsters/enchanted_knight\", \"/monsters/enchanted_bishop\"];\r\n let randomPromotionIndex = Math.floor(Math.random() * promotionHrids.length);\r\n return new Monster(promotionHrids[randomPromotionIndex], source.difficultyTier);\r\n }\r\n\r\n processAbilitySpendHpEffect(source, ability, abilityEffect) {\r\n if (abilityEffect.targetType != \"self\") {\r\n throw new Error(\"Unsupported target type for spend hp ability effect: \" + ability.hrid);\r\n }\r\n\r\n let hpSpent = CombatUtilities.processSpendHp(source, abilityEffect);\r\n\r\n this.simResult.addHitpointsSpent(source, ability.hrid, hpSpent);\r\n }\r\n}\r\n\r\nexport default CombatSimulator;\r\n","class CombatUnit {\r\n isPlayer;\r\n isStunned = false;\r\n stunExpireTime = null;\r\n isBlinded = false;\r\n blindExpireTime = null;\r\n isSilenced = false;\r\n silenceExpireTime = null;\r\n\r\n isOutOfMana = false;\r\n\r\n // Base levels which don't change after initialization\r\n staminaLevel = 1;\r\n intelligenceLevel = 1;\r\n attackLevel = 1;\r\n meleeLevel = 1;\r\n defenseLevel = 1;\r\n rangedLevel = 1;\r\n magicLevel = 1;\r\n\r\n experience = 0;\r\n experienceRate = 0;\r\n enrageTime = 0;\r\n\r\n abilities = [null, null, null, null];\r\n food = [null, null, null];\r\n drinks = [null, null, null];\r\n houseRooms = [];\r\n achievements = null;\r\n dropTable = [];\r\n rareDropTable = [];\r\n abilityManaCosts = new Map();\r\n\r\n // Calculated combat stats including temporary buffs\r\n combatDetails = {\r\n staminaLevel: 1,\r\n intelligenceLevel: 1,\r\n attackLevel: 1,\r\n meleeLevel: 1,\r\n defenseLevel: 1,\r\n rangedLevel: 1,\r\n magicLevel: 1,\r\n maxHitpoints: 110,\r\n currentHitpoints: 110,\r\n maxManapoints: 110,\r\n currentManapoints: 110,\r\n stabAccuracyRating: 11,\r\n slashAccuracyRating: 11,\r\n smashAccuracyRating: 11,\r\n rangedAccuracyRating: 11,\r\n magicAccuracyRating: 11,\r\n stabMaxDamage: 11,\r\n slashMaxDamage: 11,\r\n smashMaxDamage: 11,\r\n rangedMaxDamage: 11,\r\n magicMaxDamage: 11,\r\n stabEvasionRating: 11,\r\n slashEvasionRating: 11,\r\n smashEvasionRating: 11,\r\n rangedEvasionRating: 11,\r\n magicEvasionRating: 11,\r\n defensiveMaxDamage: 0,\r\n totalArmor: 0.2,\r\n totalWaterResistance: 0.4,\r\n totalNatureResistance: 0.4,\r\n totalFireResistance: 0.4,\r\n abilityHaste: 0,\r\n tenacity: 0,\r\n totalThreat: 100,\r\n combatStats: {\r\n combatStyleHrid: \"/combat_styles/smash\",\r\n damageType: \"/damage_types/physical\",\r\n attackInterval: 3000000000,\r\n autoAttackDamage: 0,\r\n abilityDamage: 0,\r\n criticalRate: 0,\r\n criticalDamage: 0,\r\n stabAccuracy: 0,\r\n slashAccuracy: 0,\r\n smashAccuracy: 0,\r\n rangedAccuracy: 0,\r\n magicAccuracy: 0,\r\n stabDamage: 0,\r\n slashDamage: 0,\r\n smashDamage: 0,\r\n rangedDamage: 0,\r\n magicDamage: 0,\r\n defensiveDamage: 0,\r\n taskDamage: 0,\r\n physicalAmplify: 0,\r\n waterAmplify: 0,\r\n natureAmplify: 0,\r\n fireAmplify: 0,\r\n healingAmplify: 0,\r\n physicalThorns: 0,\r\n elementalThorns: 0,\r\n maxHitpoints: 0,\r\n maxManapoints: 0,\r\n stabEvasion: 0,\r\n slashEvasion: 0,\r\n smashEvasion: 0,\r\n rangedEvasion: 0,\r\n magicEvasion: 0,\r\n armor: 0,\r\n waterResistance: 0,\r\n natureResistance: 0,\r\n fireResistance: 0,\r\n lifeSteal: 0,\r\n hpRegenPer10: 0.01,\r\n mpRegenPer10: 0.01,\r\n combatDropRate: 0,\r\n combatDropQuantity: 0,\r\n combatRareFind: 0,\r\n combatExperience: 0,\r\n foodSlots: 1,\r\n drinkSlots: 1,\r\n armorPenetration: 0,\r\n waterPenetration: 0,\r\n naturePenetration: 0,\r\n firePenetration: 0,\r\n manaLeech: 0,\r\n castSpeed: 0,\r\n threat: 100,\r\n parry: 0,\r\n mayhem: 0,\r\n pierce: 0,\r\n curse: 0,\r\n ripple: 0,\r\n bloom: 0,\r\n blaze: 0,\r\n weaken: 0,\r\n fury: 0,\r\n foodHaste: 0,\r\n drinkConcentration: 0,\r\n damageTaken: 0,\r\n attackSpeed: 0,\r\n armorDamageRatio: 0,\r\n hpDrainRatio: 0,\r\n primaryTraining: \"\",\r\n focusTraining: \"\",\r\n staminaExperience: 0,\r\n intelligenceExperience: 0,\r\n attackExperience: 0,\r\n defenseExperience: 0,\r\n meleeExperience: 0,\r\n rangedExperience: 0,\r\n magicExperience: 0,\r\n retaliation: 0,\r\n },\r\n };\r\n combatBuffs = {};\r\n permanentBuffs = {};\r\n zoneBuffs = {};\r\n extraBuffs = {};\r\n\r\n constructor() { }\r\n\r\n updateCombatDetails() {\r\n if (this.isPlayer) {\r\n if (this.combatDetails.combatStats.hpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01 + this.combatDetails.combatStats.hpRegenPer10;\r\n }\r\n if (this.combatDetails.combatStats.mpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01 + this.combatDetails.combatStats.mpRegenPer10;\r\n }\r\n }\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((stat) => {\r\n this.combatDetails[stat + \"Level\"] = this[stat + \"Level\"];\r\n let boosts = this.getBuffBoosts(\"/buff_types/\" + stat + \"_level\");\r\n boosts.forEach((buff) => {\r\n this.combatDetails[stat + \"Level\"] += (this[stat + \"Level\"] * buff.ratioBoost);\r\n this.combatDetails[stat + \"Level\"] += buff.flatBoost;\r\n });\r\n });\r\n\r\n this.combatDetails.maxHitpoints = Math.floor\r\n (10 * (10 + this.combatDetails.staminaLevel) + this.combatDetails.combatStats.maxHitpoints);\r\n this.combatDetails.maxManapoints = Math.floor\r\n (10 * (10 + this.combatDetails.intelligenceLevel) + this.combatDetails.combatStats.maxManapoints);\r\n\r\n let accuracyRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_accuracy\").ratioBoost;\r\n let damageRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_damage\").ratioBoost;\r\n // if (accuracyRatioBoostFromFury > 0) {\r\n // console.log(\"Fury Boost: \" + accuracyRatioBoostFromFury);\r\n // }\r\n\r\n let accuracyRatioBoost = this.getBuffBoost(\"/buff_types/accuracy\").ratioBoost;\r\n let damageRatioBoost = this.getBuffBoost(\"/buff_types/damage\").ratioBoost;\r\n\r\n [\"stab\", \"slash\", \"smash\"].forEach((style) => {\r\n this.combatDetails[style + \"AccuracyRating\"] =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Accuracy\"]) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails[style + \"MaxDamage\"] =\r\n (10 + this.combatDetails.meleeLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Damage\"]) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n let baseEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats[style + \"Evasion\"]);\r\n this.combatDetails[style + \"EvasionRating\"] = baseEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails[style + \"EvasionRating\"] += boost.flatBoost;\r\n this.combatDetails[style + \"EvasionRating\"] += baseEvasion * boost.ratioBoost;\r\n }\r\n });\r\n\r\n this.combatDetails.defensiveMaxDamage = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.defensiveDamage);\r\n\r\n // when equiped bulwark\r\n if (this.equipment?.['/equipment_types/two_hand']?.hrid.includes(\"bulwark\")) {\r\n this.combatDetails.smashMaxDamage += this.combatDetails.defensiveMaxDamage;\r\n }\r\n\r\n this.combatDetails.rangedAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.rangedAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.rangedMaxDamage =\r\n (10 + this.combatDetails.rangedLevel) *\r\n (1 + this.combatDetails.combatStats.rangedDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseRangedEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.rangedEvasion);\r\n this.combatDetails.rangedEvasionRating = baseRangedEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.rangedEvasionRating += boost.flatBoost;\r\n this.combatDetails.rangedEvasionRating += baseRangedEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.damageTaken = this.getBuffBoost(\"/buff_types/damage_taken\").flatBoost;\r\n // if (this.combatDetails.combatStats.damageTaken > 0) {\r\n // console.log(\"Damage taken: \" + this.combatDetails.combatStats.damageTaken);\r\n // }\r\n\r\n this.combatDetails.magicAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.magicAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.magicMaxDamage =\r\n (10 + this.combatDetails.magicLevel) *\r\n (1 + this.combatDetails.combatStats.magicDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseMagicEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.magicEvasion);\r\n this.combatDetails.magicEvasionRating = baseMagicEvasion;\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.magicEvasionRating += boost.flatBoost;\r\n this.combatDetails.magicEvasionRating += baseMagicEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.physicalAmplify += this.getBuffBoost(\"/buff_types/physical_amplify\").flatBoost;\r\n this.combatDetails.combatStats.waterAmplify += this.getBuffBoost(\"/buff_types/water_amplify\").flatBoost;\r\n this.combatDetails.combatStats.natureAmplify += this.getBuffBoost(\"/buff_types/nature_amplify\").flatBoost;\r\n this.combatDetails.combatStats.fireAmplify += this.getBuffBoost(\"/buff_types/fire_amplify\").flatBoost;\r\n\r\n this.combatDetails.combatStats.attackInterval /= (1 + (this.combatDetails.attackLevel / 2000));\r\n\r\n let baseAttackSpeed = this.combatDetails.combatStats.attackSpeed;\r\n this.combatDetails.combatStats.attackInterval /= (1 + baseAttackSpeed);\r\n let attackIntervalBoosts = this.getBuffBoosts(\"/buff_types/attack_speed\");\r\n let attackIntervalRatioBoost = attackIntervalBoosts\r\n .map((boost) => boost.ratioBoost)\r\n .reduce((prev, cur) => prev + cur, 0);\r\n this.combatDetails.combatStats.attackInterval /= (1 + attackIntervalRatioBoost);\r\n\r\n let baseArmor = 0.2 * this.combatDetails.defenseLevel + this.combatDetails.combatStats.armor;\r\n this.combatDetails.totalArmor = baseArmor;\r\n let armorBoosts = this.getBuffBoosts(\"/buff_types/armor\");\r\n for (const boost of armorBoosts) {\r\n this.combatDetails.totalArmor += boost.flatBoost;\r\n this.combatDetails.totalArmor += baseArmor * boost.ratioBoost;\r\n }\r\n\r\n let baseWaterResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.waterResistance;\r\n this.combatDetails.totalWaterResistance = baseWaterResistance;\r\n let waterResistanceBoosts = this.getBuffBoosts(\"/buff_types/water_resistance\");\r\n for (const boost of waterResistanceBoosts) {\r\n this.combatDetails.totalWaterResistance += boost.flatBoost;\r\n this.combatDetails.totalWaterResistance += baseWaterResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseNatureResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.natureResistance;\r\n this.combatDetails.totalNatureResistance = baseNatureResistance;\r\n let natureResistanceBoosts = this.getBuffBoosts(\"/buff_types/nature_resistance\");\r\n for (const boost of natureResistanceBoosts) {\r\n this.combatDetails.totalNatureResistance += boost.flatBoost;\r\n this.combatDetails.totalNatureResistance += baseNatureResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseFireResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.fireResistance;\r\n this.combatDetails.totalFireResistance = baseFireResistance;\r\n let fireResistanceBoosts = this.getBuffBoosts(\"/buff_types/fire_resistance\");\r\n for (const boost of fireResistanceBoosts) {\r\n this.combatDetails.totalFireResistance += boost.flatBoost;\r\n this.combatDetails.totalFireResistance += baseFireResistance * boost.ratioBoost;\r\n }\r\n\r\n let hpRegenBoosts = this.getBuffBoost(\"/buff_types/hp_regen\");\r\n this.combatDetails.combatStats.hpRegenPer10 += this.combatDetails.combatStats.hpRegenPer10 * hpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.hpRegenPer10 += hpRegenBoosts.flatBoost;\r\n\r\n let mpRegenBoosts = this.getBuffBoost(\"/buff_types/mp_regen\");\r\n this.combatDetails.combatStats.mpRegenPer10 += this.combatDetails.combatStats.mpRegenPer10 * mpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.mpRegenPer10 += mpRegenBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.lifeSteal += this.getBuffBoost(\"/buff_types/life_steal\").flatBoost;\r\n this.combatDetails.combatStats.physicalThorns += this.getBuffBoost(\r\n \"/buff_types/physical_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.elementalThorns += this.getBuffBoost(\r\n \"/buff_types/elemental_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.combatExperience += this.getBuffBoost(\"/buff_types/wisdom\").flatBoost;\r\n this.combatDetails.combatStats.criticalRate += this.getBuffBoost(\"/buff_types/critical_rate\").flatBoost;\r\n this.combatDetails.combatStats.criticalDamage += this.getBuffBoost(\"/buff_types/critical_damage\").flatBoost;\r\n\r\n this.combatDetails.combatStats.castSpeed += this.getBuffBoost(\"/buff_types/cast_speed\").flatBoost;\r\n this.combatDetails.combatStats.castSpeed += this.combatDetails[\"attackLevel\"] / 2000;\r\n\r\n let combatDropRateBoosts = this.getBuffBoost(\"/buff_types/combat_drop_rate\");\r\n this.combatDetails.combatStats.combatDropRate += (1 + this.combatDetails.combatStats.combatDropRate) * combatDropRateBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropRate += combatDropRateBoosts.flatBoost;\r\n let combatRareFindBoosts = this.getBuffBoost(\"/buff_types/rare_find\");\r\n this.combatDetails.combatStats.combatRareFind += (1 + this.combatDetails.combatStats.combatRareFind) * combatRareFindBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatRareFind += combatRareFindBoosts.flatBoost;\r\n let combatDropQuantityBoosts = this.getBuffBoost(\"/buff_types/combat_drop_quantity\");\r\n this.combatDetails.combatStats.combatDropQuantity += (1 + this.combatDetails.combatStats.combatDropQuantity) * combatDropQuantityBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropQuantity += combatDropQuantityBoosts.flatBoost;\r\n\r\n let baseThreat = 100 + this.combatDetails.combatStats.threat;\r\n this.combatDetails.totalThreat = baseThreat;\r\n let threatBoosts = this.getBuffBoost(\"/buff_types/threat\");\r\n if (threatBoosts.ratioBoost !== 0) {\r\n this.combatDetails.combatStats.threat += baseThreat * threatBoosts.ratioBoost;\r\n } else {\r\n this.combatDetails.combatStats.threat = baseThreat;\r\n }\r\n this.combatDetails.combatStats.threat += threatBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.retaliation += this.getBuffBoost(\"/buff_types/retaliation\").flatBoost;\r\n }\r\n\r\n addBuff(buff, currentTime) {\r\n buff.startTime = currentTime;\r\n this.combatBuffs[buff.uniqueHrid] = buff;\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n removeBuff(buff) {\r\n if (!this.combatBuffs[buff.uniqueHrid]) {\r\n return;\r\n }\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n addPermanentBuff(buff) {\r\n if (this.permanentBuffs[buff.typeHrid]) {\r\n this.permanentBuffs[buff.typeHrid].flatBoost += buff.flatBoost;\r\n this.permanentBuffs[buff.typeHrid].ratioBoost += buff.ratioBoost;\r\n } else {\r\n this.permanentBuffs[buff.typeHrid] = buff;\r\n }\r\n }\r\n\r\n generatePermanentBuffs() {\r\n for (let i = 0; i < this.houseRooms.length; i++) {\r\n const houseRoom = this.houseRooms[i];\r\n houseRoom.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n\r\n if (this.achievements) {\r\n this.achievements.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.zoneBuffs) {\r\n this.zoneBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.extraBuffs) {\r\n this.extraBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n }\r\n\r\n removeExpiredBuffs(currentTime) {\r\n let expiredBuffs = Object.values(this.combatBuffs).filter(\r\n (buff) => buff.startTime + buff.duration <= currentTime\r\n );\r\n expiredBuffs.forEach((buff) => {\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n });\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearBuffs() {\r\n this.combatBuffs = structuredClone(this.permanentBuffs);\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearCCs() {\r\n this.isStunned = false;\r\n this.stunExpireTime = null;\r\n this.isSilenced = false;\r\n this.silenceExpireTime = null;\r\n this.isBlinded = false;\r\n this.blindExpireTime = null;\r\n this.combatDetails.combatStats.damageTaken = 0;\r\n }\r\n\r\n getBuffBoosts(type) {\r\n let boosts = [];\r\n Object.values(this.combatBuffs)\r\n .filter((buff) => buff.typeHrid == type)\r\n .forEach((buff) => {\r\n boosts.push({ ratioBoost: buff.ratioBoost, flatBoost: buff.flatBoost });\r\n });\r\n\r\n return boosts;\r\n }\r\n\r\n getBuffBoost(type) {\r\n let boosts = this.getBuffBoosts(type);\r\n\r\n let boost = {\r\n ratioBoost: 0,\r\n flatBoost: 0,\r\n };\r\n\r\n for (let i = 0; i < boosts.length; i++) {\r\n boost.ratioBoost += boosts[i]?.ratioBoost ?? 0;\r\n boost.flatBoost += boosts[i]?.flatBoost ?? 0;\r\n }\r\n\r\n return boost;\r\n }\r\n\r\n reset(currentTime = 0) {\r\n this.clearCCs();\r\n this.clearBuffs();\r\n this.updateCombatDetails();\r\n this.resetCooldowns(currentTime);\r\n\r\n this.combatDetails.currentHitpoints = this.combatDetails.maxHitpoints;\r\n this.combatDetails.currentManapoints = this.combatDetails.maxManapoints;\r\n }\r\n\r\n resetCooldowns(currentTime = 0) {\r\n this.food.filter((food) => food != null).forEach((food) => (food.lastUsed = Number.MIN_SAFE_INTEGER));\r\n this.drinks.filter((drink) => drink != null).forEach((drink) => (drink.lastUsed = Number.MIN_SAFE_INTEGER));\r\n\r\n let haste = this.combatDetails.combatStats.abilityHaste;\r\n\r\n this.abilities\r\n .filter((ability) => ability != null)\r\n .forEach((ability) => {\r\n if (this.isPlayer) {\r\n ability.lastUsed = Number.MIN_SAFE_INTEGER;\r\n } else {\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n ability.lastUsed = currentTime - Math.floor(cooldownDuration * 0.5) + Math.floor(Math.random() * cooldownDuration * 0.5);\r\n }\r\n });\r\n }\r\n\r\n addHitpoints(hitpoints) {\r\n let hitpointsAdded = 0;\r\n\r\n if (this.combatDetails.currentHitpoints >= this.combatDetails.maxHitpoints) {\r\n return hitpointsAdded;\r\n }\r\n\r\n let newHitpoints = Math.min(this.combatDetails.currentHitpoints + hitpoints, this.combatDetails.maxHitpoints);\r\n hitpointsAdded = newHitpoints - this.combatDetails.currentHitpoints;\r\n this.combatDetails.currentHitpoints = newHitpoints;\r\n\r\n return hitpointsAdded;\r\n }\r\n\r\n addManapoints(manapoints) {\r\n let manapointsAdded = 0;\r\n\r\n if (this.combatDetails.currentManapoints >= this.combatDetails.maxManapoints) {\r\n return manapointsAdded;\r\n }\r\n\r\n let newManapoints = Math.min(\r\n this.combatDetails.currentManapoints + manapoints,\r\n this.combatDetails.maxManapoints\r\n );\r\n manapointsAdded = newManapoints - this.combatDetails.currentManapoints;\r\n this.combatDetails.currentManapoints = newManapoints;\r\n\r\n return manapointsAdded;\r\n }\r\n}\r\n\r\nexport default CombatUnit;\r\n","class CombatUtilities {\r\n static getTarget(enemies) {\r\n if (!enemies) {\r\n return null;\r\n }\r\n let target = enemies.find((enemy) => enemy.combatDetails.currentHitpoints > 0);\r\n\r\n return target ?? null;\r\n }\r\n\r\n static randomInt(min, max) {\r\n if (max < min) {\r\n let temp = min;\r\n min = max;\r\n max = temp;\r\n }\r\n\r\n let minCeil = Math.ceil(min);\r\n let maxFloor = Math.floor(max);\r\n\r\n if (Math.floor(min) == maxFloor) {\r\n return Math.floor((min + max) / 2 + Math.random());\r\n }\r\n\r\n let minTail = -1 * (min - minCeil);\r\n let maxTail = max - maxFloor;\r\n\r\n let balancedWeight = 2 * minTail + (maxFloor - minCeil);\r\n let balancedAverage = (maxFloor + minCeil) / 2;\r\n let average = (max + min) / 2;\r\n let extraTailWeight = (balancedWeight * (average - balancedAverage)) / (maxFloor + 1 - average);\r\n let extraTailChance = Math.abs(extraTailWeight / (extraTailWeight + balancedWeight));\r\n\r\n if (Math.random() < extraTailChance) {\r\n if (maxTail > minTail) {\r\n return Math.floor(maxFloor + 1);\r\n } else {\r\n return Math.floor(minCeil - 1);\r\n }\r\n }\r\n\r\n if (maxTail > minTail) {\r\n return Math.floor(min + Math.random() * (maxFloor + minTail - min + 1));\r\n } else {\r\n return Math.floor(minCeil - maxTail + Math.random() * (max - (minCeil - maxTail) + 1));\r\n }\r\n }\r\n\r\n static processAttack(source, target, abilityEffect = null) {\r\n let combatStyle = abilityEffect\r\n ? abilityEffect.combatStyleHrid\r\n : source.combatDetails.combatStats.combatStyleHrid;\r\n let damageType = abilityEffect ? abilityEffect.damageType : source.combatDetails.combatStats.damageType;\r\n\r\n let sourceAccuracyRating = 1;\r\n let sourceAutoAttackMaxDamage = 1;\r\n let targetEvasionRating = 1;\r\n\r\n switch (combatStyle) {\r\n case \"/combat_styles/stab\":\r\n sourceAccuracyRating = source.combatDetails.stabAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.stabMaxDamage;\r\n targetEvasionRating = target.combatDetails.stabEvasionRating;\r\n break;\r\n case \"/combat_styles/slash\":\r\n sourceAccuracyRating = source.combatDetails.slashAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.slashMaxDamage;\r\n targetEvasionRating = target.combatDetails.slashEvasionRating;\r\n break;\r\n case \"/combat_styles/smash\":\r\n sourceAccuracyRating = source.combatDetails.smashAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.smashMaxDamage;\r\n targetEvasionRating = target.combatDetails.smashEvasionRating;\r\n break;\r\n case \"/combat_styles/ranged\":\r\n sourceAccuracyRating = source.combatDetails.rangedAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.rangedMaxDamage;\r\n targetEvasionRating = target.combatDetails.rangedEvasionRating;\r\n break;\r\n case \"/combat_styles/magic\":\r\n sourceAccuracyRating = source.combatDetails.magicAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.magicMaxDamage;\r\n targetEvasionRating = target.combatDetails.magicEvasionRating;\r\n break;\r\n default:\r\n throw new Error(\"Unknown combat style: \" + combatStyle);\r\n }\r\n\r\n let sourceDamageMultiplier = 1;\r\n let sourceResistance = 0;\r\n let sourcePenetration = 0;\r\n let targetResistance = 0;\r\n let targetThornPower = 0;\r\n let targetPenetration = 0;\r\n let thornType;\r\n\r\n switch (damageType) {\r\n case \"/damage_types/physical\":\r\n sourceDamageMultiplier = 1 + source.combatDetails.combatStats.physicalAmplify;\r\n sourceResistance = source.combatDetails.totalArmor;\r\n sourcePenetration = source.combatDetails.combatStats.armorPenetration;\r\n targetResistance = target.combatDetails.totalArmor;\r\n targetThornPower = target.combatDetails.combatStats.physicalThorns;\r\n targetPenetration = target.combatDetails.combatStats.armorPenetration;\r\n thornType = \"physicalThorns\";\r\n break;\r\n case \"/damage_types/water\":\r\n sourceDamageMultiplier = 1 + source.combatDetails.combatStats.waterAmplify;\r\n sourceResistance = source.combatDetails.totalWaterResistance;\r\n sourcePenetration = source.combatDetails.combatStats.waterPenetration;\r\n targetResistance = target.combatDetails.totalWaterResistance;\r\n targetThornPower = target.combatDetails.combatStats.elementalThorns;\r\n targetPenetration = target.combatDetails.combatStats.waterPenetration;\r\n thornType = \"elementalThorns\";\r\n break;\r\n case \"/damage_types/nature\":\r\n sourceDamageMultiplier = 1 + source.combatDetails.combatStats.natureAmplify;\r\n sourceResistance = source.combatDetails.totalNatureResistance;\r\n sourcePenetration = source.combatDetails.combatStats.naturePenetration;\r\n targetResistance = target.combatDetails.totalNatureResistance;\r\n targetThornPower = target.combatDetails.combatStats.elementalThorns;\r\n targetPenetration = target.combatDetails.combatStats.naturePenetration;\r\n thornType = \"elementalThorns\";\r\n break;\r\n case \"/damage_types/fire\":\r\n sourceDamageMultiplier = 1 + source.combatDetails.combatStats.fireAmplify;\r\n sourceResistance = source.combatDetails.totalFireResistance;\r\n sourcePenetration = source.combatDetails.combatStats.firePenetration;\r\n targetResistance = target.combatDetails.totalFireResistance;\r\n targetThornPower = target.combatDetails.combatStats.elementalThorns;\r\n targetPenetration = target.combatDetails.combatStats.firePenetration;\r\n thornType = \"elementalThorns\";\r\n break;\r\n default:\r\n throw new Error(\"Unknown damage type: \" + damageType);\r\n }\r\n\r\n let hitChance = 1;\r\n let critChance = 0;\r\n let isCrit = false;\r\n let bonusCritChance = source.combatDetails.combatStats.criticalRate;\r\n let bonusCritDamage = source.combatDetails.combatStats.criticalDamage;\r\n\r\n if (abilityEffect) {\r\n sourceAccuracyRating *= (1 + abilityEffect.bonusAccuracyRatio);\r\n }\r\n\r\n if (source.isWeakened) {\r\n sourceAccuracyRating = sourceAccuracyRating - (source.weakenPercentage * sourceAccuracyRating);\r\n }\r\n\r\n hitChance =\r\n Math.pow(sourceAccuracyRating, 1.4) /\r\n (Math.pow(sourceAccuracyRating, 1.4) + Math.pow(targetEvasionRating, 1.4));\r\n\r\n if (combatStyle == \"/combat_styles/ranged\") {\r\n critChance = 0.3 * hitChance;\r\n }\r\n\r\n critChance = critChance + bonusCritChance;\r\n\r\n let baseDamageFlat = abilityEffect ? abilityEffect.damageFlat : 0;\r\n let baseDamageRatio = abilityEffect ? abilityEffect.damageRatio : 1;\r\n\r\n let armorDamageRatioFlat = abilityEffect ? abilityEffect.armorDamageRatio * source.combatDetails.totalArmor : 0;\r\n\r\n let sourceMinDamage = sourceDamageMultiplier * (1 + baseDamageFlat + armorDamageRatioFlat);\r\n let sourceMaxDamage = sourceDamageMultiplier * (baseDamageRatio * sourceAutoAttackMaxDamage + baseDamageFlat + armorDamageRatioFlat);\r\n\r\n if (Math.random() < critChance) {\r\n sourceMaxDamage = sourceMaxDamage * (1 + bonusCritDamage);\r\n sourceMinDamage = sourceMaxDamage;\r\n isCrit = true;\r\n }\r\n\r\n let damageRoll = CombatUtilities.randomInt(sourceMinDamage, sourceMaxDamage);\r\n damageRoll *= (1 + source.combatDetails.combatStats.taskDamage);\r\n damageRoll *= (1 + target.combatDetails.combatStats.damageTaken);\r\n if (!abilityEffect) {\r\n damageRoll += damageRoll * source.combatDetails.combatStats.autoAttackDamage;\r\n } else {\r\n damageRoll *= (1 + source.combatDetails.combatStats.abilityDamage);\r\n }\r\n\r\n let damageDone = 0;\r\n let thornDamageDone = 0;\r\n\r\n let didHit = false;\r\n if (Math.random() < hitChance) {\r\n didHit = true;\r\n let penetratedTargetResistance = targetResistance;\r\n\r\n if (sourcePenetration > 0 && targetResistance > 0) {\r\n penetratedTargetResistance = targetResistance / (1 + sourcePenetration);\r\n }\r\n\r\n let targetDamageTakenRatio = 100 / (100 + penetratedTargetResistance);\r\n if (penetratedTargetResistance < 0) {\r\n targetDamageTakenRatio = (100 - penetratedTargetResistance) / 100;\r\n }\r\n\r\n let mitigatedDamage = Math.ceil(targetDamageTakenRatio * damageRoll);\r\n damageDone = Math.min(mitigatedDamage, target.combatDetails.currentHitpoints);\r\n target.combatDetails.currentHitpoints -= damageDone;\r\n }\r\n\r\n if (targetThornPower > 0.0 && targetResistance > -99.0) {\r\n let penetratedSourceResistance = sourceResistance\r\n\r\n if (sourceResistance > 0) {\r\n penetratedSourceResistance = sourceResistance / (1 + targetPenetration);\r\n }\r\n\r\n let sourceDamageTakenRatio = 100.0 / (100 + penetratedSourceResistance);\r\n if (penetratedSourceResistance < 0) {\r\n sourceDamageTakenRatio = (100 - penetratedSourceResistance) / 100;\r\n }\r\n\r\n let targetTaskDamageMultiplier = 1.0 + target.combatDetails.combatStats.taskDamage;\r\n let sourceDamageTakenMultiplier = 1.0 + source.combatDetails.combatStats.damageTaken;\r\n let targetDamageMultiplier = targetTaskDamageMultiplier * sourceDamageTakenMultiplier;\r\n\r\n let thornsDamageRoll = CombatUtilities.randomInt(1,\r\n targetDamageMultiplier\r\n * target.combatDetails.defensiveMaxDamage\r\n * (1.0 + targetResistance / 100.0)\r\n * targetThornPower);\r\n\r\n let mitigatedThornsDamage = Math.ceil(sourceDamageTakenRatio * thornsDamageRoll);\r\n\r\n thornDamageDone = Math.min(mitigatedThornsDamage, source.combatDetails.currentHitpoints);\r\n source.combatDetails.currentHitpoints -= thornDamageDone;\r\n }\r\n\r\n let retaliationDamageDone = 0;\r\n if (target.combatDetails.combatStats.retaliation > 0) {\r\n let retaliationHitChance = \r\n Math.pow(target.combatDetails.smashAccuracyRating, 1.4) /\r\n (Math.pow(target.combatDetails.smashAccuracyRating, 1.4) + Math.pow(source.combatDetails.smashEvasionRating, 1.4));\r\n\r\n if (retaliationHitChance > Math.random()) {\r\n let sourceEffectiveArmor = source.combatDetails.totalArmor;\r\n if (sourceEffectiveArmor > 0) {\r\n sourceEffectiveArmor = sourceEffectiveArmor / (1.0 + target.combatDetails.combatStats.armorPenetration);\r\n }\r\n\r\n let sourceDamageTakenRatio = 100.0 / (100.0 + sourceEffectiveArmor);\r\n if (sourceEffectiveArmor < 0) {\r\n sourceDamageTakenRatio = (100.0 - sourceEffectiveArmor) / 100.0;\r\n }\r\n\r\n let targetTaskDamageMultiplier = 1.0 + target.combatDetails.combatStats.taskDamage;\r\n let sourceDamageTakenMultiplier = 1.0 + source.combatDetails.combatStats.damageTaken;\r\n let retaliationDamageMultiplier = targetTaskDamageMultiplier * sourceDamageTakenMultiplier;\r\n\r\n let premitigatedDamage = damageRoll;\r\n premitigatedDamage = Math.min(premitigatedDamage, target.combatDetails.defensiveMaxDamage * 5);\r\n\r\n let retaliationMinDamage = retaliationDamageMultiplier * target.combatDetails.combatStats.retaliation * premitigatedDamage;\r\n let retaliationMaxDamage = retaliationDamageMultiplier * target.combatDetails.combatStats.retaliation * (target.combatDetails.defensiveMaxDamage + premitigatedDamage);\r\n\r\n let retaliationDamageRoll = CombatUtilities.randomInt(retaliationMinDamage, retaliationMaxDamage);\r\n let mitigatedRetaliationDamage = Math.ceil(sourceDamageTakenRatio * retaliationDamageRoll);\r\n retaliationDamageDone = Math.min(mitigatedRetaliationDamage, source.combatDetails.currentHitpoints);\r\n source.combatDetails.currentHitpoints -= retaliationDamageDone;\r\n }\r\n }\r\n\r\n let lifeStealHeal = 0;\r\n if (!abilityEffect && didHit && source.combatDetails.combatStats.lifeSteal > 0) {\r\n lifeStealHeal = source.addHitpoints(Math.floor(source.combatDetails.combatStats.lifeSteal * damageDone));\r\n }\r\n\r\n let hpDrain = 0;\r\n if (abilityEffect && didHit && abilityEffect.hpDrainRatio > 0) {\r\n let healingAmplify = 1 + source.combatDetails.combatStats.healingAmplify;\r\n hpDrain = source.addHitpoints(Math.floor(abilityEffect.hpDrainRatio * damageDone * healingAmplify));\r\n }\r\n\r\n let manaLeechMana = 0;\r\n if (!abilityEffect && didHit && source.combatDetails.combatStats.manaLeech > 0) {\r\n manaLeechMana = source.addManapoints(Math.floor(source.combatDetails.combatStats.manaLeech * damageDone));\r\n }\r\n\r\n return { damageDone, didHit, thornDamageDone, thornType, retaliationDamageDone, lifeStealHeal, hpDrain, manaLeechMana, isCrit};\r\n }\r\n\r\n static processHeal(source, abilityEffect, target) {\r\n if (abilityEffect.combatStyleHrid != \"/combat_styles/magic\") {\r\n throw new Error(\"Heal ability effect not supported for combat style: \" + abilityEffect.combatStyleHrid);\r\n }\r\n\r\n let healingAmplify = 1 + source.combatDetails.combatStats.healingAmplify;\r\n let magicMaxDamage = source.combatDetails.magicMaxDamage;\r\n\r\n let baseHealFlat = abilityEffect.damageFlat;\r\n let baseHealRatio = abilityEffect.damageRatio;\r\n\r\n let minHeal = healingAmplify * (1 + baseHealFlat);\r\n let maxHeal = healingAmplify * (baseHealRatio * magicMaxDamage + baseHealFlat);\r\n\r\n let heal = this.randomInt(minHeal, maxHeal);\r\n let amountHealed = target.addHitpoints(heal);\r\n\r\n return amountHealed;\r\n }\r\n\r\n static processRevive(source, abilityEffect, target) {\r\n if (abilityEffect.combatStyleHrid != \"/combat_styles/magic\") {\r\n throw new Error(\"Heal ability effect not supported for combat style: \" + abilityEffect.combatStyleHrid);\r\n }\r\n\r\n let healingAmplify = 1 + source.combatDetails.combatStats.healingAmplify;\r\n let magicMaxDamage = source.combatDetails.magicMaxDamage;\r\n\r\n let baseHealFlat = abilityEffect.damageFlat;\r\n let baseHealRatio = abilityEffect.damageRatio;\r\n\r\n let minHeal = healingAmplify * (1 + baseHealFlat);\r\n let maxHeal = healingAmplify * (baseHealRatio * magicMaxDamage + baseHealFlat);\r\n\r\n let heal = this.randomInt(minHeal, maxHeal);\r\n let amountHealed = target.addHitpoints(heal);\r\n target.combatDetails.currentManapoints = target.combatDetails.maxManapoints;\r\n target.clearCCs();\r\n\r\n // target.clearBuffs();\r\n\r\n return amountHealed;\r\n }\r\n\r\n static processSpendHp(source, abilityEffect) {\r\n let currentHp = source.combatDetails.currentHitpoints;\r\n let spendHpRatio = abilityEffect.spendHpRatio;\r\n\r\n let spentHp = Math.floor(currentHp * spendHpRatio);\r\n\r\n source.combatDetails.currentHitpoints -= spentHp;\r\n\r\n return spentHp;\r\n }\r\n\r\n static calculateTickValue(totalValue, totalTicks, currentTick) {\r\n let currentSum = Math.floor((currentTick * totalValue) / totalTicks);\r\n let previousSum = Math.floor(((currentTick - 1) * totalValue) / totalTicks);\r\n\r\n return currentSum - previousSum;\r\n }\r\n}\r\n\r\nexport default CombatUtilities;\r\n","import Buff from \"./buff\";\r\nimport itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nclass Consumable {\r\n constructor(hrid, triggers = null) {\r\n this.hrid = hrid;\r\n\r\n let gameConsumable = itemDetailMap[this.hrid];\r\n if (!gameConsumable) {\r\n throw new Error(\"No consumable found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.cooldownDuration = gameConsumable.consumableDetail.cooldownDuration;\r\n this.hitpointRestore = gameConsumable.consumableDetail.hitpointRestore;\r\n this.manapointRestore = gameConsumable.consumableDetail.manapointRestore;\r\n this.recoveryDuration = gameConsumable.consumableDetail.recoveryDuration;\r\n this.catagoryHrid = gameConsumable.categoryHrid;\r\n\r\n this.buffs = [];\r\n if (gameConsumable.consumableDetail.buffs) {\r\n for (const consumableBuff of gameConsumable.consumableDetail.buffs) {\r\n let buff = new Buff(consumableBuff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameConsumable.consumableDetail.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let consumable = new Consumable(dto.hrid, triggers);\r\n\r\n return consumable;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n let consumableHaste;\r\n if (this.catagoryHrid.includes(\"food\")) {\r\n consumableHaste = source.combatDetails.combatStats.foodHaste\r\n } else {\r\n consumableHaste = source.combatDetails.combatStats.drinkConcentration;\r\n }\r\n let cooldownDuration = this.cooldownDuration;\r\n if (consumableHaste > 0) {\r\n cooldownDuration = cooldownDuration / (1 + consumableHaste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Consumable;\r\n","class Drops {\r\n\r\n constructor(itemHrid, dropRate, minCount, maxCount, difficultyTier) {\r\n this.itemHrid = itemHrid;\r\n this.dropRate = dropRate;\r\n this.minCount = minCount;\r\n this.maxCount = maxCount;\r\n this.difficultyTier = difficultyTier;\r\n }\r\n}\r\n\r\nexport default Drops;\r\n","import itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport enhancementLevelTotalMultiplierTable from \"./data/enhancementLevelTotalBonusMultiplierTable.json\";\r\n\r\nclass Equipment {\r\n constructor(hrid, enhancementLevel) {\r\n this.hrid = hrid;\r\n let gameItem = itemDetailMap[this.hrid];\r\n if (!gameItem) {\r\n throw new Error(\"No equipment found for hrid: \" + this.hrid);\r\n }\r\n this.gameItem = gameItem;\r\n this.enhancementLevel = enhancementLevel;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let equipment = new Equipment(dto.hrid, dto.enhancementLevel);\r\n\r\n return equipment;\r\n }\r\n\r\n getCombatStat(combatStat) {\r\n let multiplier = enhancementLevelTotalMultiplierTable[this.enhancementLevel];\r\n if(this.gameItem.equipmentDetail.combatStats[combatStat]) {\r\n let enhancementBonus = this.gameItem.equipmentDetail.combatEnhancementBonuses[combatStat] || 0;\r\n let stat = this.gameItem.equipmentDetail.combatStats[combatStat] + multiplier * enhancementBonus;\r\n return stat;\r\n }\r\n return 0;\r\n }\r\n\r\n getCombatStyle() {\r\n return this.gameItem.equipmentDetail.combatStats.combatStyleHrids[0];\r\n }\r\n\r\n getDamageType() {\r\n return this.gameItem.equipmentDetail.combatStats.damageType;\r\n }\r\n\r\n getPrimaryTraining() {\r\n return this.gameItem.equipmentDetail.combatStats.primaryTraining;\r\n }\r\n\r\n getFocusTraining(){\r\n return this.gameItem.equipmentDetail.combatStats.focusTraining;\r\n }\r\n}\r\n\r\nexport default Equipment;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass AbilityCastEndEvent extends CombatEvent {\r\n static type = \"abilityCastEndEvent\";\r\n\r\n constructor(time, source, ability) {\r\n super(AbilityCastEndEvent.type, time);\r\n\r\n this.source = source;\r\n this.ability = ability;\r\n }\r\n}\r\n\r\nexport default AbilityCastEndEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass AutoAttackEvent extends CombatEvent {\r\n static type = \"autoAttack\";\r\n\r\n constructor(time, source) {\r\n super(AutoAttackEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default AutoAttackEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass AwaitCooldownEvent extends CombatEvent {\r\n static type = \"awaitCooldownEvent\";\r\n\r\n constructor(time, source) {\r\n super(AwaitCooldownEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default AwaitCooldownEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass BlindExpirationEvent extends CombatEvent {\r\n static type = \"blindExpiration\";\r\n\r\n constructor(time, source) {\r\n super(BlindExpirationEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default BlindExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass CheckBuffExpirationEvent extends CombatEvent {\r\n static type = \"checkBuffExpiration\";\r\n\r\n constructor(time, source) {\r\n super(CheckBuffExpirationEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default CheckBuffExpirationEvent;\r\n","class CombatEvent {\r\n constructor(type, time) {\r\n this.type = type;\r\n this.time = time;\r\n }\r\n}\r\n\r\nexport default CombatEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass CombatStartEvent extends CombatEvent {\r\n static type = \"combatStart\";\r\n\r\n constructor(time) {\r\n super(CombatStartEvent.type, time);\r\n }\r\n}\r\n\r\nexport default CombatStartEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass ConsumableTickEvent extends CombatEvent {\r\n static type = \"consumableTick\";\r\n\r\n constructor(time, source, consumable, totalTicks, currentTick) {\r\n super(ConsumableTickEvent.type, time);\r\n\r\n this.source = source;\r\n this.consumable = consumable;\r\n this.totalTicks = totalTicks;\r\n this.currentTick = currentTick;\r\n }\r\n}\r\n\r\nexport default ConsumableTickEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass CooldownReadyEvent extends CombatEvent {\r\n static type = \"cooldownReady\";\r\n\r\n constructor(time) {\r\n super(CooldownReadyEvent.type, time);\r\n }\r\n}\r\n\r\nexport default CooldownReadyEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass CurseExpirationEvent extends CombatEvent {\r\n static type = \"curseExpiration\";\r\n static maxCurseStacks = 5;\r\n\r\n constructor(time, curseAmount, source) {\r\n super(CurseExpirationEvent.type, time);\r\n\r\n this.curseAmount = Math.min(curseAmount + 1, CurseExpirationEvent.maxCurseStacks);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default CurseExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass DamageOverTimeEvent extends CombatEvent {\r\n static type = \"damageOverTime\";\r\n\r\n constructor(time, sourceRef, target, damage, totalTicks, currentTick, combatStyleHrid) {\r\n super(DamageOverTimeEvent.type, time);\r\n\r\n // Calling it 'source' would wrongly clear Damage Over Time when the source dies\r\n this.sourceRef = sourceRef;\r\n this.target = target;\r\n this.damage = damage;\r\n this.totalTicks = totalTicks;\r\n this.currentTick = currentTick;\r\n this.combatStyleHrid = combatStyleHrid;\r\n }\r\n}\r\n\r\nexport default DamageOverTimeEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass EnemyRespawnEvent extends CombatEvent {\r\n static type = \"enemyRespawn\";\r\n\r\n constructor(time) {\r\n super(EnemyRespawnEvent.type, time);\r\n }\r\n}\r\n\r\nexport default EnemyRespawnEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass EnrageTickEvent extends CombatEvent {\r\n static type = \"enrageTick\";\r\n\r\n constructor(time, encounterTime) {\r\n\r\n super(EnrageTickEvent.type, time);\r\n\r\n this.encounterTime = encounterTime;\r\n }\r\n}\r\n\r\nexport default EnrageTickEvent;\r\n","import Heap from \"heap-js\";\r\n\r\nclass EventQueue {\r\n constructor() {\r\n this.minHeap = new Heap((a, b) => a.time - b.time);\r\n }\r\n\r\n addEvent(event) {\r\n this.minHeap.push(event);\r\n }\r\n\r\n getNextEvent() {\r\n return this.minHeap.pop();\r\n }\r\n\r\n containsEventOfType(type) {\r\n let heapEvents = this.minHeap.toArray();\r\n\r\n return heapEvents.some((event) => event.type == type);\r\n }\r\n\r\n containsEventOfTypeAndHrid(type, hrid) {\r\n let heapEvents = this.minHeap.toArray();\r\n return heapEvents.some((event) => event.type == type && event.hrid == hrid);\r\n }\r\n\r\n clear() {\r\n this.minHeap = new Heap((a, b) => a.time - b.time);\r\n }\r\n\r\n clearEventsForUnit(unit) {\r\n this.clearMatching((event) => event.source == unit || event.target == unit);\r\n }\r\n\r\n clearEventsOfType(type) {\r\n this.clearMatching((event) => event.type == type);\r\n }\r\n\r\n clearMatching(fn) {\r\n let cleared = false;\r\n let heapEvents = this.minHeap.toArray();\r\n\r\n for (const event of heapEvents) {\r\n if (fn(event)) {\r\n this.minHeap.remove(event);\r\n cleared = true;\r\n }\r\n }\r\n return cleared;\r\n }\r\n\r\n getMatching(fn) {\r\n let heapEvents = this.minHeap.toArray(); \r\n \r\n for (const event of heapEvents) {\r\n if (fn(event)) {\r\n return event; \r\n }\r\n }\r\n \r\n return null; \r\n }\r\n}\r\n\r\nexport default EventQueue;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass FuryExpirationEvent extends CombatEvent {\r\n static type = \"furyExpiration\";\r\n\r\n constructor(time, furyAmount, source) {\r\n super(FuryExpirationEvent.type, time);\r\n \r\n this.furyAmount = furyAmount;\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default FuryExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass PlayerRespawnEvent extends CombatEvent {\r\n static type = \"playerRespawn\";\r\n\r\n constructor(time, hrid) {\r\n super(PlayerRespawnEvent.type, time);\r\n this.hrid = hrid;\r\n }\r\n}\r\n\r\nexport default PlayerRespawnEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass RegenTickEvent extends CombatEvent {\r\n static type = \"regenTick\";\r\n\r\n constructor(time) {\r\n super(RegenTickEvent.type, time);\r\n }\r\n}\r\n\r\nexport default RegenTickEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass SilenceExpirationEvent extends CombatEvent {\r\n static type = \"silenceExpiration\";\r\n\r\n constructor(time, source) {\r\n super(SilenceExpirationEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default SilenceExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass StunExpirationEvent extends CombatEvent {\r\n static type = \"stunExpiration\";\r\n\r\n constructor(time, source) {\r\n super(StunExpirationEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default StunExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass WeakenExpirationEvent extends CombatEvent {\r\n static type = \"weakenExpiration\";\r\n static maxWeakenStacks = 5;\r\n\r\n constructor(time, weakenAmount, source) {\r\n super(WeakenExpirationEvent.type, time);\r\n this.weakenAmount = Math.min(\r\n weakenAmount + 1,\r\n WeakenExpirationEvent.maxWeakenStacks\r\n );\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default WeakenExpirationEvent;","import Buff from \"./buff\";\r\nimport houseRoomDetailMap from \"./data/houseRoomDetailMap.json\";\r\n\r\nclass HouseRoom {\r\n constructor(hrid, level) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameHouseRoom = houseRoomDetailMap[this.hrid];\r\n if (!gameHouseRoom) {\r\n throw new Error(\"No house room found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.buffs = [];\r\n if (gameHouseRoom.actionBuffs) {\r\n for (const actionBuff of gameHouseRoom.actionBuffs) {\r\n let buff = new Buff(actionBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n if (gameHouseRoom.globalBuffs) {\r\n for (const globalBuff of gameHouseRoom.globalBuffs) {\r\n let buff = new Buff(globalBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default HouseRoom;","import Ability from \"./ability\";\r\nimport CombatUnit from \"./combatUnit\";\r\nimport combatMonsterDetailMap from \"./data/combatMonsterDetailMap.json\";\r\nimport Drops from \"./drops\";\r\n\r\nclass Monster extends CombatUnit {\r\n\r\n difficultyTier = 0;\r\n\r\n constructor(hrid, difficultyTier = 0) {\r\n super();\r\n\r\n this.isPlayer = false;\r\n this.hrid = hrid;\r\n this.difficultyTier = difficultyTier;\r\n\r\n let gameMonster = combatMonsterDetailMap[this.hrid];\r\n if (!gameMonster) {\r\n throw new Error(\"No monster found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.enrageTime = gameMonster.enrageTime;\r\n\r\n for (let i = 0; i < gameMonster.abilities.length; i++) {\r\n if (gameMonster.abilities[i].minDifficultyTier > this.difficultyTier) {\r\n continue;\r\n }\r\n this.abilities[i] = new Ability(gameMonster.abilities[i].abilityHrid, gameMonster.abilities[i].level);\r\n }\r\n if(gameMonster.dropTable)\r\n for (let i = 0; i < gameMonster.dropTable.length; i++) {\r\n this.dropTable[i] = new Drops(gameMonster.dropTable[i].itemHrid, gameMonster.dropTable[i].dropRate, gameMonster.dropTable[i].minCount, gameMonster.dropTable[i].maxCount, gameMonster.dropTable[i].difficultyTier);\r\n }\r\n for (let i = 0; i < gameMonster.rareDropTable.length; i++) {\r\n let dropTableItem = (gameMonster.dropTable && i < gameMonster.dropTable.length) ? gameMonster.dropTable[i] : null;\r\n let difficultyTier = dropTableItem?.difficultyTier ?? gameMonster.rareDropTable[i].minDifficultyTier;\r\n\r\n this.rareDropTable[i] = new Drops(gameMonster.rareDropTable[i].itemHrid, gameMonster.rareDropTable[i].dropRate, gameMonster.rareDropTable[i].minCount, difficultyTier);\r\n }\r\n }\r\n\r\n updateCombatDetails() {\r\n let gameMonster = combatMonsterDetailMap[this.hrid];\r\n\r\n let levelMultiplier = 1.0 + 0.25 * this.difficultyTier;\r\n let defLevelMultiplier = 1.0 + 0.15 * this.difficultyTier;\r\n let levelBonus = 20.0 * this.difficultyTier;\r\n\r\n this.staminaLevel = levelMultiplier * (gameMonster.combatDetails.staminaLevel + levelBonus);\r\n this.intelligenceLevel = levelMultiplier * (gameMonster.combatDetails.intelligenceLevel + levelBonus);\r\n this.attackLevel = levelMultiplier * (gameMonster.combatDetails.attackLevel + levelBonus);\r\n this.meleeLevel = levelMultiplier * (gameMonster.combatDetails.meleeLevel + levelBonus);\r\n this.defenseLevel = defLevelMultiplier * (gameMonster.combatDetails.defenseLevel + levelBonus);\r\n this.rangedLevel = levelMultiplier * (gameMonster.combatDetails.rangedLevel + levelBonus);\r\n this.magicLevel = levelMultiplier * (gameMonster.combatDetails.magicLevel + levelBonus);\r\n\r\n \r\n let expMultiplier = 1.0 + 0.5 * this.difficultyTier;\r\n let expBonus = 5.0 * this.difficultyTier;\r\n\r\n this.experience = expMultiplier * (gameMonster.experience + expBonus);\r\n\r\n this.combatDetails.combatStats.combatStyleHrid = gameMonster.combatDetails.combatStats.combatStyleHrids[0];\r\n\r\n for (const [key, value] of Object.entries(gameMonster.combatDetails.combatStats)) {\r\n this.combatDetails.combatStats[key] = value;\r\n }\r\n\r\n [\r\n \"stabAccuracy\",\r\n \"slashAccuracy\",\r\n \"smashAccuracy\",\r\n \"rangedAccuracy\",\r\n \"magicAccuracy\",\r\n \"stabDamage\",\r\n \"slashDamage\",\r\n \"smashDamage\",\r\n \"rangedDamage\",\r\n \"magicDamage\",\r\n \"defensiveDamage\",\r\n \"taskDamage\",\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"stabEvasion\",\r\n \"slashEvasion\",\r\n \"smashEvasion\",\r\n \"rangedEvasion\",\r\n \"magicEvasion\",\r\n \"armor\",\r\n \"waterResistance\",\r\n \"natureResistance\",\r\n \"fireResistance\",\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"combatDropRate\",\r\n \"combatRareFind\",\r\n \"combatDropQuantity\",\r\n \"combatExperience\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"abilityHaste\",\r\n \"tenacity\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"threat\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"foodHaste\",\r\n \"drinkConcentration\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"retaliation\"\r\n ].forEach((stat) => {\r\n if (gameMonster.combatDetails.combatStats[stat] == null) {\r\n this.combatDetails.combatStats[stat] = 0;\r\n }\r\n });\r\n\r\n if (this.combatDetails.combatStats.attackInterval == 0) {\r\n this.combatDetails.combatStats.attackInterval = gameMonster.combatDetails.attackInterval;\r\n }\r\n\r\n super.updateCombatDetails();\r\n }\r\n}\r\n\r\nexport default Monster;\r\n","import Ability from \"./ability\";\r\nimport CombatUnit from \"./combatUnit\";\r\nimport Consumable from \"./consumable\";\r\nimport Equipment from \"./equipment\";\r\nimport HouseRoom from \"./houseRoom\";\r\nimport Achievement from \"./achievement\";\r\n\r\nclass Player extends CombatUnit {\r\n equipment = {\r\n \"/equipment_types/head\": null,\r\n \"/equipment_types/body\": null,\r\n \"/equipment_types/legs\": null,\r\n \"/equipment_types/feet\": null,\r\n \"/equipment_types/hands\": null,\r\n \"/equipment_types/main_hand\": null,\r\n \"/equipment_types/two_hand\": null,\r\n \"/equipment_types/off_hand\": null,\r\n \"/equipment_types/pouch\": null,\r\n \"/equipment_types/back\": null,\r\n };\r\n\r\n constructor() {\r\n super();\r\n\r\n this.isPlayer = true;\r\n this.hrid = \"player\";\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let player = new Player();\r\n\r\n player.staminaLevel = dto.staminaLevel;\r\n player.intelligenceLevel = dto.intelligenceLevel;\r\n player.attackLevel = dto.attackLevel;\r\n player.meleeLevel = dto.meleeLevel;\r\n player.defenseLevel = dto.defenseLevel;\r\n player.rangedLevel = dto.rangedLevel;\r\n player.magicLevel = dto.magicLevel;\r\n\r\n player.hrid = dto.hrid;\r\n\r\n for (const [key, value] of Object.entries(dto.equipment)) {\r\n player.equipment[key] = value ? Equipment.createFromDTO(value) : null;\r\n }\r\n\r\n player.food = dto.food.map((food) => (food ? Consumable.createFromDTO(food) : null));\r\n player.drinks = dto.drinks.map((drink) => (drink ? Consumable.createFromDTO(drink) : null));\r\n player.abilities = dto.abilities.map((ability) => (ability ? Ability.createFromDTO(ability) : null));\r\n Object.entries(dto.houseRooms).forEach(houseRoom => {\r\n if (houseRoom[1] > 0) {\r\n player.houseRooms.push(new HouseRoom(houseRoom[0], houseRoom[1]))\r\n }\r\n });\r\n\r\n player.achievements = new Achievement(dto.achievements);\r\n\r\n player.debuffOnLevelGap = dto.debuffOnLevelGap;\r\n\r\n return player;\r\n }\r\n\r\n updateCombatDetails() {\r\n if (this.equipment[\"/equipment_types/main_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/main_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/main_hand\"].getPrimaryTraining();\r\n } else if (this.equipment[\"/equipment_types/two_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/two_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/two_hand\"].getPrimaryTraining();\r\n } else {\r\n this.combatDetails.combatStats.combatStyleHrid = \"/combat_styles/smash\";\r\n this.combatDetails.combatStats.damageType = \"/damage_types/physical\";\r\n this.combatDetails.combatStats.attackInterval = 3000000000;\r\n this.combatDetails.combatStats.primaryTraining = \"/skills/melee\";\r\n }\r\n\r\n if (this.equipment[\"/equipment_types/charm\"]) {\r\n this.combatDetails.combatStats.focusTraining = this.equipment[\"/equipment_types/charm\"].getFocusTraining();\r\n } else {\r\n this.combatDetails.combatStats.focusTraining = \"\";\r\n }\r\n\r\n [\r\n \"stabAccuracy\",\r\n \"slashAccuracy\",\r\n \"smashAccuracy\",\r\n \"rangedAccuracy\",\r\n \"magicAccuracy\",\r\n \"stabDamage\",\r\n \"slashDamage\",\r\n \"smashDamage\",\r\n \"rangedDamage\",\r\n \"magicDamage\",\r\n \"defensiveDamage\",\r\n \"taskDamage\",\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"stabEvasion\",\r\n \"slashEvasion\",\r\n \"smashEvasion\",\r\n \"rangedEvasion\",\r\n \"magicEvasion\",\r\n \"armor\",\r\n \"waterResistance\",\r\n \"natureResistance\",\r\n \"fireResistance\",\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"combatDropRate\",\r\n \"combatRareFind\",\r\n \"combatDropQuantity\",\r\n \"combatExperience\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"abilityHaste\",\r\n \"tenacity\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"threat\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"foodHaste\",\r\n \"drinkConcentration\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\",\r\n \"retaliation\"\r\n ].forEach((stat) => {\r\n this.combatDetails.combatStats[stat] = Object.values(this.equipment)\r\n .filter((equipment) => equipment != null)\r\n .map((equipment) => equipment.getCombatStat(stat))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n });\r\n\r\n if (this.equipment[\"/equipment_types/pouch\"]) {\r\n this.combatDetails.combatStats.foodSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"foodSlots\");\r\n this.combatDetails.combatStats.drinkSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"drinkSlots\");\r\n } else {\r\n this.combatDetails.combatStats.foodSlots = 1;\r\n this.combatDetails.combatStats.drinkSlots = 1;\r\n }\r\n\r\n super.updateCombatDetails();\r\n }\r\n}\r\n\r\nexport default Player;\r\n","import combatStyleDetailMap from \"./data/combatStyleDetailMap.json\"\r\n\r\nclass SimResult {\r\n constructor(zone, numberOfPlayers) {\r\n this.deaths = {};\r\n this.experienceGained = {};\r\n this.encounters = 0;\r\n this.attacks = {};\r\n this.consumablesUsed = {};\r\n this.hitpointsGained = {};\r\n this.manapointsGained = {};\r\n this.debuffOnLevelGap = {};\r\n this.dropRateMultiplier = {};\r\n this.rareFindMultiplier = {};\r\n this.combatDropQuantity = {};\r\n this.playerRanOutOfMana = {\r\n \"player1\": false,\r\n \"player2\": false,\r\n \"player3\": false,\r\n \"player4\": false,\r\n \"player5\": false\r\n };\r\n this.playerRanOutOfManaTime = {};\r\n this.manaUsed = {};\r\n this.timeSpentAlive = [];\r\n this.bossSpawns = [];\r\n this.hitpointsSpent = {};\r\n this.zoneName = zone.hrid;\r\n this.difficultyTier = zone.difficultyTier;\r\n this.isDungeon = false;\r\n this.dungeonsCompleted = 0;\r\n this.dungeonsFailed = 0;\r\n this.maxWaveReached = 0;\r\n this.numberOfPlayers = numberOfPlayers;\r\n this.maxEnrageStack = 0;\r\n\r\n this.wipeEvents = [];\r\n }\r\n\r\n addWipeEvent(logs, simulationTime, wave) {\r\n this.wipeEvents.push({\r\n simulationTime: simulationTime,\r\n logs: logs,\r\n wave: wave,\r\n timestamp: new Date().toISOString()\r\n });\r\n }\r\n \r\n addDeath(unit) {\r\n if (!this.deaths[unit.hrid]) {\r\n this.deaths[unit.hrid] = 0;\r\n }\r\n\r\n this.deaths[unit.hrid] += 1;\r\n }\r\n\r\n updateTimeSpentAlive(name, alive, time) {\r\n const i = this.timeSpentAlive.findIndex(e => e.name === name);\r\n if (alive) {\r\n if (i !== -1) {\r\n this.timeSpentAlive[i].alive = true;\r\n this.timeSpentAlive[i].spawnedAt = time;\r\n } else {\r\n this.timeSpentAlive.push({ name: name, timeSpentAlive: 0, spawnedAt: time, alive: true, count: 0 });\r\n }\r\n } else {\r\n const timeAlive = time - this.timeSpentAlive[i].spawnedAt;\r\n this.timeSpentAlive[i].alive = false;\r\n this.timeSpentAlive[i].timeSpentAlive += timeAlive;\r\n this.timeSpentAlive[i].count += 1;\r\n }\r\n }\r\n\r\n addExperienceGain(unit, experience) {\r\n if (!unit.isPlayer) {\r\n return;\r\n }\r\n\r\n if (!this.experienceGained[unit.hrid]) {\r\n this.experienceGained[unit.hrid] = {\r\n stamina: 0,\r\n intelligence: 0,\r\n attack: 0,\r\n melee: 0,\r\n defense: 0,\r\n ranged: 0,\r\n magic: 0,\r\n };\r\n }\r\n\r\n let experienceGainedRate = {\r\n \"stamina\": 0,\r\n \"intelligence\": 0,\r\n \"attack\": 0,\r\n \"melee\": 0,\r\n \"defense\": 0,\r\n \"ranged\": 0,\r\n \"magic\": 0,\r\n };\r\n\r\n const primaryTraining = unit.combatDetails.combatStats.primaryTraining;\r\n experienceGainedRate[primaryTraining.split(\"/\")[2]] = .3;\r\n\r\n const skillExpMap = combatStyleDetailMap[unit.combatDetails.combatStats.combatStyleHrid].skillExpMap;\r\n const skillExpMapLength = Object.keys(skillExpMap).length;\r\n\r\n const focusTraining = unit.combatDetails.combatStats.focusTraining;\r\n if (focusTraining && skillExpMap[focusTraining]) {\r\n experienceGainedRate[focusTraining.split(\"/\")[2]] += .7;\r\n } else {\r\n Object.keys(skillExpMap).forEach(skillHrid => {\r\n experienceGainedRate[skillHrid.split(\"/\")[2]] += .7 / skillExpMapLength;\r\n });\r\n }\r\n\r\n for (const [type, rate] of Object.entries(experienceGainedRate)) {\r\n if (rate <= 0) continue;\r\n\r\n const skillExperience = rate * (1 + unit.combatDetails.combatStats[type + \"Experience\"]);\r\n\r\n this.experienceGained[unit.hrid][type] += (\r\n experience\r\n * (1 + unit.combatDetails.combatStats.combatExperience)\r\n * skillExperience\r\n * (1 + unit.debuffOnLevelGap)\r\n\r\n );\r\n }\r\n }\r\n\r\n addEncounterEnd() {\r\n this.encounters++;\r\n }\r\n\r\n addAttack(source, target, ability, hit) {\r\n if (!this.attacks[source.hrid]) {\r\n this.attacks[source.hrid] = {};\r\n }\r\n if (!this.attacks[source.hrid][target.hrid]) {\r\n this.attacks[source.hrid][target.hrid] = {};\r\n }\r\n if (!this.attacks[source.hrid][target.hrid][ability]) {\r\n this.attacks[source.hrid][target.hrid][ability] = {};\r\n }\r\n\r\n if (!this.attacks[source.hrid][target.hrid][ability][hit]) {\r\n this.attacks[source.hrid][target.hrid][ability][hit] = 0;\r\n }\r\n\r\n this.attacks[source.hrid][target.hrid][ability][hit] += 1;\r\n }\r\n\r\n addConsumableUse(unit, consumable) {\r\n if (!this.consumablesUsed[unit.hrid]) {\r\n this.consumablesUsed[unit.hrid] = {};\r\n }\r\n if (!this.consumablesUsed[unit.hrid][consumable.hrid]) {\r\n this.consumablesUsed[unit.hrid][consumable.hrid] = 0;\r\n }\r\n\r\n this.consumablesUsed[unit.hrid][consumable.hrid] += 1;\r\n }\r\n\r\n addHitpointsGained(unit, source, amount) {\r\n if (!this.hitpointsGained[unit.hrid]) {\r\n this.hitpointsGained[unit.hrid] = {};\r\n }\r\n if (!this.hitpointsGained[unit.hrid][source]) {\r\n this.hitpointsGained[unit.hrid][source] = 0;\r\n }\r\n\r\n this.hitpointsGained[unit.hrid][source] += amount;\r\n }\r\n\r\n addManapointsGained(unit, source, amount) {\r\n if (!this.manapointsGained[unit.hrid]) {\r\n this.manapointsGained[unit.hrid] = {};\r\n }\r\n if (!this.manapointsGained[unit.hrid][source]) {\r\n this.manapointsGained[unit.hrid][source] = 0;\r\n }\r\n\r\n this.manapointsGained[unit.hrid][source] += amount;\r\n }\r\n\r\n setDropRateMultipliers(unit) {\r\n if (!this.dropRateMultiplier[unit.hrid]) {\r\n this.dropRateMultiplier[unit.hrid] = {};\r\n }\r\n this.dropRateMultiplier[unit.hrid] = 1 + unit.combatDetails.combatStats.combatDropRate;\r\n\r\n if (!this.rareFindMultiplier[unit.hrid]) {\r\n this.rareFindMultiplier[unit.hrid] = {};\r\n }\r\n this.rareFindMultiplier[unit.hrid] = 1 + unit.combatDetails.combatStats.combatRareFind;\r\n\r\n if (!this.combatDropQuantity[unit.hrid]) {\r\n this.combatDropQuantity[unit.hrid] = {};\r\n }\r\n this.combatDropQuantity[unit.hrid] = unit.combatDetails.combatStats.combatDropQuantity;\r\n\r\n if (!this.debuffOnLevelGap[unit.hrid]) {\r\n this.debuffOnLevelGap[unit.hrid] = {};\r\n }\r\n this.debuffOnLevelGap[unit.hrid] = unit.debuffOnLevelGap;\r\n }\r\n\r\n setManaUsed(unit) {\r\n this.manaUsed[unit.hrid] = {};\r\n for (let [key, value] of unit.abilityManaCosts.entries()) {\r\n this.manaUsed[unit.hrid][key] = value;\r\n }\r\n }\r\n\r\n addHitpointsSpent(unit, source, amount) {\r\n if (!this.hitpointsSpent[unit.hrid]) {\r\n this.hitpointsSpent[unit.hrid] = {};\r\n }\r\n if (!this.hitpointsSpent[unit.hrid][source]) {\r\n this.hitpointsSpent[unit.hrid][source] = 0;\r\n }\r\n\r\n this.hitpointsSpent[unit.hrid][source] += amount;\r\n }\r\n\r\n addRanOutOfManaCount(unit, isOutOfMana, time) {\r\n if (isOutOfMana) this.playerRanOutOfMana[unit.hrid] = true;\r\n\r\n if (!this.playerRanOutOfManaTime[unit.hrid]) {\r\n this.playerRanOutOfManaTime[unit.hrid] = {isOutOfMana: false, startTimeForOutOfMana:0, totalTimeForOutOfMana:0};\r\n }\r\n\r\n if (isOutOfMana) {\r\n if (!this.playerRanOutOfManaTime[unit.hrid].isOutOfMana) {\r\n this.playerRanOutOfManaTime[unit.hrid].isOutOfMana = true;\r\n this.playerRanOutOfManaTime[unit.hrid].startTimeForOutOfMana = time;\r\n }\r\n } else {\r\n if (this.playerRanOutOfManaTime[unit.hrid].isOutOfMana) {\r\n this.playerRanOutOfManaTime[unit.hrid].isOutOfMana = false;\r\n this.playerRanOutOfManaTime[unit.hrid].totalTimeForOutOfMana += time - this.playerRanOutOfManaTime[unit.hrid].startTimeForOutOfMana;\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default SimResult;\r\n","import combatTriggerDependencyDetailMap from \"./data/combatTriggerDependencyDetailMap.json\";\r\n\r\nclass Trigger {\r\n constructor(dependencyHrid, conditionHrid, comparatorHrid, value = 0) {\r\n this.dependencyHrid = dependencyHrid;\r\n this.conditionHrid = conditionHrid;\r\n this.comparatorHrid = comparatorHrid;\r\n this.value = value;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let trigger = new Trigger(dto.dependencyHrid, dto.conditionHrid, dto.comparatorHrid, dto.value);\r\n\r\n return trigger;\r\n }\r\n\r\n isActive(source, target, friendlies, enemies, currentTime) {\r\n if (combatTriggerDependencyDetailMap[this.dependencyHrid].isSingleTarget) {\r\n return this.isActiveSingleTarget(source, target, currentTime);\r\n } else {\r\n return this.isActiveMultiTarget(friendlies, enemies, currentTime);\r\n }\r\n }\r\n\r\n isActiveSingleTarget(source, target, currentTime) {\r\n let dependencyValue;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/self\":\r\n dependencyValue = this.getDependencyValue(source, currentTime);\r\n break;\r\n case \"/combat_trigger_dependencies/targeted_enemy\":\r\n if (!target) {\r\n return false;\r\n }\r\n dependencyValue = this.getDependencyValue(target, currentTime);\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n isActiveMultiTarget(friendlies, enemies, currentTime) {\r\n let dependency;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/all_allies\":\r\n dependency = friendlies;\r\n break;\r\n case \"/combat_trigger_dependencies/all_enemies\":\r\n if (!enemies) {\r\n return false;\r\n }\r\n dependency = enemies;\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n let dependencyValue;\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/number_of_active_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints > 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/number_of_dead_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints <= 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/lowest_hp_percentage\":\r\n dependencyValue = dependency.reduce((prev, curr) => {\r\n let currentHpPercentage = curr.combatDetails.currentHitpoints / curr.combatDetails.maxHitpoints;\r\n return currentHpPercentage < prev ? currentHpPercentage : prev;\r\n }, 2) * 100;\r\n break;\r\n default:\r\n dependencyValue = dependency\r\n .map((unit) => this.getDependencyValue(unit, currentTime))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n break;\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n getDependencyValue(source, currentTime) {\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/berserk\":\r\n case \"/combat_trigger_conditions/frenzy\":\r\n case \"/combat_trigger_conditions/precision\":\r\n case \"/combat_trigger_conditions/vampirism\":\r\n case \"/combat_trigger_conditions/attack_coffee\":\r\n case \"/combat_trigger_conditions/defense_coffee\":\r\n case \"/combat_trigger_conditions/lucky_coffee\":\r\n case \"/combat_trigger_conditions/magic_coffee\":\r\n case \"/combat_trigger_conditions/melee_coffee\":\r\n case \"/combat_trigger_conditions/ranged_coffee\":\r\n case \"/combat_trigger_conditions/swiftness_coffee\":\r\n case \"/combat_trigger_conditions/wisdom_coffee\":\r\n case \"/combat_trigger_conditions/ice_spear\":\r\n case \"/combat_trigger_conditions/puncture\":\r\n case \"/combat_trigger_conditions/frost_surge\":\r\n case \"/combat_trigger_conditions/elusiveness\":\r\n case \"/combat_trigger_conditions/channeling_coffee\":\r\n case \"/combat_trigger_conditions/fierce_aura\":\r\n case \"/combat_trigger_conditions/invincible_armor\":\r\n case \"/combat_trigger_conditions/invincible_fire_resistance\":\r\n case \"/combat_trigger_conditions/invincible_nature_resistance\":\r\n case \"/combat_trigger_conditions/invincible_water_resistance\":\r\n case \"/combat_trigger_conditions/provoke\":\r\n case \"/combat_trigger_conditions/taunt\":\r\n case \"/combat_trigger_conditions/crippling_slash\":\r\n case \"/combat_trigger_conditions/mana_spring\":\r\n case \"/combat_trigger_conditions/retribution\":\r\n case \"/combat_trigger_conditions/fracturing_impact\":\r\n case \"/combat_trigger_conditions/maim\":\r\n case \"/combat_trigger_conditions/curse\":\r\n case \"/combat_trigger_conditions/weaken\":\r\n let buffHrid = \"/buff_uniques\";\r\n buffHrid += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n return source.combatBuffs[buffHrid];\r\n case \"/combat_trigger_conditions/critical_aura\":\r\n case \"/combat_trigger_conditions/critical_coffee\":\r\n case \"/combat_trigger_conditions/intelligence_coffee\":\r\n case \"/combat_trigger_conditions/stamina_coffee\":\r\n case \"/combat_trigger_conditions/elemental_affinity\":\r\n case \"/combat_trigger_conditions/fury\":\r\n case \"/combat_trigger_conditions/guardian_aura\":\r\n case \"/combat_trigger_conditions/insanity\":\r\n case \"/combat_trigger_conditions/spike_shell\":\r\n case \"/combat_trigger_conditions/toxic_pollen\":\r\n case \"/combat_trigger_conditions/invincible\":\r\n case \"/combat_trigger_conditions/mystic_aura\":\r\n case \"/combat_trigger_conditions/pestilent_shot\":\r\n case \"/combat_trigger_conditions/smoke_burst\":\r\n case \"/combat_trigger_conditions/speed_aura\":\r\n case \"/combat_trigger_conditions/toughness\":\r\n case \"/combat_trigger_conditions/enrage\":\r\n let buffPrefix = \"/buff_uniques\";\r\n buffPrefix += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n let buffs = Object.keys(source.combatBuffs).filter(buff => buff.startsWith(buffPrefix));\r\n return source.combatBuffs[buffs?.[0]];\r\n case \"/combat_trigger_conditions/current_hp\":\r\n return source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/current_mp\":\r\n return source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/missing_hp\":\r\n return source.combatDetails.maxHitpoints - source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/missing_mp\":\r\n return source.combatDetails.maxManapoints - source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/stun_status\":\r\n // Replicate the game's behaviour of \"stun status active\" triggers activating\r\n // immediately after the stun has worn off\r\n return source.isStunned || source.stunExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/blind_status\":\r\n return source.isBlinded || source.blindExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/silence_status\":\r\n return source.isSilenced || source.silenceExpireTime == currentTime;\r\n default:\r\n throw new Error(\"Unknown conditionHrid in trigger: \" + this.conditionHrid);\r\n }\r\n }\r\n\r\n compareValue(dependencyValue) {\r\n switch (this.comparatorHrid) {\r\n case \"/combat_trigger_comparators/greater_than_equal\":\r\n return dependencyValue >= this.value;\r\n case \"/combat_trigger_comparators/less_than_equal\":\r\n return dependencyValue <= this.value;\r\n case \"/combat_trigger_comparators/is_active\":\r\n return !!dependencyValue;\r\n case \"/combat_trigger_comparators/is_inactive\":\r\n return !dependencyValue;\r\n default:\r\n throw new Error(\"Unknown comparatorHrid in trigger: \" + this.comparatorHrid);\r\n }\r\n }\r\n}\r\n\r\nexport default Trigger;\r\n","import actionDetailMap from \"./data/actionDetailMap.json\";\r\nimport Monster from \"./monster\";\r\n\r\nclass Zone {\r\n constructor(hrid, difficultyTier) {\r\n this.hrid = hrid;\r\n this.difficultyTier = difficultyTier;\r\n\r\n let gameZone = actionDetailMap[this.hrid];\r\n this.monsterSpawnInfo = gameZone.combatZoneInfo.fightInfo;\r\n this.dungeonSpawnInfo = gameZone.combatZoneInfo.dungeonInfo;\r\n this.encountersKilled = 1;\r\n this.monsterSpawnInfo.battlesPerBoss = 10;\r\n this.buffs = gameZone.buffs;\r\n this.isDungeon = gameZone.combatZoneInfo.isDungeon;\r\n this.dungeonsCompleted = 0;\r\n this.dungeonsFailed = 0;\r\n this.finalWave = false;\r\n }\r\n\r\n getRandomEncounter() {\r\n\r\n if (this.monsterSpawnInfo.bossSpawns && this.encountersKilled == this.monsterSpawnInfo.battlesPerBoss) {\r\n this.encountersKilled = 1;\r\n return this.monsterSpawnInfo.bossSpawns.map((monster) => new Monster(monster.combatMonsterHrid, monster.difficultyTier + this.difficultyTier));\r\n }\r\n\r\n let totalWeight = this.monsterSpawnInfo.randomSpawnInfo.spawns.reduce((prev, cur) => prev + cur.rate, 0);\r\n\r\n let encounterHrids = [];\r\n let totalStrength = 0;\r\n\r\n outer: for (let i = 0; i < this.monsterSpawnInfo.randomSpawnInfo.maxSpawnCount; i++) {\r\n let randomWeight = totalWeight * Math.random();\r\n let cumulativeWeight = 0;\r\n\r\n for (const spawn of this.monsterSpawnInfo.randomSpawnInfo.spawns) {\r\n cumulativeWeight += spawn.rate;\r\n if (randomWeight <= cumulativeWeight) {\r\n totalStrength += spawn.strength;\r\n\r\n if (totalStrength <= this.monsterSpawnInfo.randomSpawnInfo.maxTotalStrength) {\r\n encounterHrids.push({ 'hrid': spawn.combatMonsterHrid, 'difficultyTier': spawn.difficultyTier});\r\n\r\n } else {\r\n break outer;\r\n }\r\n break;\r\n }\r\n }\r\n }\r\n this.encountersKilled++;\r\n return encounterHrids.map((hrid) => new Monster(hrid.hrid, hrid.difficultyTier + this.difficultyTier));\r\n }\r\n\r\n failWave() {\r\n this.dungeonsFailed++;\r\n this.encountersKilled = 1;\r\n }\r\n\r\n getNextWave() {\r\n if (this.encountersKilled > this.dungeonSpawnInfo.maxWaves) {\r\n this.dungeonsCompleted++;\r\n this.encountersKilled = 1;\r\n }\r\n // console.log(\"Wave #\" + this.encountersKilled);\r\n if (this.dungeonSpawnInfo.fixedSpawnsMap.hasOwnProperty(this.encountersKilled.toString())) {\r\n let currentMonsters = this.dungeonSpawnInfo.fixedSpawnsMap[(this.encountersKilled).toString()];\r\n this.encountersKilled++;\r\n return currentMonsters.map((monster) => new Monster(monster.combatMonsterHrid, monster.difficultyTier + this.difficultyTier));\r\n } else {\r\n let monsterSpawns = {};\r\n const waveKeys = Object.keys(this.dungeonSpawnInfo.randomSpawnInfoMap).map(Number).sort((a, b) => a - b);\r\n if (this.encountersKilled > waveKeys[waveKeys.length - 1]) {\r\n monsterSpawns = this.dungeonSpawnInfo.randomSpawnInfoMap[waveKeys[waveKeys.length - 1]];\r\n } else {\r\n for (let i = 0; i < waveKeys.length - 1; i++) {\r\n if (this.encountersKilled >= waveKeys[i] && this.encountersKilled <= waveKeys[i + 1]) {\r\n monsterSpawns = this.dungeonSpawnInfo.randomSpawnInfoMap[waveKeys[i]];\r\n break;\r\n }\r\n }\r\n }\r\n let totalWeight = monsterSpawns.spawns.reduce((prev, cur) => prev + cur.rate, 0);\r\n\r\n let encounterHrids = [];\r\n let totalStrength = 0;\r\n\r\n outer: for (let i = 0; i < monsterSpawns.maxSpawnCount; i++) {\r\n let randomWeight = totalWeight * Math.random();\r\n let cumulativeWeight = 0;\r\n\r\n for (const spawn of monsterSpawns.spawns) {\r\n cumulativeWeight += spawn.rate;\r\n if (randomWeight <= cumulativeWeight) {\r\n totalStrength += spawn.strength;\r\n\r\n if (totalStrength <= monsterSpawns.maxTotalStrength) {\r\n encounterHrids.push({ 'hrid': spawn.combatMonsterHrid, 'difficultyTier': spawn.difficultyTier});\r\n\r\n } else {\r\n break outer;\r\n }\r\n break;\r\n }\r\n }\r\n }\r\n this.encountersKilled++;\r\n return encounterHrids.map((hrid) => new Monster(hrid.hrid, hrid.difficultyTier + this.difficultyTier));\r\n }\r\n }\r\n}\r\n\r\nexport default Zone;\r\n","import CombatSimulator from \"./combatsimulator/combatSimulator\";\r\nimport Player from \"./combatsimulator/player\";\r\nimport Zone from \"./combatsimulator/zone\";\r\n\r\n\r\nonmessage = async function (event) {\r\n switch (event.data.type) {\r\n case \"start_simulation\":\r\n let extraBuffs = [];\r\n if (event.data.extra.mooPass) {\r\n const mooPassBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/experience_moo_pass_buff\",\r\n \"typeHrid\": \"/buff_types/wisdom\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0.05,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": 0\r\n };\r\n extraBuffs.push(mooPassBuff);\r\n }\r\n if (event.data.extra.comExp > 0) {\r\n const comExpBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/experience_community_buff\",\r\n \"typeHrid\": \"/buff_types/wisdom\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0.005 * (event.data.extra.comExp - 1) + 0.2,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": 0\r\n };\r\n extraBuffs.push(comExpBuff);\r\n }\r\n if (event.data.extra.comDrop > 0) {\r\n const comDropBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/combat_community_buff\",\r\n \"typeHrid\": \"/buff_types/combat_drop_quantity\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0.005 * (event.data.extra.comDrop - 1) + 0.2,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": 0\r\n };\r\n extraBuffs.push(comDropBuff);\r\n }\r\n\r\n let playersData = event.data.players;\r\n let players = [];\r\n let zone = new Zone(event.data.zone.zoneHrid, event.data.zone.difficultyTier);\r\n for (let i = 0; i < playersData.length; i++) {\r\n let currentPlayer = Player.createFromDTO(structuredClone(playersData[i]));\r\n currentPlayer.zoneBuffs = zone.buffs;\r\n currentPlayer.extraBuffs = extraBuffs;\r\n players.push(currentPlayer);\r\n }\r\n let simulationTimeLimit = event.data.simulationTimeLimit;\r\n let combatSimulator = new CombatSimulator(players, zone);\r\n combatSimulator.addEventListener(\"progress\", (event) => {\r\n this.postMessage({ type: \"simulation_progress\", progress: event.detail.progress, zone: event.detail.zone, difficultyTier: event.detail.difficultyTier });\r\n });\r\n\r\n try {\r\n let simResult = await combatSimulator.simulate(simulationTimeLimit);\r\n this.postMessage({ type: \"simulation_result\", simResult: simResult });\r\n } catch (e) {\r\n console.log(e);\r\n this.postMessage({ type: \"simulation_error\", error: e });\r\n }\r\n break;\r\n }\r\n};\r\n"],"names":[],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"src_worker_js.bundle.js","mappings":";;;;;;;;;;;;;;;;AAA0B;AACkC;AAC5B;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,wDAAgB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,6CAAI;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;ACxNG;AACkD;AACR;AACpE;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,gEAAwB;AAChE;AACA,0CAA0C,4DAAoB;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,WAAW;;;;;;;;;;;;;;AC1B1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,IAAI,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACd4B;AACO;AACQ;AACU;AAChB;AACM;AACF;AACF;AACd;AACgB;AACR;AACU;AACE;AACI;AACJ;AACE;AACJ;AACR;AACnB;AAC2B;AACF;AAC7B;AACA;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,0DAAU;AACxC,6BAA6B,mDAAS;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,kBAAkB;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,aAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,KAAK,MAAM,WAAW,OAAO,YAAY;AAChE,wBAAwB,YAAY,KAAK,YAAY;AACrD,yBAAyB,cAAc,IAAI,YAAY;AACvD,4BAA4B,0BAA0B,OAAO,IAAI,UAAU,GAAG,MAAM,eAAe;AACnG;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA,mCAAmC,UAAU;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA,mCAAmC,UAAU;AAC7C;AACA;AACA;AACA;AACA;AACA,gBAAgB,yBAAyB;AACzC;AACA;AACA,wBAAwB,WAAW;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,gEAAgB;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,iBAAiB;AACjB;AACA;AACA;AACA;AACA,2BAA2B,0CAA0C;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0CAA0C;AAC1E;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA,wBAAwB,yBAAyB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,mDAAS;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,gEAAgB;AACjC;AACA;AACA,iBAAiB,kEAAkB;AACnC;AACA;AACA,iBAAiB,iEAAiB;AAClC;AACA;AACA,iBAAiB,+DAAe;AAChC;AACA;AACA,iBAAiB,mEAAmB;AACpC;AACA;AACA,iBAAiB,mEAAmB;AACpC;AACA;AACA,iBAAiB,wEAAwB;AACzC;AACA;AACA,iBAAiB,+DAAc;AAC/B;AACA;AACA,iBAAiB,oEAAmB;AACpC;AACA;AACA,iBAAiB,qEAAoB;AACrC;AACA;AACA,iBAAiB,uEAAsB;AACvC;AACA;AACA,iBAAiB,qEAAoB;AACrC;AACA;AACA,iBAAiB,sEAAqB;AACtC;AACA;AACA,iBAAiB,oEAAmB;AACpC;AACA;AACA,iBAAiB,gEAAe;AAChC;AACA;AACA,iBAAiB,oEAAmB;AACpC;AACA;AACA,iBAAiB,mEAAkB;AACnC;AACA;AACA;AACA,iBAAiB,kEAAkB;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,yBAAyB;AACjD,mCAAmC;AACnC;AACA;AACA;AACA;AACA,iCAAiC,+DAAc;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,yBAAyB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,0CAA0C,gEAAe;AACzD,kCAAkC,gEAAe;AACjD;AACA;AACA;AACA,0CAA0C,oEAAmB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,yBAAyB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,wDAAe;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6FAA6F,qEAAoB;AACjH;AACA;AACA,uEAAuE,qEAAoB;AAC3F;AACA,+CAA+C,qEAAoB;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4FAA4F,oEAAmB;AAC/G,uEAAuE,oEAAmB;AAC1F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,oEAAmB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8FAA8F,sEAAqB;AACnH;AACA;AACA;AACA,uEAAuE,sEAAqB;AAC5F,gDAAgD,sEAAqB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,+DAAe;AAC7D;AACA,wCAAwC,iEAAiB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4GAA4G,kEAAkB;AAC9H;AACA,iDAAiD,kEAAkB;AACnE;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,gEAAgB;AAC3D;AACA,cAAc;AACd,kDAAkD,+DAAe;AACjE,kDAAkD,oEAAmB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,oEAAmB,uBAAuB,+DAAe;AAC3H;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,wDAAe;AACpC;AACA;AACA,UAAU;AACV,qBAAqB,wDAAe;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD,oEAAmB;AACzE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sCAAsC,+DAAe;AACrD;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,wDAAe;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,wDAAe;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,mEAAkB;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,mEAAmB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,wDAAe;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,mEAAmB;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,mEAAkB;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,+DAAc;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,kCAAkC,gEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,wDAAe;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,qCAAqC,kEAAkB;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,mEAAkB;AACnE;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV,0CAA0C,mEAAmB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+CAA+C,wEAAwB;AACvE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,mCAAmC,iDAAO;AAC1C;AACA;AACA;AACA,mCAAmC,iDAAO;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,uDAAuD,wEAAwB;AAC/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+CAA+C,wEAAwB;AACvE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC;AACpC;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,wDAAe;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,wDAAe;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2DAA2D,wEAAwB;AACnF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,mEAAmB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4EAA4E,+DAAe,uBAAuB,oEAAmB,uBAAuB,oEAAmB;AAC/K,kDAAkD,oEAAmB;AACrE;AACA;AACA;AACA;AACA;AACA;AACA,2EAA2E,qEAAoB;AAC/F,+EAA+E,+DAAe;AAC9F;AACA;AACA;AACA,mDAAmD,qEAAoB;AACvE;AACA;AACA;AACA;AACA;AACA;AACA,2EAA2E,uEAAsB;AACjG,+EAA+E,oEAAmB;AAClG;AACA;AACA;AACA,qDAAqD,uEAAsB;AAC3E;AACA;AACA;AACA;AACA;AACA,iGAAiG,qEAAoB;AACrH;AACA;AACA,2EAA2E,qEAAoB;AAC/F;AACA,mDAAmD,qEAAoB;AACvE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kGAAkG,sEAAqB;AACvH;AACA;AACA;AACA,2EAA2E,sEAAqB;AAChG,oDAAoD,sEAAqB;AACzE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,wDAAe;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,wDAAe;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,wDAAe;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mEAAmE,kEAAkB;AACrF;AACA;AACA;AACA,+BAA+B,wDAAe;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,iDAAO;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,wDAAe;AACrC;AACA;AACA;AACA;AACA;AACA,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;;AC9jD/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wDAAwD;AACtF,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,mBAAmB;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;AC/gB1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;;;;;AC9VL;AAC4B;AACtB;AAChC;AACA;AACA;AACA;AACA;AACA,6BAA6B,qDAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;ACtF1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,KAAK,EAAC;;;;;;;;;;;;;;;;;ACXiC;AACmD;AACzG;AACA;AACA;AACA;AACA,uBAAuB,qDAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,iFAAoC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS,EAAC;;;;;;;;;;;;;;;;AC/Ce;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB;;;;;;;;;;;;;;;ACbM;AACxC;AACA,8BAA8B,oDAAW;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;;;ACZS;AACxC;AACA,iCAAiC,oDAAW;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,kBAAkB;;;;;;;;;;;;;;;ACZO;AACxC;AACA,mCAAmC,oDAAW;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,oBAAoB;;;;;;;;;;;;;;;ACZK;AACxC;AACA,uCAAuC,oDAAW;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,wBAAwB,EAAC;;;;;;;;;;;;;;;ACZxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,WAAW,EAAC;;;;;;;;;;;;;;;;ACPa;AACxC;AACA,+BAA+B,oDAAW;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,gBAAgB,EAAC;;;;;;;;;;;;;;;;ACVQ;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB,EAAC;;;;;;;;;;;;;;;;ACfK;AACxC;AACA,iCAAiC,oDAAW;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,kBAAkB,EAAC;;;;;;;;;;;;;;;;ACVM;AACxC;AACA,mCAAmC,oDAAW;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,oBAAoB;;;;;;;;;;;;;;;ACfK;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB,EAAC;;;;;;;;;;;;;;;;AClBK;AACxC;AACA,gCAAgC,oDAAW;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,iBAAiB,EAAC;;;;;;;;;;;;;;;;ACVO;AACxC;AACA,8BAA8B,oDAAW;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;;;ACbJ;AAC3B;AACA;AACA;AACA,2BAA2B,+CAAI;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,+CAAI;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;AChEc;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB;;;;;;;;;;;;;;;ACbM;AACxC;AACA,iCAAiC,oDAAW;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,kBAAkB,EAAC;;;;;;;;;;;;;;;;ACXM;AACxC;AACA,6BAA6B,oDAAW;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,cAAc,EAAC;;;;;;;;;;;;;;;;ACVU;AACxC;AACA,qCAAqC,oDAAW;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,sBAAsB;;;;;;;;;;;;;;;ACZG;AACxC;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,mBAAmB;;;;;;;;;;;;;;;ACZM;AACxC;AACA,oCAAoC,oDAAW;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,qBAAqB;;;;;;;;;;;;;;;;AChBV;AACsC;AAChE;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,0DAAkB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS;;;;;;;;;;;;;;;;;;AC7BQ;AACM;AACkC;AAC5C;AAC5B;AACA,sBAAsB,mDAAU;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,8DAAsB;AAChD;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,kCAAkC;AAC1D;AACA;AACA;AACA,oCAAoC,gDAAO;AAC3C;AACA;AACA,wBAAwB,kCAAkC;AAC1D,oCAAoC,8CAAK;AACzC;AACA,wBAAwB,sCAAsC;AAC9D;AACA;AACA;AACA,wCAAwC,8CAAK;AAC7C;AACA;AACA;AACA;AACA,0BAA0B,8DAAsB;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;;;;AClJS;AACM;AACA;AACF;AACA;AACI;AACxC;AACA,qBAAqB,mDAAU;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,kDAAS;AACrD;AACA;AACA,qDAAqD,mDAAU;AAC/D,2DAA2D,mDAAU;AACrE,qEAAqE,gDAAO;AAC5E;AACA;AACA,2CAA2C,kDAAS;AACpD;AACA,SAAS;AACT;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,MAAM,EAAC;;;;;;;;;;;;;;;;ACvL6C;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd,2CAA2C,uEAAuE;AAClH;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,4DAAoB;AAChD;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD;AACtD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS,EAAC;;;;;;;;;;;;;;;;ACpQmE;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,wEAAgC;AAC5C;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;ACjLmC;AAC1B;AAChC;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,uDAAe;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yEAAyE,gDAAO;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,yDAAyD;AACxF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,wEAAwE;AACtH;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gDAAgD,gDAAO;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wDAAwD,gDAAO;AAC/D,UAAU;AACV;AACA;AACA;AACA;AACA,cAAc;AACd,gCAAgC,yBAAyB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,iCAAiC;AACpE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,wEAAwE;AAC1H;AACA,0BAA0B;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oDAAoD,gDAAO;AAC3D;AACA;AACA;AACA;AACA,iEAAe,IAAI,EAAC;;;;;;;;;;;;;;;ACjH4C;AAClB;AACJ;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,6DAAI;AAC/B,4BAA4B,wBAAwB;AACpD,oCAAoC,+DAAM;AAC1C;AACA;AACA;AACA;AACA;AACA,sCAAsC,wEAAe;AACrD;AACA,mCAAmC,oIAAoI;AACvK,aAAa;AACb;AACA;AACA;AACA,mCAAmC,iDAAiD;AACpF,cAAc;AACd;AACA,mCAAmC,oCAAoC;AACvE;AACA;AACA;AACA","sources":["webpack://mwicombatsimulator/./src/combatsimulator/ability.js","webpack://mwicombatsimulator/./src/combatsimulator/achievement.js","webpack://mwicombatsimulator/./src/combatsimulator/buff.js","webpack://mwicombatsimulator/./src/combatsimulator/combatSimulator.js","webpack://mwicombatsimulator/./src/combatsimulator/combatUnit.js","webpack://mwicombatsimulator/./src/combatsimulator/combatUtilities.js","webpack://mwicombatsimulator/./src/combatsimulator/consumable.js","webpack://mwicombatsimulator/./src/combatsimulator/drops.js","webpack://mwicombatsimulator/./src/combatsimulator/equipment.js","webpack://mwicombatsimulator/./src/combatsimulator/events/abilityCastEndEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/autoAttackEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/awaitCooldownEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/blindExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/checkBuffExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/combatEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/combatStartEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/consumableTickEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/cooldownReadyEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/curseExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/damageOverTimeEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/enemyRespawnEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/enrageTickEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/eventQueue.js","webpack://mwicombatsimulator/./src/combatsimulator/events/furyExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/playerRespawnEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/regenTickEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/silenceExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/stunExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/events/weakenExpirationEvent.js","webpack://mwicombatsimulator/./src/combatsimulator/houseRoom.js","webpack://mwicombatsimulator/./src/combatsimulator/monster.js","webpack://mwicombatsimulator/./src/combatsimulator/player.js","webpack://mwicombatsimulator/./src/combatsimulator/simResult.js","webpack://mwicombatsimulator/./src/combatsimulator/trigger.js","webpack://mwicombatsimulator/./src/combatsimulator/zone.js","webpack://mwicombatsimulator/./src/worker.js"],"sourcesContent":["import Buff from \"./buff\";\r\nimport abilityDetailMap from \"./data/abilityDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nconst abilityFromCombatStat = {\r\n \"blaze\":\r\n {\r\n \"hrid\": \"/abilities/blaze\",\r\n \"name\": \"Blaze\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"allEnemies\",\r\n \"effectType\": \"/ability_effect_types/damage\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"/damage_types/fire\",\r\n \"baseDamageFlat\": 0,\r\n \"baseDamageFlatLevelBonus\": 0.0,\r\n \"baseDamageRatio\": 0.3,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/number_of_active_units\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n },\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/current_hp\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n }\r\n ],\r\n },\r\n \"bloom\":\r\n {\r\n \"hrid\": \"/abilities/bloom\",\r\n \"name\": \"Bloom\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"lowestHpAlly\",\r\n \"effectType\": \"/ability_effect_types/heal\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"\",\r\n \"baseDamageFlat\": 10,\r\n \"baseDamageFlatLevelBonus\": 0,\r\n \"baseDamageRatio\": 0.15,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_allies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/lowest_hp_percentage\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/less_than_equal\",\r\n \"value\": 100\r\n }\r\n ],\r\n }\r\n}\r\n\r\nclass Ability {\r\n constructor(hrid, level = 1, triggers = null) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameAbility = abilityDetailMap[hrid];\r\n if (!gameAbility) {\r\n gameAbility = abilityFromCombatStat[hrid];\r\n }\r\n if (!gameAbility) {\r\n throw new Error(\"No ability found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.manaCost = gameAbility.manaCost;\r\n this.cooldownDuration = gameAbility.cooldownDuration;\r\n this.castDuration = gameAbility.castDuration;\r\n this.isSpecialAbility = gameAbility.isSpecialAbility;\r\n\r\n this.abilityEffects = [];\r\n\r\n for (const effect of gameAbility.abilityEffects) {\r\n let abilityEffect = {\r\n targetType: effect.targetType,\r\n effectType: effect.effectType,\r\n combatStyleHrid: effect.combatStyleHrid,\r\n damageType: effect.damageType,\r\n damageFlat: effect.baseDamageFlat + (this.level - 1) * effect.baseDamageFlatLevelBonus,\r\n damageRatio: effect.baseDamageRatio + (this.level - 1) * effect.baseDamageRatioLevelBonus,\r\n bonusAccuracyRatio: effect.bonusAccuracyRatio + (this.level - 1) * effect.bonusAccuracyRatioLevelBonus,\r\n damageOverTimeRatio: effect.damageOverTimeRatio,\r\n damageOverTimeDuration: effect.damageOverTimeDuration,\r\n armorDamageRatio: effect.armorDamageRatio + (this.level - 1) * effect.armorDamageRatioLevelBonus,\r\n hpDrainRatio: effect.hpDrainRatio,\r\n pierceChance: effect.pierceChance,\r\n blindChance: effect.blindChance,\r\n blindDuration: effect.blindDuration,\r\n silenceChance: effect.silenceChance,\r\n silenceDuration: effect.silenceDuration,\r\n stunChance: effect.stunChance,\r\n stunDuration: effect.stunDuration,\r\n spendHpRatio: effect.spendHpRatio,\r\n buffs: null,\r\n };\r\n if (effect.buffs) {\r\n abilityEffect.buffs = [];\r\n for (const buff of effect.buffs) {\r\n abilityEffect.buffs.push(new Buff(buff, this.level));\r\n }\r\n }\r\n this.abilityEffects.push(abilityEffect);\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameAbility.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let ability = new Ability(dto.hrid, dto.level, triggers);\r\n\r\n return ability;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n\r\n if (source.isSilenced) {\r\n return false;\r\n }\r\n\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = this.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Ability;\r\n","import Buff from \"./buff\";\r\nimport achievementTierDetailMap from \"./data/achievementTierDetailMap.json\";\r\nimport achievementDetailMap from \"./data/achievementDetailMap.json\";\r\n\r\nclass Achievement {\r\n constructor(achievements) {\r\n this.achievements = achievements;\r\n this.buffs = [];\r\n\r\n for(const tier of Object.values(achievementTierDetailMap)) {\r\n let isGetAll = true;\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid)\r\n for(const achievement of Object.values(detailMap)) {\r\n if(!this.achievements[achievement.hrid] || this.achievements[achievement.hrid] == false) {\r\n isGetAll = false;\r\n break;\r\n }\r\n }\r\n if(isGetAll) {\r\n let buff = new Buff(tier.buff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default Achievement;","class Buff {\r\n startTime;\r\n\r\n constructor(buff, level = 1) {\r\n this.uniqueHrid = buff.uniqueHrid;\r\n this.typeHrid = buff.typeHrid;\r\n this.ratioBoost = buff.ratioBoost + (level - 1) * buff.ratioBoostLevelBonus;\r\n this.flatBoost = buff.flatBoost + (level - 1) * buff.flatBoostLevelBonus;\r\n this.duration = buff.duration;\r\n this.multiplierForSkillHrid = buff.multiplierForSkillHrid ?? \"\";\r\n this.multiplierPerSkillLevel = buff.multiplierPerSkillLevel ?? 0;\r\n }\r\n}\r\n\r\nexport default Buff;\r\n","import CombatUtilities from \"./combatUtilities\";\r\nimport AutoAttackEvent from \"./events/autoAttackEvent\";\r\nimport DamageOverTimeEvent from \"./events/damageOverTimeEvent\";\r\nimport CheckBuffExpirationEvent from \"./events/checkBuffExpirationEvent\";\r\nimport CombatStartEvent from \"./events/combatStartEvent\";\r\nimport ConsumableTickEvent from \"./events/consumableTickEvent\";\r\nimport CooldownReadyEvent from \"./events/cooldownReadyEvent\";\r\nimport EnemyRespawnEvent from \"./events/enemyRespawnEvent\";\r\nimport EventQueue from \"./events/eventQueue\";\r\nimport PlayerRespawnEvent from \"./events/playerRespawnEvent\";\r\nimport RegenTickEvent from \"./events/regenTickEvent\";\r\nimport StunExpirationEvent from \"./events/stunExpirationEvent\";\r\nimport BlindExpirationEvent from \"./events/blindExpirationEvent\";\r\nimport SilenceExpirationEvent from \"./events/silenceExpirationEvent\";\r\nimport CurseExpirationEvent from \"./events/curseExpirationEvent\";\r\nimport WeakenExpirationEvent from \"./events/weakenExpirationEvent\";\r\nimport FuryExpirationEvent from \"./events/furyExpirationEvent\";\r\nimport EnrageTickEvent from \"./events/enrageTickEvent\";\r\nimport SimResult from \"./simResult\";\r\nimport AbilityCastEndEvent from \"./events/abilityCastEndEvent\";\r\nimport AwaitCooldownEvent from \"./events/awaitCooldownEvent\";\r\nimport Monster from \"./monster\";\r\nimport Ability from \"./ability\";\r\n\r\nconst ONE_SECOND = 1e9;\r\nconst HOT_TICK_INTERVAL = 5 * ONE_SECOND;\r\nconst DOT_TICK_INTERVAL = 3 * ONE_SECOND;\r\nconst REGEN_TICK_INTERVAL = 10 * ONE_SECOND;\r\nconst ENEMY_RESPAWN_INTERVAL = 3 * ONE_SECOND;\r\nconst PLAYER_RESPAWN_INTERVAL = 150 * ONE_SECOND;\r\nconst RESTART_INTERVAL = 15 * ONE_SECOND;\r\nconst ENRAGE_TICK_INTERVAL = 60 * ONE_SECOND;\r\n\r\nclass CombatSimulator extends EventTarget {\r\n constructor(players, zone) {\r\n super();\r\n this.players = players;\r\n this.zone = zone;\r\n this.eventQueue = new EventQueue();\r\n this.simResult = new SimResult(zone, players.length);\r\n this.allPlayersDead = false;\r\n\r\n this.wipeLogs = {\r\n buffer: new Array(200),\r\n index: 0,\r\n count: 0,\r\n maxSize: 200\r\n };\r\n }\r\n\r\n addToWipeLogs(logEntry) {\r\n const { buffer, maxSize } = this.wipeLogs;\r\n\r\n buffer[this.wipeLogs.index] = logEntry;\r\n this.wipeLogs.index = (this.wipeLogs.index + 1) % maxSize;\r\n this.wipeLogs.count = Math.min(this.wipeLogs.count + 1, maxSize);\r\n }\r\n\r\n logAndResetWipeLogs() {\r\n const logs = this.getOrderedWipeLogs();\r\n \r\n // console.log(\"===== 团灭日志 =====\");\r\n // console.log(`最后 ${logs.length} 条战斗日志:`);\r\n \r\n logs.forEach(log => {\r\n if (log.error) {\r\n console.log(log.error);\r\n return;\r\n }\r\n \r\n const time = (log.time / 1e9).toFixed(2);\r\n // console.log(\r\n // `[${time}s] [${log.source}] 用 [${log.ability}] ` +\r\n // `对 ${log.target} 造成 ${log.damage} 伤害,` +\r\n // `HP ${log.beforeHp} → ${log.afterHp}。` +\r\n // `队伍生命值:${log.playersHp.map(p => `${p.hrid}: ${p.current}/${p.max}`).join(\" | \")}`\r\n // );\r\n });\r\n\r\n this.wipeLogs.index = 0;\r\n this.wipeLogs.count = 0;\r\n // console.log(\"===== 团灭日志结束 =====\");\r\n }\r\n \r\n buildCombatLog(source, ability, target, damageDone) {\r\n try {\r\n const sourceHrid = source?.hrid || \"UNKNOWN_SOURCE\";\r\n const targetHrid = target?.hrid || \"UNKNOWN_TARGET\";\r\n \r\n const afterHp = target?.combatDetails?.currentHitpoints || 0;\r\n const beforeHp = Math.max(0, afterHp + damageDone);\r\n\r\n const playersHp = this.players.map(p => ({\r\n hrid: p.hrid || \"UNKNOWN_PLAYER\",\r\n current: p.combatDetails?.currentHitpoints ?? 0,\r\n max: p.combatDetails?.maxHitpoints ?? 0\r\n }));\r\n \r\n return {\r\n time: this.simulationTime,\r\n wave: (this.zone.encountersKilled - 1),\r\n source: sourceHrid,\r\n ability: ability,\r\n target: targetHrid,\r\n damage: damageDone,\r\n beforeHp: beforeHp,\r\n afterHp: afterHp,\r\n playersHp: playersHp,\r\n // enemiesHp: enemiesHp,\r\n isCrit: false,\r\n };\r\n } catch (e) {\r\n return {\r\n error: `[日志生成错误] ${e.message}`\r\n };\r\n }\r\n }\r\n\r\n generateCombatLog(source, ability, target, attackResult) {\r\n try {\r\n const sourceHrid = source?.hrid || \"UNKNOWN_SOURCE\";\r\n const targetHrid = target?.hrid || \"UNKNOWN_TARGET\";\r\n const damage = attackResult?.damageDone || 0;\r\n \r\n const afterHp = target?.combatDetails?.currentHitpoints || 0;\r\n const beforeHp = Math.max(0, afterHp + damage);\r\n\r\n const playersHp = this.players.map(p => ({\r\n hrid: p.hrid || \"UNKNOWN_PLAYER\",\r\n current: p.combatDetails?.currentHitpoints ?? 0,\r\n max: p.combatDetails?.maxHitpoints ?? 0\r\n }));\r\n \r\n return {\r\n time: this.simulationTime,\r\n wave: (this.zone.encountersKilled - 1),\r\n source: sourceHrid,\r\n ability: ability,\r\n target: targetHrid,\r\n damage: damage,\r\n beforeHp: beforeHp,\r\n afterHp: afterHp,\r\n playersHp: playersHp,\r\n // enemiesHp: enemiesHp,\r\n isCrit: attackResult?.isCrit || false,\r\n };\r\n } catch (e) {\r\n return {\r\n error: `[日志生成错误] ${e.message}`\r\n };\r\n }\r\n }\r\n \r\n getOrderedWipeLogs() {\r\n const { buffer, maxSize, count } = this.wipeLogs;\r\n const logs = [];\r\n \r\n for (let i = 0; i < count; i++) {\r\n const idx = (this.wipeLogs.index - count + maxSize + i) % maxSize;\r\n logs.push(buffer[idx]);\r\n }\r\n \r\n return logs;\r\n }\r\n\r\n saveWipeLogsToSimResult(wave) {\r\n const logs = this.getOrderedWipeLogs();\r\n this.simResult.addWipeEvent(logs, this.simulationTime, wave);\r\n }\r\n\r\n async simulate(simulationTimeLimit) {\r\n this.reset();\r\n\r\n let ticks = 0;\r\n\r\n let combatStartEvent = new CombatStartEvent(0);\r\n this.eventQueue.addEvent(combatStartEvent);\r\n\r\n while (this.simulationTime < simulationTimeLimit) {\r\n let nextEvent = this.eventQueue.getNextEvent();\r\n await this.processEvent(nextEvent);\r\n\r\n ticks++;\r\n if (ticks == 1000) {\r\n ticks = 0;\r\n let progressEvent = new CustomEvent(\"progress\", {\r\n detail: {\r\n zone: this.zone.hrid,\r\n difficultyTier: this.zone.difficultyTier,\r\n progress: Math.min(this.simulationTime / simulationTimeLimit, 1)\r\n },\r\n });\r\n this.dispatchEvent(progressEvent);\r\n }\r\n }\r\n\r\n // for (let i = 0; i < this.simResult.timeSpentAlive.length; i++) {\r\n // if (this.simResult.timeSpentAlive[i].alive == true) {\r\n // this.simResult.updateTimeSpentAlive(this.simResult.timeSpentAlive[i].name, false, simulationTimeLimit);\r\n // }\r\n // }\r\n\r\n this.simResult.isDungeon = this.zone.isDungeon;\r\n if (this.simResult.isDungeon) {\r\n console.log(\"Timeout now at wave #\" + (this.zone.encountersKilled - 1));\r\n\r\n this.simResult.dungeonsCompleted = this.zone.dungeonsCompleted;\r\n this.simResult.dungeonsFailed = this.zone.dungeonsFailed;\r\n if (this.simResult.dungeonsCompleted < 1) {\r\n this.simResult.maxWaveReached = 0;\r\n for (let i = 0; i <= this.zone.dungeonSpawnInfo.maxWaves; i++) {\r\n let waveName = \"#\" + i.toString();\r\n const idx = this.simResult.timeSpentAlive.findIndex(e => e.name === waveName);\r\n if (idx == -1 || this.simResult.timeSpentAlive[idx].count == 0) {\r\n break;\r\n }\r\n this.simResult.maxWaveReached = i;\r\n }\r\n } else {\r\n this.simResult.maxWaveReached = this.zone.dungeonSpawnInfo.maxWaves;\r\n }\r\n }\r\n this.simResult.simulatedTime = this.simulationTime;\r\n \r\n for (let i = 0; i < this.players.length; i++) {\r\n this.simResult.setDropRateMultipliers(this.players[i]);\r\n this.simResult.setManaUsed(this.players[i]);\r\n }\r\n\r\n if (this.zone.isDungeon) {\r\n Object.entries(this.zone.dungeonSpawnInfo.fixedSpawnsMap).forEach(([wave, monsters]) => {\r\n let waveName = \"#\" + wave.toString();\r\n monsters.forEach(monster => {\r\n waveName += ',' + monster.combatMonsterHrid;\r\n });\r\n this.simResult.bossSpawns.push(waveName);\r\n });\r\n\r\n }\r\n if (this.zone.monsterSpawnInfo.bossSpawns) {\r\n for (const boss of this.zone.monsterSpawnInfo.bossSpawns) {\r\n this.simResult.bossSpawns.push(boss.combatMonsterHrid);\r\n }\r\n }\r\n\r\n return this.simResult;\r\n }\r\n\r\n reset() {\r\n this.tempDungeonCount = 0;\r\n this.simulationTime = 0;\r\n this.eventQueue.clear();\r\n this.simResult = new SimResult(this.zone, this.players.length);\r\n }\r\n\r\n async processEvent(event) {\r\n this.simulationTime = event.time;\r\n\r\n // console.log(this.simulationTime / 1e9, event.type, event);\r\n\r\n switch (event.type) {\r\n case CombatStartEvent.type:\r\n this.processCombatStartEvent(event);\r\n break;\r\n case PlayerRespawnEvent.type:\r\n this.processPlayerRespawnEvent(event);\r\n break;\r\n case EnemyRespawnEvent.type:\r\n this.processEnemyRespawnEvent(event);\r\n break;\r\n case AutoAttackEvent.type:\r\n this.processAutoAttackEvent(event);\r\n break;\r\n case ConsumableTickEvent.type:\r\n this.processConsumableTickEvent(event);\r\n break;\r\n case DamageOverTimeEvent.type:\r\n this.processDamageOverTimeTickEvent(event);\r\n break;\r\n case CheckBuffExpirationEvent.type:\r\n this.processCheckBuffExpirationEvent(event);\r\n break;\r\n case RegenTickEvent.type:\r\n this.processRegenTickEvent(event);\r\n break;\r\n case StunExpirationEvent.type:\r\n this.processStunExpirationEvent(event);\r\n break;\r\n case BlindExpirationEvent.type:\r\n this.processBlindExpirationEvent(event);\r\n break;\r\n case SilenceExpirationEvent.type:\r\n this.processSilenceExpirationEvent(event);\r\n break;\r\n case CurseExpirationEvent.type:\r\n this.processCurseExpirationEvent(event);\r\n break;\r\n case WeakenExpirationEvent.type:\r\n this.processWeakenExpirationEvent(event);\r\n break;\r\n case FuryExpirationEvent.type:\r\n this.processFuryExpirationEvent(event);\r\n break;\r\n case EnrageTickEvent.type:\r\n this.processEnrageTickEvent(event);\r\n break;\r\n case AbilityCastEndEvent.type:\r\n this.tryUseAbility(event.source, event.ability);\r\n break;\r\n case AwaitCooldownEvent.type:\r\n // console.log(\"Await CD \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(event.source);\r\n break;\r\n case CooldownReadyEvent.type:\r\n // Only used to check triggers\r\n break;\r\n }\r\n\r\n this.checkTriggers();\r\n }\r\n\r\n processCombatStartEvent(event) {\r\n // console.log(\"Combat Start \" + (this.simulationTime / 1000000000));\r\n for (let i = 0; i < this.players.length; i++) {\r\n if (event.time == 0) { // First combat start event\r\n this.players[i].generatePermanentBuffs();\r\n }\r\n this.players[i].reset(this.simulationTime);\r\n }\r\n let regenTickEvent = new RegenTickEvent(this.simulationTime + REGEN_TICK_INTERVAL);\r\n this.eventQueue.addEvent(regenTickEvent);\r\n\r\n this.startNewEncounter();\r\n }\r\n\r\n processPlayerRespawnEvent(event) {\r\n // console.log(\"Player \" + event.hrid + \" respawn at \" + + (this.simulationTime / 1000000000));\r\n let respawningPlayer = this.players.find(player => player.hrid === event.hrid);\r\n respawningPlayer.combatDetails.currentHitpoints = respawningPlayer.combatDetails.maxHitpoints;\r\n respawningPlayer.combatDetails.currentManapoints = respawningPlayer.combatDetails.maxManapoints;\r\n respawningPlayer.clearBuffs();\r\n respawningPlayer.clearCCs();\r\n if (this.allPlayersDead) {\r\n this.allPlayersDead = false;\r\n this.startAttacks();\r\n } else {\r\n this.addNextAttackEvent(respawningPlayer);\r\n }\r\n }\r\n\r\n processEnemyRespawnEvent(event) {\r\n this.startNewEncounter();\r\n }\r\n\r\n startNewEncounter() {\r\n if (this.allPlayersDead) {\r\n this.allPlayersDead = false;\r\n this.zone.failWave();\r\n }\r\n\r\n if (!this.zone.isDungeon) {\r\n this.enemies = this.zone.getRandomEncounter();\r\n } else {\r\n this.enemies = this.zone.getNextWave();\r\n this.simResult.updateTimeSpentAlive(\"#\" + (this.zone.encountersKilled - 1).toString(), true, this.simulationTime);\r\n let currentDungeonCount = this.zone.dungeonsCompleted;\r\n // console.log('wave at #' + (this.zone.encountersKilled - 1) +' completed:' + this.zone.dungeonsCompleted + ' failed:'+ this.zone.dungeonsFailed + ' temp:'+ this.tempDungeonCount);\r\n if (currentDungeonCount > this.tempDungeonCount) {\r\n this.tempDungeonCount = currentDungeonCount;\r\n for (let i = 0; i < this.players.length; i++) {\r\n this.players[i].combatDetails.currentHitpoints = this.players[i].combatDetails.maxHitpoints;\r\n this.players[i].combatDetails.currentManapoints = this.players[i].combatDetails.maxManapoints;\r\n // this.simResult.playerRanOutOfMana[this.players[i].hrid] = false;\r\n }\r\n }\r\n }\r\n\r\n this.enemies.forEach((enemy) => {\r\n enemy.reset(this.simulationTime);\r\n this.simResult.updateTimeSpentAlive(enemy.hrid, true, this.simulationTime);\r\n //console.log(enemy.hrid, \"spawned\");\r\n });\r\n\r\n this.eventQueue.clearEventsOfType(EnrageTickEvent.type);\r\n let enrageTickEvent = new EnrageTickEvent(this.simulationTime + ENRAGE_TICK_INTERVAL, ENRAGE_TICK_INTERVAL);\r\n this.eventQueue.addEvent(enrageTickEvent);\r\n this.enrageBeginTime = this.simulationTime;\r\n\r\n this.eventQueue.clearEventsOfType(AbilityCastEndEvent.type);\r\n\r\n this.startAttacks();\r\n }\r\n\r\n startAttacks() {\r\n let units = [...this.players];\r\n if (this.enemies) {\r\n units.push(...this.enemies);\r\n }\r\n\r\n for (const unit of units) {\r\n if (unit.combatDetails.currentHitpoints <= 0) {\r\n continue;\r\n }\r\n\r\n /*-if (unit.isPlayer) {\r\n // console.log(\"Start Attacks \" + (this.simulationTime / 1000000000));\r\n }*/\r\n this.addNextAttackEvent(unit);\r\n }\r\n }\r\n\r\n checkParry(targets) {\r\n let parryUnits = targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0 && unit.combatDetails.combatStats.parry > 0);\r\n if (parryUnits.length <= 0) {\r\n return undefined;\r\n }\r\n let randomIndex = Math.floor(Math.random() * parryUnits.length);\r\n if (parryUnits[randomIndex].combatDetails.combatStats.parry > Math.random()) {\r\n return parryUnits[randomIndex];\r\n }\r\n return undefined;\r\n }\r\n\r\n processAutoAttackEvent(event) {\r\n // console.log(\"source:\", event.source.hrid);\r\n // console.log(\"aa \" + (this.simulationTime / 1000000000));\r\n\r\n let targets = event.source.isPlayer ? this.enemies : this.players;\r\n\r\n if (!targets) {\r\n return;\r\n }\r\n\r\n const aliveTargets = targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0);\r\n\r\n for (let i = 0; i < aliveTargets.length; i++) {\r\n let target = aliveTargets[i];\r\n if (!event.source.isPlayer && aliveTargets.length > 1) {\r\n let cumulativeThreat = 0;\r\n let cumulativeRanges = [];\r\n aliveTargets.forEach(player => {\r\n let playerThreat = player.combatDetails.combatStats.threat;\r\n cumulativeThreat += playerThreat;\r\n cumulativeRanges.push({\r\n player: player,\r\n rangeStart: cumulativeThreat - playerThreat,\r\n rangeEnd: cumulativeThreat\r\n });\r\n });\r\n let randomValueHit = Math.random() * cumulativeThreat;\r\n target = cumulativeRanges.find(range => randomValueHit >= range.rangeStart && randomValueHit < range.rangeEnd).player;\r\n }\r\n let source = event.source;\r\n\r\n let parryTarget = this.checkParry(targets);\r\n if (parryTarget) {\r\n target = source;\r\n source = parryTarget;\r\n }\r\n\r\n let attackResult = CombatUtilities.processAttack(source, target);\r\n if (this.zone.isDungeon && target.isPlayer && attackResult.didHit && attackResult.damageDone > 0) {\r\n const log = this.generateCombatLog(source, \"autoAttack\", target, attackResult);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n let mayhem = source.combatDetails.combatStats.mayhem > Math.random();\r\n\r\n if (attackResult.didHit && source.combatDetails.combatStats.curse > 0) {\r\n const curseExpireTime = 15000000000;\r\n let currentCurseEvent = this.eventQueue.getMatching((event) => event.type == CurseExpirationEvent.type && event.source == target);\r\n let currentCurseAmount = 0;\r\n if (currentCurseEvent) currentCurseAmount = currentCurseEvent.curseAmount;\r\n this.eventQueue.clearMatching((event) => event.type == CurseExpirationEvent.type && event.source == target);\r\n\r\n let curseExpirationEvent = new CurseExpirationEvent(this.simulationTime + curseExpireTime, currentCurseAmount, target);\r\n const curseBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/curse\",\r\n \"typeHrid\": \"/buff_types/damage_taken\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": source.combatDetails.combatStats.curse * curseExpirationEvent.curseAmount,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": curseExpireTime\r\n };\r\n target.addBuff(curseBuff);\r\n this.eventQueue.addEvent(curseExpirationEvent);\r\n }\r\n\r\n if (source.combatDetails.combatStats.fury > 0) {\r\n let currentFuryEvent = this.eventQueue.getMatching((event) => event.type == FuryExpirationEvent.type && event.source == source);\r\n this.eventQueue.clearMatching((event) => event.type == FuryExpirationEvent.type && event.source == source);\r\n\r\n const furyExpireTime = 15000000000;\r\n const maxFuryStack = 5;\r\n\r\n let furyAmount = 0;\r\n if (currentFuryEvent) furyAmount = currentFuryEvent.furyAmount;\r\n\r\n if (attackResult.didHit) {\r\n furyAmount = Math.min(furyAmount + 1, maxFuryStack);\r\n } else {\r\n furyAmount = Math.floor(furyAmount / 2);\r\n }\r\n\r\n const furyAccuracyBuf = {\r\n \"uniqueHrid\": \"/buff_uniques/fury_accuracy\",\r\n \"typeHrid\": \"/buff_types/fury_accuracy\",\r\n \"ratioBoost\": furyAmount * source.combatDetails.combatStats.fury,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": furyExpireTime\r\n };\r\n const furyDamageBuf = {\r\n \"uniqueHrid\": \"/buff_uniques/fury_damage\",\r\n \"typeHrid\": \"/buff_types/fury_damage\",\r\n \"ratioBoost\": furyAmount * source.combatDetails.combatStats.fury,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": furyExpireTime\r\n };\r\n\r\n if (furyAmount > 0) {\r\n let furyExpirationEvent = new FuryExpirationEvent(this.simulationTime + furyExpireTime, furyAmount, source);\r\n this.eventQueue.addEvent(furyExpirationEvent);\r\n\r\n source.addBuff(furyAccuracyBuf, this.simulationTime);\r\n source.addBuff(furyDamageBuf, this.simulationTime);\r\n }\r\n else {\r\n source.removeBuff(furyAccuracyBuf);\r\n source.removeBuff(furyDamageBuf);\r\n }\r\n }\r\n\r\n if (target.combatDetails.combatStats.weaken > 0) {\r\n const weakenExpireTime = 15000000000;\r\n let currentWeakenEvent = this.eventQueue.getMatching((event) => event.type == WeakenExpirationEvent.type && event.source == source);\r\n let weakenAmount = 0;\r\n if (currentWeakenEvent)\r\n weakenAmount = currentWeakenEvent.weakenAmount;\r\n this.eventQueue.clearMatching((event) => event.type == WeakenExpirationEvent.type && event.source == source);\r\n let weakenExpirationEvent = new WeakenExpirationEvent(this.simulationTime + 15000000000, weakenAmount, source);\r\n const weakenBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/weaken\",\r\n \"typeHrid\": \"/buff_types/damage\",\r\n \"ratioBoost\": -1 * target.combatDetails.combatStats.weaken * weakenExpirationEvent.weakenAmount,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": weakenExpireTime\r\n };\r\n source.addBuff(weakenBuff);\r\n this.eventQueue.addEvent(weakenExpirationEvent);\r\n }\r\n\r\n if (!mayhem || (mayhem && attackResult.didHit) || (mayhem && i == (aliveTargets.length - 1))) {\r\n let attackType = \"autoAttack\";\r\n if (parryTarget) attackType = \"parry\";\r\n this.simResult.addAttack(\r\n source,\r\n target,\r\n \"autoAttack\",\r\n attackResult.didHit ? attackResult.damageDone : \"miss\"\r\n );\r\n }\r\n\r\n if (attackResult.lifeStealHeal > 0) {\r\n this.simResult.addHitpointsGained(source, \"lifesteal\", attackResult.lifeStealHeal);\r\n }\r\n\r\n if (attackResult.manaLeechMana > 0) {\r\n this.simResult.addManapointsGained(source, \"manaLeech\", attackResult.manaLeechMana);\r\n }\r\n\r\n if (attackResult.thornDamageDone > 0) {\r\n this.simResult.addAttack(target, source, attackResult.thornType, attackResult.thornDamageDone);\r\n }\r\n if (this.zone.isDungeon && attackResult.thornDamageDone > 0 && source.isPlayer) {\r\n const log = this.buildCombatLog(target, attackResult.thornType, source, attackResult.thornDamageDone);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (target.combatDetails.combatStats.retaliation > 0) {\r\n this.simResult.addAttack(target, source, \"retaliation\", attackResult.retaliationDamageDone > 0?attackResult.retaliationDamageDone:\"miss\");\r\n }\r\n if (this.zone.isDungeon && attackResult.retaliationDamageDone > 0 && source.isPlayer) {\r\n const log = this.buildCombatLog(target, \"retaliation\", source, attackResult.retaliationDamageDone);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (target.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(target);\r\n this.simResult.addDeath(target);\r\n if (!target.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(target.hrid, false, this.simulationTime);\r\n }\r\n // console.log(target.hrid, \"died\");\r\n }\r\n\r\n // Could die from reflect damage\r\n if (source.combatDetails.currentHitpoints == 0 && \r\n (attackResult.thornDamageDone != 0 || attackResult.retaliationDamageDone != 0)\r\n ) {\r\n this.eventQueue.clearEventsForUnit(source);\r\n this.simResult.addDeath(source);\r\n if (!source.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(source.hrid, false, this.simulationTime);\r\n }\r\n break;\r\n }\r\n\r\n if (mayhem && !attackResult.didHit) {\r\n continue;\r\n }\r\n\r\n if (!attackResult.didHit || parryTarget || source.combatDetails.combatStats.pierce <= Math.random()) {\r\n break;\r\n }\r\n }\r\n\r\n if (!this.checkEncounterEnd()) {\r\n // console.log(\"!EncounterEnd \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(event.source);\r\n }\r\n }\r\n\r\n checkEncounterEnd() {\r\n if (this.enemies) {\r\n let deadEnemies = this.enemies.filter((enemy) => enemy.combatDetails.currentHitpoints <= 0 && enemy.experienceRate == 0);\r\n if (deadEnemies.length > 0) {\r\n deadEnemies.forEach(enemy => {\r\n let aliveDuration = this.simulationTime - this.enrageBeginTime;\r\n if (aliveDuration > enemy.enrageTime) {\r\n aliveDuration = enemy.enrageTime;\r\n }\r\n enemy.experienceRate = 1.0 + aliveDuration / enemy.enrageTime;\r\n // console.log(enemy.hrid, \"alive duration\", aliveDuration, \"exp rate\", enemy.experienceRate);\r\n })\r\n }\r\n }\r\n\r\n let encounterEnded = false;\r\n\r\n if (this.enemies && !this.enemies.some((enemy) => enemy.combatDetails.currentHitpoints > 0)) {\r\n this.eventQueue.clearEventsOfType(AutoAttackEvent.type);\r\n // this.eventQueue.clearEventsOfType(AbilityCastEndEvent.type);\r\n let enemyRespawnEvent = new EnemyRespawnEvent(this.simulationTime + ENEMY_RESPAWN_INTERVAL);\r\n this.eventQueue.addEvent(enemyRespawnEvent);\r\n\r\n //calc exp before clear\r\n if (this.enemies.some(enemy => enemy.experienceRate <= 0)) {\r\n console.log(\"WARN: Some enemies have no experience rate\");\r\n }\r\n\r\n let totalExp = this.enemies.map(enemy => enemy.experience * enemy.experienceRate).reduce((a, b) => a + b, 0);\r\n this.players.forEach(player => {\r\n this.simResult.addExperienceGain(player, totalExp / this.players.length);\r\n });\r\n\r\n this.enemies = null;\r\n\r\n if (this.zone.isDungeon) {\r\n this.simResult.updateTimeSpentAlive(\"#\" + (this.zone.encountersKilled - 1).toString(), false, this.simulationTime);\r\n if (this.zone.encountersKilled > this.zone.dungeonSpawnInfo.maxWaves) {\r\n this.simResult.updateDungenonFinish(\"#1\", this.simulationTime);\r\n }\r\n }\r\n this.simResult.addEncounterEnd();\r\n // console.log(\"All enemies died\");\r\n\r\n encounterEnded = true;\r\n // console.log(\"encounter end \" + (this.simulationTime / 1000000000))\r\n }\r\n\r\n this.players.forEach(player => {\r\n if ((player.combatDetails.currentHitpoints <= 0) && !this.eventQueue.containsEventOfTypeAndHrid(PlayerRespawnEvent.type, player.hrid)) {\r\n if (!this.zone.isDungeon) {\r\n let playerRespawnEvent = new PlayerRespawnEvent(this.simulationTime + PLAYER_RESPAWN_INTERVAL, player.hrid);\r\n this.eventQueue.addEvent(playerRespawnEvent);\r\n }\r\n this.simResult.addRanOutOfManaCount(player, false, this.simulationTime);\r\n // console.log(player.hrid + \" died at \" + (this.simulationTime / 1000000000) + 'in wave #' + (this.zone.encountersKilled - 1) + ' with ememies: ' + this.enemies?.map(enemy => (enemy.hrid+\"(\"+(enemy.combatDetails.currentHitpoints*100/enemy.combatDetails.maxHitpoints).toFixed(2)+\"%)\")).join(\", \"));\r\n }\r\n });\r\n\r\n if (\r\n !this.players.some((player) => player.combatDetails.currentHitpoints > 0)\r\n ) {\r\n if (this.zone.isDungeon) {\r\n console.log(\"All Players died at wave #\" + (this.zone.encountersKilled - 1) + \" with ememies: \" + this.enemies.map(enemy => (enemy.hrid+\"(\"+(enemy.combatDetails.currentHitpoints*100/enemy.combatDetails.maxHitpoints).toFixed(2)+\"%)\")).join(\", \"));\r\n\r\n this.saveWipeLogsToSimResult(this.zone.encountersKilled - 1);\r\n // console.log(this.simResult)\r\n this.wipeLogs.index = 0;\r\n this.wipeLogs.count = 0;\r\n\r\n this.eventQueue.clear();\r\n this.enemies = null;\r\n\r\n let combatStartEvent = new CombatStartEvent(this.simulationTime + RESTART_INTERVAL);\r\n this.eventQueue.addEvent(combatStartEvent);\r\n } else {\r\n this.eventQueue.clearEventsOfType(AutoAttackEvent.type);\r\n this.eventQueue.clearEventsOfType(AbilityCastEndEvent.type);\r\n }\r\n // console.log(\"All Players died\");\r\n encounterEnded = true;\r\n this.allPlayersDead = true;\r\n }\r\n\r\n return encounterEnded;\r\n }\r\n\r\n addNextAttackEvent(source) {\r\n if (this.eventQueue.getMatching((event) => (event.type == AbilityCastEndEvent.type || event.type == AutoAttackEvent.type)&& event.source == source)) {\r\n return;\r\n }\r\n\r\n let target;\r\n let friendlies;\r\n let enemies;\r\n if (source.isPlayer) {\r\n target = CombatUtilities.getTarget(this.enemies);\r\n friendlies = this.players;\r\n enemies = this.enemies;\r\n } else {\r\n target = CombatUtilities.getTarget(this.players);\r\n friendlies = this.enemies;\r\n enemies = this.players;\r\n }\r\n\r\n let usedAbility = false;\r\n let skipNextAbility = false; \r\n\r\n source.abilities\r\n .filter((ability) => ability != null)\r\n .forEach((ability) => {\r\n if (!usedAbility && !skipNextAbility && ability.shouldTrigger(this.simulationTime, source, target, friendlies, enemies)) {\r\n if (!this.canUseAbility(source, ability, true)) {\r\n skipNextAbility = true;\r\n }\r\n\r\n if (!skipNextAbility) {\r\n let castDuration = ability.castDuration;\r\n castDuration /= (1 + source.combatDetails.combatStats.castSpeed)\r\n let abilityCastEndEvent = new AbilityCastEndEvent(this.simulationTime + castDuration, source, ability);\r\n this.eventQueue.addEvent(abilityCastEndEvent);\r\n /*-if (source.isPlayer) {\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n // console.log((this.simulationTime / 1000000000) + \" Casting \" + ability.hrid + \" Cast time \" + (castDuration / 1e9) + \" Off CD at \" + ((this.simulationTime + cooldownDuration + castDuration) / 1e9) + \" CD \" + ((cooldownDuration) / 1e9));\r\n }*/\r\n usedAbility = true;\r\n }\r\n }\r\n });\r\n\r\n if (usedAbility) {\r\n source.isOutOfMana = false;\r\n return;\r\n }\r\n\r\n if (!enemies) {\r\n return;\r\n }\r\n\r\n if (!source.isBlinded) {\r\n let autoAttackEvent = new AutoAttackEvent(\r\n this.simulationTime + source.combatDetails.combatStats.attackInterval,\r\n source\r\n );\r\n /*-if (source.isPlayer) {\r\n // console.log(\"next attack \" + ((this.simulationTime + source.combatDetails.combatStats.attackInterval) / 1e9))\r\n }*/\r\n this.eventQueue.addEvent(autoAttackEvent);\r\n } else {\r\n source.isOutOfMana = true;\r\n }\r\n }\r\n\r\n processConsumableTickEvent(event) {\r\n if (event.consumable.hitpointRestore > 0) {\r\n let tickValue = CombatUtilities.calculateTickValue(\r\n event.consumable.hitpointRestore,\r\n event.totalTicks,\r\n event.currentTick\r\n );\r\n let hitpointsAdded = event.source.addHitpoints(tickValue);\r\n this.simResult.addHitpointsGained(event.source, event.consumable.hrid, hitpointsAdded);\r\n // console.log(\"Added hitpoints:\", hitpointsAdded);\r\n }\r\n\r\n if (event.consumable.manapointRestore > 0) {\r\n let tickValue = CombatUtilities.calculateTickValue(\r\n event.consumable.manapointRestore,\r\n event.totalTicks,\r\n event.currentTick\r\n );\r\n let manapointsAdded = event.source.addManapoints(tickValue);\r\n this.simResult.addManapointsGained(event.source, event.consumable.hrid, manapointsAdded);\r\n // console.log(\"Added manapoints:\", manapointsAdded);\r\n\r\n // when oom check ability trigger\r\n if (event.source.isOutOfMana) {\r\n let awaitCooldownEvent = new AwaitCooldownEvent(\r\n this.simulationTime,\r\n event.source\r\n );\r\n this.eventQueue.addEvent(awaitCooldownEvent);\r\n }\r\n }\r\n\r\n if (event.currentTick < event.totalTicks) {\r\n let consumableTickEvent = new ConsumableTickEvent(\r\n this.simulationTime + HOT_TICK_INTERVAL,\r\n event.source,\r\n event.consumable,\r\n event.totalTicks,\r\n event.currentTick + 1\r\n );\r\n this.eventQueue.addEvent(consumableTickEvent);\r\n }\r\n }\r\n\r\n processDamageOverTimeTickEvent(event) {\r\n let tickDamage = CombatUtilities.calculateTickValue(event.damage, event.totalTicks, event.currentTick);\r\n let damage = Math.min(tickDamage, event.target.combatDetails.currentHitpoints);\r\n\r\n event.target.combatDetails.currentHitpoints -= damage;\r\n this.simResult.addAttack(event.sourceRef, event.target, \"damageOverTime\", damage);\r\n\r\n const log = this.buildCombatLog(\"\", \"damageOverTime\", event.target, damage);\r\n this.addToWipeLogs(log);\r\n\r\n // console.log(event.target.hrid, \"bleed for\", damage);\r\n\r\n if (event.currentTick < event.totalTicks) {\r\n let damageOverTimeTickEvent = new DamageOverTimeEvent(\r\n this.simulationTime + DOT_TICK_INTERVAL,\r\n event.sourceRef,\r\n event.target,\r\n event.damage,\r\n event.totalTicks,\r\n event.currentTick + 1,\r\n event.combatStyleHrid\r\n );\r\n this.eventQueue.addEvent(damageOverTimeTickEvent);\r\n }\r\n\r\n if (event.target.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(event.target);\r\n this.simResult.addDeath(event.target);\r\n if (!event.target.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(event.target.hrid, false, this.simulationTime);\r\n }\r\n }\r\n\r\n this.checkEncounterEnd();\r\n }\r\n\r\n processRegenTickEvent(event) {\r\n let units = [...this.players];\r\n\r\n // regen of emeny always set to 0, ingore the proc time\r\n // if (this.enemies) {\r\n // units.push(...this.enemies);\r\n // }\r\n\r\n for (const unit of units) {\r\n if (unit.combatDetails.currentHitpoints <= 0) {\r\n continue;\r\n }\r\n\r\n let hitpointRegen = Math.floor(unit.combatDetails.maxHitpoints * unit.combatDetails.combatStats.hpRegenPer10);\r\n let hitpointsAdded = unit.addHitpoints(hitpointRegen);\r\n this.simResult.addHitpointsGained(unit, \"regen\", hitpointsAdded);\r\n // console.log(\"Added hitpoints:\", hitpointsAdded);\r\n\r\n let manapointRegen = Math.floor(unit.combatDetails.maxManapoints * unit.combatDetails.combatStats.mpRegenPer10);\r\n let manapointsAdded = unit.addManapoints(manapointRegen);\r\n this.simResult.addManapointsGained(unit, \"regen\", manapointsAdded);\r\n // console.log(\"Added manapoints:\", manapointsAdded);\r\n\r\n // when oom check ability trigger\r\n if (unit.isOutOfMana) {\r\n let awaitCooldownEvent = new AwaitCooldownEvent(\r\n this.simulationTime,\r\n unit\r\n );\r\n this.eventQueue.addEvent(awaitCooldownEvent);\r\n }\r\n }\r\n\r\n let regenTickEvent = new RegenTickEvent(this.simulationTime + REGEN_TICK_INTERVAL);\r\n this.eventQueue.addEvent(regenTickEvent);\r\n }\r\n\r\n processCheckBuffExpirationEvent(event) {\r\n event.source.removeExpiredBuffs(this.simulationTime);\r\n }\r\n\r\n processStunExpirationEvent(event) {\r\n event.source.isStunned = false;\r\n // console.log(\"Stun \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(event.source);\r\n }\r\n\r\n processBlindExpirationEvent(event) {\r\n event.source.isBlinded = false;\r\n this.addNextAttackEvent(event.source);\r\n }\r\n\r\n processSilenceExpirationEvent(event) {\r\n event.source.isSilenced = false;\r\n }\r\n\r\n processCurseExpirationEvent(event) {\r\n event.source.removeExpiredBuffs(this.simulationTime);\r\n }\r\n\r\n processWeakenExpirationEvent(event) {\r\n event.source.removeExpiredBuffs(this.simulationTime);\r\n }\r\n\r\n processFuryExpirationEvent(event) {\r\n event.source.removeExpiredBuffs(this.simulationTime);\r\n console.log(\"Fury Timeout\");\r\n }\r\n\r\n processEnrageTickEvent(event) {\r\n if (!this.enemies) return;\r\n const maxEnrageStack = 10;\r\n this.enemies.filter((enemy) => enemy.combatDetails.currentHitpoints > 0).forEach((enemy) => {\r\n let nowStack = Math.min(maxEnrageStack, Math.floor(event.encounterTime / enemy.enrageTime));\r\n\r\n if (nowStack <= 0) {\r\n return;\r\n }\r\n\r\n console.log(enemy.hrid, nowStack, \" stack Enrage at \", (event.encounterTime / ONE_SECOND));\r\n\r\n const enrageDamageBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/enrage_damage\",\r\n \"typeHrid\": \"/buff_types/damage\",\r\n \"ratioBoost\": nowStack * 0.1,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": ENRAGE_TICK_INTERVAL\r\n };\r\n const enrageAccuracyBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/enrage_accuracy\",\r\n \"typeHrid\": \"/buff_types/accuracy\",\r\n \"ratioBoost\": nowStack * 0.1,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": ENRAGE_TICK_INTERVAL\r\n };\r\n enemy.addBuff(enrageDamageBuff);\r\n enemy.addBuff(enrageAccuracyBuff);\r\n \r\n this.simResult.maxEnrageStack = Math.max(this.simResult.maxEnrageStack, nowStack);\r\n });\r\n\r\n let enrageTickEvent = new EnrageTickEvent(this.simulationTime + ENRAGE_TICK_INTERVAL, event.encounterTime + ENRAGE_TICK_INTERVAL);\r\n this.eventQueue.addEvent(enrageTickEvent);\r\n }\r\n\r\n checkTriggers() {\r\n let triggeredSomething;\r\n\r\n do {\r\n triggeredSomething = false;\r\n\r\n this.players\r\n .filter((player) => player.combatDetails.currentHitpoints > 0)\r\n .forEach((player) => {\r\n if (this.checkTriggersForUnit(player, this.players, this.enemies)) {\r\n triggeredSomething = true;\r\n }\r\n });\r\n\r\n if (this.enemies) {\r\n this.enemies\r\n .filter((enemy) => enemy.combatDetails.currentHitpoints > 0)\r\n .forEach((enemy) => {\r\n if (this.checkTriggersForUnit(enemy, this.enemies, this.players)) {\r\n triggeredSomething = true;\r\n }\r\n });\r\n }\r\n } while (triggeredSomething);\r\n }\r\n\r\n checkTriggersForUnit(unit, friendlies, enemies) {\r\n if (unit.combatDetails.currentHitpoints <= 0) {\r\n throw new Error(\"Checking triggers for a dead unit\");\r\n }\r\n\r\n let triggeredSomething = false;\r\n let target = CombatUtilities.getTarget(enemies);\r\n\r\n for (const food of unit.food) {\r\n if (food && food.shouldTrigger(this.simulationTime, unit, target, friendlies, enemies)) {\r\n let result = this.tryUseConsumable(unit, food);\r\n if (result) {\r\n triggeredSomething = true;\r\n }\r\n }\r\n }\r\n\r\n for (const drink of unit.drinks) {\r\n if (drink && drink.shouldTrigger(this.simulationTime, unit, target, friendlies, enemies)) {\r\n let result = this.tryUseConsumable(unit, drink);\r\n if (result) {\r\n triggeredSomething = true;\r\n }\r\n }\r\n }\r\n\r\n return triggeredSomething;\r\n }\r\n\r\n tryUseConsumable(source, consumable) {\r\n // console.log(\"Consuming:\", consumable);\r\n\r\n if (source.combatDetails.currentHitpoints <= 0) {\r\n return false;\r\n }\r\n\r\n consumable.lastUsed = this.simulationTime;\r\n let consumeCooldown = consumable.cooldownDuration;\r\n if (source.combatDetails.combatStats.drinkConcentration > 0 && consumable.catagoryHrid.includes(\"drink\")) {\r\n consumeCooldown = consumeCooldown / (1 + source.combatDetails.combatStats.drinkConcentration);\r\n } else if (source.combatDetails.combatStats.foodHaste > 0 && consumable.catagoryHrid.includes(\"food\")) {\r\n consumeCooldown = consumeCooldown / (1 + source.combatDetails.combatStats.foodHaste);\r\n }\r\n let cooldownReadyEvent = new CooldownReadyEvent(this.simulationTime + consumeCooldown);\r\n this.eventQueue.addEvent(cooldownReadyEvent);\r\n\r\n this.simResult.addConsumableUse(source, consumable);\r\n\r\n if (consumable.recoveryDuration == 0) {\r\n if (consumable.hitpointRestore > 0) {\r\n let hitpointsAdded = source.addHitpoints(consumable.hitpointRestore);\r\n this.simResult.addHitpointsGained(source, consumable.hrid, hitpointsAdded);\r\n // console.log(\"Added hitpoints:\", hitpointsAdded);\r\n }\r\n\r\n if (consumable.manapointRestore > 0) {\r\n let manapointsAdded = source.addManapoints(consumable.manapointRestore);\r\n this.simResult.addManapointsGained(source, consumable.hrid, manapointsAdded);\r\n // console.log(\"Added manapoints:\", manapointsAdded);\r\n\r\n // when oom check ability trigger\r\n if (source.isOutOfMana) {\r\n let awaitCooldownEvent = new AwaitCooldownEvent(\r\n this.simulationTime,\r\n source\r\n );\r\n this.eventQueue.addEvent(awaitCooldownEvent);\r\n }\r\n }\r\n } else {\r\n let consumableTickEvent = new ConsumableTickEvent(\r\n this.simulationTime + HOT_TICK_INTERVAL,\r\n source,\r\n consumable,\r\n consumable.recoveryDuration / HOT_TICK_INTERVAL,\r\n 1\r\n );\r\n this.eventQueue.addEvent(consumableTickEvent);\r\n }\r\n\r\n for (const buff of consumable.buffs) {\r\n let currentBuff = structuredClone(buff);\r\n if (source.combatDetails.combatStats.drinkConcentration > 0 && consumable.catagoryHrid.includes(\"drink\")) {\r\n currentBuff.ratioBoost *= (1 + source.combatDetails.combatStats.drinkConcentration);\r\n currentBuff.flatBoost *= (1 + source.combatDetails.combatStats.drinkConcentration);\r\n currentBuff.duration = currentBuff.duration / (1 + source.combatDetails.combatStats.drinkConcentration);\r\n }\r\n source.addBuff(currentBuff, this.simulationTime);\r\n // console.log(\"Added buff:\", currentBuff);\r\n let checkBuffExpirationEvent = new CheckBuffExpirationEvent(this.simulationTime + currentBuff.duration, source);\r\n this.eventQueue.addEvent(checkBuffExpirationEvent);\r\n }\r\n\r\n return true;\r\n }\r\n\r\n canUseAbility(source, ability, oomCheck) {\r\n if (source.combatDetails.currentHitpoints <= 0) {\r\n return false;\r\n }\r\n\r\n if (source.combatDetails.currentManapoints < ability.manaCost) {\r\n if (source.isPlayer && oomCheck) {\r\n // if (this.simResult.playerRanOutOfMana[source.hrid] == false) {\r\n // console.log(source.hrid + \" ran out of mana\" + ' at wave #' + (this.zone.encountersKilled - 1) + ' at time ' + this.simulationTime / 1000000000 + 's');\r\n // }\r\n this.simResult.addRanOutOfManaCount(source, true, this.simulationTime);\r\n }\r\n return false;\r\n }\r\n if (source.isPlayer && oomCheck) {\r\n this.simResult.addRanOutOfManaCount(source, false, this.simulationTime);\r\n }\r\n return true;\r\n }\r\n\r\n tryUseAbility(source, ability) {\r\n\r\n if (!this.canUseAbility(source, ability, true)) {\r\n // console.log(\"Falseeeeeee\");\r\n return false;\r\n }\r\n\r\n // console.log(\"Casting:\", ability);\r\n\r\n if (source.isPlayer) {\r\n if (source.abilityManaCosts.has(ability.hrid)) {\r\n source.abilityManaCosts.set(ability.hrid, source.abilityManaCosts.get(ability.hrid) + ability.manaCost);\r\n } else {\r\n source.abilityManaCosts.set(ability.hrid, ability.manaCost);\r\n }\r\n }\r\n\r\n source.combatDetails.currentManapoints -= ability.manaCost;\r\n\r\n ability.lastUsed = this.simulationTime;\r\n\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n\r\n /*-if (source.isPlayer) {\r\n let castDuration = ability.castDuration;\r\n castDuration /= (1 + source.combatDetails.combatStats.castSpeed)\r\n // console.log((this.simulationTime / 1000000000) + \" Used ability \" + ability.hrid + \" Cast time \" + (castDuration / 1e9));\r\n }*/\r\n\r\n let todoAbilities = [ability];\r\n\r\n if (source.combatDetails.combatStats.blaze > 0 && Math.random() < source.combatDetails.combatStats.blaze) {\r\n todoAbilities.push(new Ability(\"blaze\"));\r\n }\r\n\r\n if (source.combatDetails.combatStats.bloom > 0 && Math.random() < source.combatDetails.combatStats.bloom) {\r\n todoAbilities.push(new Ability(\"bloom\"));\r\n }\r\n\r\n for (const todoAbility of todoAbilities) {\r\n for (const abilityEffect of todoAbility.abilityEffects) {\r\n switch (abilityEffect.effectType) {\r\n case \"/ability_effect_types/buff\":\r\n this.processAbilityBuffEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/damage\":\r\n this.processAbilityDamageEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/heal\":\r\n this.processAbilityHealEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/spend_hp\":\r\n this.processAbilitySpendHpEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/revive\":\r\n this.processAbilityReviveEffect(source, todoAbility, abilityEffect);\r\n break;\r\n case \"/ability_effect_types/promote\":\r\n this.eventQueue.clearEventsForUnit(source);\r\n source = this.processAbilityPromoteEffect(source, todoAbility, abilityEffect);\r\n this.addNextAttackEvent(source);\r\n break;\r\n default:\r\n throw new Error(\"Unsupported effect type for ability: \" + todoAbility.hrid + \" effectType: \" + abilityEffect.effectType);\r\n }\r\n }\r\n }\r\n\r\n if (source.combatDetails.combatStats.ripple > 0 && Math.random() < source.combatDetails.combatStats.ripple) {\r\n let manapointsAdded = source.addManapoints(10);\r\n this.simResult.addManapointsGained(source, \"ripple\", manapointsAdded);\r\n for (const ability of source.abilities) {\r\n if (ability && ability.lastUsed) {\r\n const remainingCooldown = ability.lastUsed + ability.cooldownDuration - this.simulationTime;\r\n if (remainingCooldown > 0) {\r\n ability.lastUsed = Math.max(ability.lastUsed - ONE_SECOND * 2, this.simulationTime - ability.cooldownDuration);\r\n }\r\n }\r\n }\r\n }\r\n\r\n this.addNextAttackEvent(source);\r\n\r\n // Could die from reflect damage\r\n if (source.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(source);\r\n this.simResult.addDeath(source);\r\n if (!source.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(source.hrid, false, this.simulationTime);\r\n }\r\n }\r\n\r\n this.checkEncounterEnd();\r\n\r\n return true;\r\n }\r\n\r\n processAbilityBuffEffect(source, ability, abilityEffect) {\r\n if (abilityEffect.targetType == \"allAllies\") {\r\n let targets = source.isPlayer ? this.players : this.enemies;\r\n for (const target of targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0)) {\r\n for (const buff of abilityEffect.buffs) {\r\n if (ability.isSpecialAbility && buff.multiplierForSkillHrid && buff.multiplierPerSkillLevel > 0) {\r\n let multiplier = 1.0 + source.combatDetails[buff.multiplierForSkillHrid.split('/')[2] + 'Level'] * buff.multiplierPerSkillLevel;\r\n let currentBuff = structuredClone(buff);\r\n currentBuff.flatBoost *= multiplier;\r\n target.addBuff(currentBuff, this.simulationTime);\r\n } else {\r\n target.addBuff(buff, this.simulationTime);\r\n }\r\n let checkBuffExpirationEvent = new CheckBuffExpirationEvent(this.simulationTime + buff.duration, target);\r\n this.eventQueue.addEvent(checkBuffExpirationEvent);\r\n }\r\n }\r\n return;\r\n }\r\n\r\n if (abilityEffect.targetType != \"self\") {\r\n throw new Error(\"Unsupported target type for buff ability effect: \" + ability.hrid);\r\n }\r\n\r\n for (const buff of abilityEffect.buffs) {\r\n source.addBuff(buff, this.simulationTime);\r\n // console.log(\"Added buff:\", abilityEffect.buff);\r\n let checkBuffExpirationEvent = new CheckBuffExpirationEvent(this.simulationTime + buff.duration, source);\r\n this.eventQueue.addEvent(checkBuffExpirationEvent);\r\n }\r\n }\r\n\r\n processAbilityDamageEffect(source, ability, abilityEffect) {\r\n let targets;\r\n switch (abilityEffect.targetType) {\r\n case \"enemy\":\r\n case \"allEnemies\":\r\n targets = source.isPlayer ? this.enemies : this.players;\r\n break;\r\n default:\r\n throw new Error(\"Unsupported target type for damage ability effect: \" + ability.hrid);\r\n }\r\n\r\n if (!targets) {\r\n return;\r\n }\r\n\r\n let avoidTarget = [];\r\n\r\n let isSkipParry = false;\r\n\r\n for (let target of targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0)) {\r\n let parryTarget = undefined;\r\n if (!isSkipParry) {\r\n parryTarget = this.checkParry(targets);\r\n isSkipParry = true; // parry check only once on first target\r\n }\r\n \r\n if (parryTarget) {\r\n let tempTarget = source;\r\n let tempSource = parryTarget;\r\n\r\n let attackResult = CombatUtilities.processAttack(tempSource, tempTarget);\r\n\r\n this.simResult.addAttack(\r\n tempSource,\r\n tempTarget,\r\n \"parry\",\r\n attackResult.didHit ? attackResult.damageDone : \"miss\"\r\n );\r\n\r\n if (attackResult.lifeStealHeal > 0) {\r\n this.simResult.addHitpointsGained(tempSource, \"lifesteal\", attackResult.lifeStealHeal);\r\n }\r\n\r\n if (attackResult.manaLeechMana > 0) {\r\n this.simResult.addManapointsGained(tempSource, \"manaLeech\", attackResult.manaLeechMana);\r\n }\r\n\r\n if (attackResult.thornDamageDone > 0) {\r\n this.simResult.addAttack(tempTarget, tempSource, attackResult.thornType, attackResult.thornDamageDone);\r\n }\r\n if (tempTarget.combatDetails.combatStats.retaliation > 0) {\r\n this.simResult.addAttack(tempTarget, tempSource, \"retaliation\", attackResult.retaliationDamageDone > 0 ? attackResult.retaliationDamageDone : \"miss\");\r\n }\r\n\r\n if (tempTarget.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(tempTarget);\r\n this.simResult.addDeath(tempTarget);\r\n if (!tempTarget.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(tempTarget.hrid, false, this.simulationTime);\r\n }\r\n // console.log(tempTarget.hrid, \"died\");\r\n }\r\n\r\n // Could die from reflect damage\r\n if (tempSource.combatDetails.currentHitpoints == 0 && \r\n (attackResult.thornDamageDone != 0 || attackResult.retaliationDamageDone != 0)\r\n ) {\r\n this.eventQueue.clearEventsForUnit(tempSource);\r\n this.simResult.addDeath(tempSource);\r\n if (!tempSource.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(tempSource.hrid, false, this.simulationTime);\r\n }\r\n }\r\n } else {\r\n targets = targets.filter((unit) => unit && !avoidTarget.includes(unit.hrid) && unit.combatDetails.currentHitpoints > 0);\r\n if (!source.isPlayer && targets.length > 0 && abilityEffect.targetType == \"enemy\") {\r\n let cumulativeThreat = 0;\r\n let cumulativeRanges = [];\r\n targets.forEach(player => {\r\n let playerThreat = player.combatDetails.combatStats.threat;\r\n cumulativeThreat += playerThreat;\r\n cumulativeRanges.push({\r\n player: player,\r\n rangeStart: cumulativeThreat - playerThreat,\r\n rangeEnd: cumulativeThreat\r\n });\r\n });\r\n let randomValueHit = Math.random() * cumulativeThreat;\r\n target = cumulativeRanges.find(range => randomValueHit >= range.rangeStart && randomValueHit < range.rangeEnd).player;\r\n avoidTarget.push(target.hrid);\r\n }\r\n if (targets.length <= 0) {\r\n break;\r\n }\r\n\r\n let attackResult = CombatUtilities.processAttack(source, target, abilityEffect);\r\n\r\n if (this.zone.isDungeon && target.isPlayer && attackResult.didHit && attackResult.damageDone > 0) {\r\n const log = this.generateCombatLog(source, ability.hrid, target, attackResult);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (attackResult.hpDrain > 0) {\r\n this.simResult.addHitpointsGained(source, ability.hrid, attackResult.hpDrain);\r\n }\r\n\r\n if (attackResult.didHit && abilityEffect.buffs) {\r\n for (const buff of abilityEffect.buffs) {\r\n target.addBuff(buff, this.simulationTime);\r\n let checkBuffExpirationEvent = new CheckBuffExpirationEvent(\r\n this.simulationTime + buff.duration,\r\n target\r\n );\r\n this.eventQueue.addEvent(checkBuffExpirationEvent);\r\n }\r\n }\r\n\r\n if (abilityEffect.damageOverTimeRatio > 0 && attackResult.damageDone > 0) {\r\n let damageOverTimeEvent = new DamageOverTimeEvent(\r\n this.simulationTime + DOT_TICK_INTERVAL,\r\n source,\r\n target,\r\n attackResult.damageDone * abilityEffect.damageOverTimeRatio,\r\n abilityEffect.damageOverTimeDuration / DOT_TICK_INTERVAL,\r\n 1, abilityEffect.combatStyleHrid\r\n );\r\n this.eventQueue.addEvent(damageOverTimeEvent);\r\n }\r\n\r\n if (attackResult.didHit && abilityEffect.stunChance > 0 && Math.random() < (abilityEffect.stunChance * 100 / (100 + target.combatDetails.combatStats.tenacity))) {\r\n target.isStunned = true;\r\n target.stunExpireTime = this.simulationTime + abilityEffect.stunDuration;\r\n this.eventQueue.clearMatching((event) => (event.type == AutoAttackEvent.type || event.type == AbilityCastEndEvent.type || event.type == StunExpirationEvent.type) && event.source == target);\r\n let stunExpirationEvent = new StunExpirationEvent(target.stunExpireTime, target);\r\n this.eventQueue.addEvent(stunExpirationEvent);\r\n }\r\n\r\n if (attackResult.didHit && abilityEffect.blindChance > 0 && Math.random() < (abilityEffect.blindChance * 100 / (100 + target.combatDetails.combatStats.tenacity))) {\r\n target.isBlinded = true;\r\n target.blindExpireTime = this.simulationTime + abilityEffect.blindDuration;\r\n this.eventQueue.clearMatching((event) => event.type == BlindExpirationEvent.type && event.source == target)\r\n if (this.eventQueue.clearMatching((event) => event.type == AutoAttackEvent.type && event.source == target)) {\r\n // console.log(\"Blind \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(target);\r\n }\r\n let blindExpirationEvent = new BlindExpirationEvent(target.blindExpireTime, target);\r\n this.eventQueue.addEvent(blindExpirationEvent);\r\n }\r\n\r\n if (attackResult.didHit && abilityEffect.silenceChance > 0 && Math.random() < (abilityEffect.silenceChance * 100 / (100 + target.combatDetails.combatStats.tenacity))) {\r\n target.isSilenced = true;\r\n target.silenceExpireTime = this.simulationTime + abilityEffect.silenceDuration;\r\n this.eventQueue.clearMatching((event) => event.type == SilenceExpirationEvent.type && event.source == target)\r\n if (this.eventQueue.clearMatching((event) => event.type == AbilityCastEndEvent.type && event.source == target)) {\r\n // console.log(\"Silence \" + (this.simulationTime / 1000000000));\r\n this.addNextAttackEvent(target);\r\n }\r\n let silenceExpirationEvent = new SilenceExpirationEvent(target.silenceExpireTime, target);\r\n this.eventQueue.addEvent(silenceExpirationEvent);\r\n }\r\n\r\n if (attackResult.didHit && source.combatDetails.combatStats.curse > 0 && Math.random() < (100 / (100 + target.combatDetails.combatStats.tenacity))) {\r\n const curseExpireTime = 15000000000;\r\n let currentCurseEvent = this.eventQueue.getMatching((event) => event.type == CurseExpirationEvent.type && event.source == target);\r\n let currentCurseAmount = 0;\r\n if (currentCurseEvent) currentCurseAmount = currentCurseEvent.curseAmount;\r\n this.eventQueue.clearMatching((event) => event.type == CurseExpirationEvent.type && event.source == target);\r\n\r\n let curseExpirationEvent = new CurseExpirationEvent(this.simulationTime + curseExpireTime, currentCurseAmount, target);\r\n const curseBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/curse\",\r\n \"typeHrid\": \"/buff_types/damage_taken\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": source.combatDetails.combatStats.curse * curseExpirationEvent.curseAmount,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": curseExpireTime\r\n };\r\n target.addBuff(curseBuff);\r\n this.eventQueue.addEvent(curseExpirationEvent);\r\n }\r\n\r\n if (target.combatDetails.combatStats.weaken > 0) {\r\n const weakenExpireTime = 15000000000;\r\n source.weakenExpireTime = this.simulationTime + weakenExpireTime;\r\n let currentWeakenEvent = this.eventQueue.getMatching((event) => event.type == WeakenExpirationEvent.type && event.source == source);\r\n let weakenAmount = 0;\r\n if (currentWeakenEvent)\r\n weakenAmount = currentWeakenEvent.weakenAmount;\r\n this.eventQueue.clearMatching((event) => event.type == WeakenExpirationEvent.type && event.source == source);\r\n let weakenExpirationEvent = new WeakenExpirationEvent(this.simulationTime + weakenExpireTime, weakenAmount, source);\r\n const weakenBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/weaken\",\r\n \"typeHrid\": \"/buff_types/damage\",\r\n \"ratioBoost\": -1 * target.combatDetails.combatStats.weaken * weakenExpirationEvent.weakenAmount,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": weakenExpireTime\r\n };\r\n source.addBuff(weakenBuff);\r\n this.eventQueue.addEvent(weakenExpirationEvent);\r\n }\r\n\r\n this.simResult.addAttack(\r\n source,\r\n target,\r\n ability.hrid,\r\n attackResult.didHit ? attackResult.damageDone : \"miss\"\r\n );\r\n\r\n if (attackResult.thornDamageDone > 0) {\r\n this.simResult.addAttack(target, source, attackResult.thornType, attackResult.thornDamageDone);\r\n }\r\n if (this.zone.isDungeon && attackResult.thornDamageDone > 0 && source.isPlayer) {\r\n const log = this.buildCombatLog(target, attackResult.thornType, source, attackResult.thornDamageDone);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (target.combatDetails.combatStats.retaliation > 0) {\r\n this.simResult.addAttack(target, source, \"retaliation\", attackResult.retaliationDamageDone > 0 ? attackResult.retaliationDamageDone : \"miss\");\r\n }\r\n if (this.zone.isDungeon && attackResult.retaliationDamageDone > 0 && source.isPlayer) {\r\n const log = this.buildCombatLog(target, \"retaliation\", source, attackResult.retaliationDamageDone);\r\n this.addToWipeLogs(log);\r\n }\r\n\r\n if (target.combatDetails.currentHitpoints == 0) {\r\n this.eventQueue.clearEventsForUnit(target);\r\n this.simResult.addDeath(target);\r\n if (!target.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(target.hrid, false, this.simulationTime);\r\n }\r\n // console.log(target.hrid, \"died\");\r\n }\r\n\r\n\r\n if (attackResult.didHit && abilityEffect.pierceChance > Math.random()) {\r\n continue;\r\n }\r\n }\r\n \r\n if (parryTarget)\r\n {\r\n break;\r\n }\r\n\r\n if (abilityEffect.targetType == \"enemy\") {\r\n break;\r\n }\r\n }\r\n }\r\n\r\n processAbilityHealEffect(source, ability, abilityEffect) {\r\n\r\n if (abilityEffect.targetType == \"allAllies\") {\r\n let targets = source.isPlayer ? this.players : this.enemies;\r\n for (const target of targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0)) {\r\n let amountHealed = CombatUtilities.processHeal(source, abilityEffect, target);\r\n\r\n this.simResult.addHitpointsGained(target, ability.hrid, amountHealed);\r\n }\r\n return;\r\n }\r\n\r\n if (abilityEffect.targetType == \"lowestHpAlly\") {\r\n let targets = source.isPlayer ? this.players : this.enemies;\r\n let healTarget;\r\n for (const target of targets.filter((unit) => unit && unit.combatDetails.currentHitpoints > 0)) {\r\n if (!healTarget) {\r\n healTarget = target;\r\n continue;\r\n }\r\n if (target.combatDetails.currentHitpoints < healTarget.combatDetails.currentHitpoints) {\r\n healTarget = target;\r\n }\r\n }\r\n\r\n if (healTarget) {\r\n let amountHealed = CombatUtilities.processHeal(source, abilityEffect, healTarget);\r\n\r\n this.simResult.addHitpointsGained(healTarget, ability.hrid, amountHealed);\r\n }\r\n return;\r\n }\r\n\r\n if (abilityEffect.targetType != \"self\") {\r\n throw new Error(\"Unsupported target type for heal ability effect: \" + ability.hrid);\r\n }\r\n\r\n let amountHealed = CombatUtilities.processHeal(source, abilityEffect, source);\r\n\r\n this.simResult.addHitpointsGained(source, ability.hrid, amountHealed);\r\n }\r\n\r\n processAbilityReviveEffect(source, ability, abilityEffect) {\r\n if (abilityEffect.targetType != \"deadAlly\") {\r\n throw new Error(\"Unsupported target type for revive ability effect: \" + ability.hrid);\r\n }\r\n\r\n let targets = source.isPlayer ? this.players : this.enemies;\r\n let reviveTarget = targets.find((unit) => unit && unit.combatDetails.currentHitpoints <= 0);\r\n\r\n if (reviveTarget) {\r\n this.eventQueue.clearMatching((event) => event.type == PlayerRespawnEvent.type && event.hrid == reviveTarget.hrid);\r\n\r\n reviveTarget.removeExpiredBuffs(this.simulationTime);\r\n\r\n let amountHealed = CombatUtilities.processRevive(source, abilityEffect, reviveTarget);\r\n\r\n this.simResult.addHitpointsGained(reviveTarget, ability.hrid, amountHealed);\r\n\r\n this.addNextAttackEvent(reviveTarget);\r\n\r\n if (!source.isPlayer) {\r\n this.simResult.updateTimeSpentAlive(reviveTarget.hrid, true, this.simulationTime);\r\n }\r\n\r\n // console.log(source.hrid + \" revived \" + reviveTarget.hrid + \" with \" + amountHealed + \" HP.\" + ' at wave #' + (this.zone.encountersKilled - 1) + ' at time ' + this.simulationTime / 1000000000 + 's');\r\n }\r\n return;\r\n }\r\n\r\n processAbilityPromoteEffect(source, ability, abilityEffect) {\r\n const promotionHrids = [\"/monsters/enchanted_rook\", \"/monsters/enchanted_knight\", \"/monsters/enchanted_bishop\"];\r\n let randomPromotionIndex = Math.floor(Math.random() * promotionHrids.length);\r\n return new Monster(promotionHrids[randomPromotionIndex], source.difficultyTier);\r\n }\r\n\r\n processAbilitySpendHpEffect(source, ability, abilityEffect) {\r\n if (abilityEffect.targetType != \"self\") {\r\n throw new Error(\"Unsupported target type for spend hp ability effect: \" + ability.hrid);\r\n }\r\n\r\n let hpSpent = CombatUtilities.processSpendHp(source, abilityEffect);\r\n\r\n this.simResult.addHitpointsSpent(source, ability.hrid, hpSpent);\r\n }\r\n}\r\n\r\nexport default CombatSimulator;\r\n","class CombatUnit {\r\n isPlayer;\r\n isStunned = false;\r\n stunExpireTime = null;\r\n isBlinded = false;\r\n blindExpireTime = null;\r\n isSilenced = false;\r\n silenceExpireTime = null;\r\n\r\n isOutOfMana = false;\r\n\r\n // Base levels which don't change after initialization\r\n staminaLevel = 1;\r\n intelligenceLevel = 1;\r\n attackLevel = 1;\r\n meleeLevel = 1;\r\n defenseLevel = 1;\r\n rangedLevel = 1;\r\n magicLevel = 1;\r\n\r\n experience = 0;\r\n experienceRate = 0;\r\n enrageTime = 0;\r\n\r\n abilities = [null, null, null, null];\r\n food = [null, null, null];\r\n drinks = [null, null, null];\r\n houseRooms = [];\r\n achievements = null;\r\n dropTable = [];\r\n rareDropTable = [];\r\n abilityManaCosts = new Map();\r\n\r\n // Calculated combat stats including temporary buffs\r\n combatDetails = {\r\n staminaLevel: 1,\r\n intelligenceLevel: 1,\r\n attackLevel: 1,\r\n meleeLevel: 1,\r\n defenseLevel: 1,\r\n rangedLevel: 1,\r\n magicLevel: 1,\r\n maxHitpoints: 110,\r\n currentHitpoints: 110,\r\n maxManapoints: 110,\r\n currentManapoints: 110,\r\n stabAccuracyRating: 11,\r\n slashAccuracyRating: 11,\r\n smashAccuracyRating: 11,\r\n rangedAccuracyRating: 11,\r\n magicAccuracyRating: 11,\r\n stabMaxDamage: 11,\r\n slashMaxDamage: 11,\r\n smashMaxDamage: 11,\r\n rangedMaxDamage: 11,\r\n magicMaxDamage: 11,\r\n stabEvasionRating: 11,\r\n slashEvasionRating: 11,\r\n smashEvasionRating: 11,\r\n rangedEvasionRating: 11,\r\n magicEvasionRating: 11,\r\n defensiveMaxDamage: 0,\r\n totalArmor: 0.2,\r\n totalWaterResistance: 0.4,\r\n totalNatureResistance: 0.4,\r\n totalFireResistance: 0.4,\r\n abilityHaste: 0,\r\n tenacity: 0,\r\n totalThreat: 100,\r\n combatStats: {\r\n combatStyleHrid: \"/combat_styles/smash\",\r\n damageType: \"/damage_types/physical\",\r\n attackInterval: 3000000000,\r\n autoAttackDamage: 0,\r\n abilityDamage: 0,\r\n criticalRate: 0,\r\n criticalDamage: 0,\r\n stabAccuracy: 0,\r\n slashAccuracy: 0,\r\n smashAccuracy: 0,\r\n rangedAccuracy: 0,\r\n magicAccuracy: 0,\r\n stabDamage: 0,\r\n slashDamage: 0,\r\n smashDamage: 0,\r\n rangedDamage: 0,\r\n magicDamage: 0,\r\n defensiveDamage: 0,\r\n taskDamage: 0,\r\n physicalAmplify: 0,\r\n waterAmplify: 0,\r\n natureAmplify: 0,\r\n fireAmplify: 0,\r\n healingAmplify: 0,\r\n physicalThorns: 0,\r\n elementalThorns: 0,\r\n maxHitpoints: 0,\r\n maxManapoints: 0,\r\n stabEvasion: 0,\r\n slashEvasion: 0,\r\n smashEvasion: 0,\r\n rangedEvasion: 0,\r\n magicEvasion: 0,\r\n armor: 0,\r\n waterResistance: 0,\r\n natureResistance: 0,\r\n fireResistance: 0,\r\n lifeSteal: 0,\r\n hpRegenPer10: 0.01,\r\n mpRegenPer10: 0.01,\r\n combatDropRate: 0,\r\n combatDropQuantity: 0,\r\n combatRareFind: 0,\r\n combatExperience: 0,\r\n foodSlots: 1,\r\n drinkSlots: 1,\r\n armorPenetration: 0,\r\n waterPenetration: 0,\r\n naturePenetration: 0,\r\n firePenetration: 0,\r\n manaLeech: 0,\r\n castSpeed: 0,\r\n threat: 100,\r\n parry: 0,\r\n mayhem: 0,\r\n pierce: 0,\r\n curse: 0,\r\n ripple: 0,\r\n bloom: 0,\r\n blaze: 0,\r\n weaken: 0,\r\n fury: 0,\r\n foodHaste: 0,\r\n drinkConcentration: 0,\r\n damageTaken: 0,\r\n attackSpeed: 0,\r\n armorDamageRatio: 0,\r\n hpDrainRatio: 0,\r\n primaryTraining: \"\",\r\n focusTraining: \"\",\r\n staminaExperience: 0,\r\n intelligenceExperience: 0,\r\n attackExperience: 0,\r\n defenseExperience: 0,\r\n meleeExperience: 0,\r\n rangedExperience: 0,\r\n magicExperience: 0,\r\n retaliation: 0,\r\n },\r\n };\r\n combatBuffs = {};\r\n permanentBuffs = {};\r\n zoneBuffs = {};\r\n extraBuffs = {};\r\n\r\n constructor() { }\r\n\r\n updateCombatDetails() {\r\n if (this.isPlayer) {\r\n if (this.combatDetails.combatStats.hpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01 + this.combatDetails.combatStats.hpRegenPer10;\r\n }\r\n if (this.combatDetails.combatStats.mpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01 + this.combatDetails.combatStats.mpRegenPer10;\r\n }\r\n }\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((stat) => {\r\n this.combatDetails[stat + \"Level\"] = this[stat + \"Level\"];\r\n let boosts = this.getBuffBoosts(\"/buff_types/\" + stat + \"_level\");\r\n boosts.forEach((buff) => {\r\n this.combatDetails[stat + \"Level\"] += (this[stat + \"Level\"] * buff.ratioBoost);\r\n this.combatDetails[stat + \"Level\"] += buff.flatBoost;\r\n });\r\n });\r\n\r\n this.combatDetails.maxHitpoints = Math.floor\r\n (10 * (10 + this.combatDetails.staminaLevel) + this.combatDetails.combatStats.maxHitpoints);\r\n this.combatDetails.maxManapoints = Math.floor\r\n (10 * (10 + this.combatDetails.intelligenceLevel) + this.combatDetails.combatStats.maxManapoints);\r\n\r\n let accuracyRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_accuracy\").ratioBoost;\r\n let damageRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_damage\").ratioBoost;\r\n // if (accuracyRatioBoostFromFury > 0) {\r\n // console.log(\"Fury Boost: \" + accuracyRatioBoostFromFury);\r\n // }\r\n\r\n let accuracyRatioBoost = this.getBuffBoost(\"/buff_types/accuracy\").ratioBoost;\r\n let damageRatioBoost = this.getBuffBoost(\"/buff_types/damage\").ratioBoost;\r\n\r\n [\"stab\", \"slash\", \"smash\"].forEach((style) => {\r\n this.combatDetails[style + \"AccuracyRating\"] =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Accuracy\"]) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails[style + \"MaxDamage\"] =\r\n (10 + this.combatDetails.meleeLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Damage\"]) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n let baseEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats[style + \"Evasion\"]);\r\n this.combatDetails[style + \"EvasionRating\"] = baseEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails[style + \"EvasionRating\"] += boost.flatBoost;\r\n this.combatDetails[style + \"EvasionRating\"] += baseEvasion * boost.ratioBoost;\r\n }\r\n });\r\n\r\n this.combatDetails.defensiveMaxDamage = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.defensiveDamage);\r\n\r\n // when equiped bulwark\r\n if (this.equipment?.['/equipment_types/two_hand']?.hrid.includes(\"bulwark\")) {\r\n this.combatDetails.smashMaxDamage += this.combatDetails.defensiveMaxDamage;\r\n }\r\n\r\n this.combatDetails.rangedAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.rangedAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.rangedMaxDamage =\r\n (10 + this.combatDetails.rangedLevel) *\r\n (1 + this.combatDetails.combatStats.rangedDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseRangedEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.rangedEvasion);\r\n this.combatDetails.rangedEvasionRating = baseRangedEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.rangedEvasionRating += boost.flatBoost;\r\n this.combatDetails.rangedEvasionRating += baseRangedEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.damageTaken = this.getBuffBoost(\"/buff_types/damage_taken\").flatBoost;\r\n // if (this.combatDetails.combatStats.damageTaken > 0) {\r\n // console.log(\"Damage taken: \" + this.combatDetails.combatStats.damageTaken);\r\n // }\r\n\r\n this.combatDetails.magicAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.magicAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.magicMaxDamage =\r\n (10 + this.combatDetails.magicLevel) *\r\n (1 + this.combatDetails.combatStats.magicDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseMagicEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.magicEvasion);\r\n this.combatDetails.magicEvasionRating = baseMagicEvasion;\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.magicEvasionRating += boost.flatBoost;\r\n this.combatDetails.magicEvasionRating += baseMagicEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.physicalAmplify += this.getBuffBoost(\"/buff_types/physical_amplify\").flatBoost;\r\n this.combatDetails.combatStats.waterAmplify += this.getBuffBoost(\"/buff_types/water_amplify\").flatBoost;\r\n this.combatDetails.combatStats.natureAmplify += this.getBuffBoost(\"/buff_types/nature_amplify\").flatBoost;\r\n this.combatDetails.combatStats.fireAmplify += this.getBuffBoost(\"/buff_types/fire_amplify\").flatBoost;\r\n\r\n this.combatDetails.combatStats.attackInterval /= (1 + (this.combatDetails.attackLevel / 2000));\r\n\r\n let baseAttackSpeed = this.combatDetails.combatStats.attackSpeed;\r\n this.combatDetails.combatStats.attackInterval /= (1 + baseAttackSpeed);\r\n let attackIntervalBoosts = this.getBuffBoosts(\"/buff_types/attack_speed\");\r\n let attackIntervalRatioBoost = attackIntervalBoosts\r\n .map((boost) => boost.ratioBoost)\r\n .reduce((prev, cur) => prev + cur, 0);\r\n this.combatDetails.combatStats.attackInterval /= (1 + attackIntervalRatioBoost);\r\n\r\n let baseArmor = 0.2 * this.combatDetails.defenseLevel + this.combatDetails.combatStats.armor;\r\n this.combatDetails.totalArmor = baseArmor;\r\n let armorBoosts = this.getBuffBoosts(\"/buff_types/armor\");\r\n for (const boost of armorBoosts) {\r\n this.combatDetails.totalArmor += boost.flatBoost;\r\n this.combatDetails.totalArmor += baseArmor * boost.ratioBoost;\r\n }\r\n\r\n let baseWaterResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.waterResistance;\r\n this.combatDetails.totalWaterResistance = baseWaterResistance;\r\n let waterResistanceBoosts = this.getBuffBoosts(\"/buff_types/water_resistance\");\r\n for (const boost of waterResistanceBoosts) {\r\n this.combatDetails.totalWaterResistance += boost.flatBoost;\r\n this.combatDetails.totalWaterResistance += baseWaterResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseNatureResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.natureResistance;\r\n this.combatDetails.totalNatureResistance = baseNatureResistance;\r\n let natureResistanceBoosts = this.getBuffBoosts(\"/buff_types/nature_resistance\");\r\n for (const boost of natureResistanceBoosts) {\r\n this.combatDetails.totalNatureResistance += boost.flatBoost;\r\n this.combatDetails.totalNatureResistance += baseNatureResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseFireResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.fireResistance;\r\n this.combatDetails.totalFireResistance = baseFireResistance;\r\n let fireResistanceBoosts = this.getBuffBoosts(\"/buff_types/fire_resistance\");\r\n for (const boost of fireResistanceBoosts) {\r\n this.combatDetails.totalFireResistance += boost.flatBoost;\r\n this.combatDetails.totalFireResistance += baseFireResistance * boost.ratioBoost;\r\n }\r\n\r\n let hpRegenBoosts = this.getBuffBoost(\"/buff_types/hp_regen\");\r\n this.combatDetails.combatStats.hpRegenPer10 += this.combatDetails.combatStats.hpRegenPer10 * hpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.hpRegenPer10 += hpRegenBoosts.flatBoost;\r\n\r\n let mpRegenBoosts = this.getBuffBoost(\"/buff_types/mp_regen\");\r\n this.combatDetails.combatStats.mpRegenPer10 += this.combatDetails.combatStats.mpRegenPer10 * mpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.mpRegenPer10 += mpRegenBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.lifeSteal += this.getBuffBoost(\"/buff_types/life_steal\").flatBoost;\r\n this.combatDetails.combatStats.physicalThorns += this.getBuffBoost(\r\n \"/buff_types/physical_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.elementalThorns += this.getBuffBoost(\r\n \"/buff_types/elemental_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.combatExperience += this.getBuffBoost(\"/buff_types/wisdom\").flatBoost;\r\n this.combatDetails.combatStats.criticalRate += this.getBuffBoost(\"/buff_types/critical_rate\").flatBoost;\r\n this.combatDetails.combatStats.criticalDamage += this.getBuffBoost(\"/buff_types/critical_damage\").flatBoost;\r\n\r\n this.combatDetails.combatStats.castSpeed += this.getBuffBoost(\"/buff_types/cast_speed\").flatBoost;\r\n this.combatDetails.combatStats.castSpeed += this.combatDetails[\"attackLevel\"] / 2000;\r\n\r\n let combatDropRateBoosts = this.getBuffBoost(\"/buff_types/combat_drop_rate\");\r\n this.combatDetails.combatStats.combatDropRate += (1 + this.combatDetails.combatStats.combatDropRate) * combatDropRateBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropRate += combatDropRateBoosts.flatBoost;\r\n let combatRareFindBoosts = this.getBuffBoost(\"/buff_types/rare_find\");\r\n this.combatDetails.combatStats.combatRareFind += (1 + this.combatDetails.combatStats.combatRareFind) * combatRareFindBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatRareFind += combatRareFindBoosts.flatBoost;\r\n let combatDropQuantityBoosts = this.getBuffBoost(\"/buff_types/combat_drop_quantity\");\r\n this.combatDetails.combatStats.combatDropQuantity += (1 + this.combatDetails.combatStats.combatDropQuantity) * combatDropQuantityBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropQuantity += combatDropQuantityBoosts.flatBoost;\r\n\r\n let baseThreat = 100 + this.combatDetails.combatStats.threat;\r\n this.combatDetails.totalThreat = baseThreat;\r\n let threatBoosts = this.getBuffBoost(\"/buff_types/threat\");\r\n if (threatBoosts.ratioBoost !== 0) {\r\n this.combatDetails.combatStats.threat += baseThreat * threatBoosts.ratioBoost;\r\n } else {\r\n this.combatDetails.combatStats.threat = baseThreat;\r\n }\r\n this.combatDetails.combatStats.threat += threatBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.retaliation += this.getBuffBoost(\"/buff_types/retaliation\").flatBoost;\r\n }\r\n\r\n addBuff(buff, currentTime) {\r\n buff.startTime = currentTime;\r\n this.combatBuffs[buff.uniqueHrid] = buff;\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n removeBuff(buff) {\r\n if (!this.combatBuffs[buff.uniqueHrid]) {\r\n return;\r\n }\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n addPermanentBuff(buff) {\r\n if (this.permanentBuffs[buff.typeHrid]) {\r\n this.permanentBuffs[buff.typeHrid].flatBoost += buff.flatBoost;\r\n this.permanentBuffs[buff.typeHrid].ratioBoost += buff.ratioBoost;\r\n } else {\r\n this.permanentBuffs[buff.typeHrid] = buff;\r\n }\r\n }\r\n\r\n generatePermanentBuffs() {\r\n for (let i = 0; i < this.houseRooms.length; i++) {\r\n const houseRoom = this.houseRooms[i];\r\n houseRoom.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n\r\n if (this.achievements) {\r\n this.achievements.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.zoneBuffs) {\r\n this.zoneBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.extraBuffs) {\r\n this.extraBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n }\r\n\r\n removeExpiredBuffs(currentTime) {\r\n let expiredBuffs = Object.values(this.combatBuffs).filter(\r\n (buff) => buff.startTime + buff.duration <= currentTime\r\n );\r\n expiredBuffs.forEach((buff) => {\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n });\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearBuffs() {\r\n this.combatBuffs = structuredClone(this.permanentBuffs);\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearCCs() {\r\n this.isStunned = false;\r\n this.stunExpireTime = null;\r\n this.isSilenced = false;\r\n this.silenceExpireTime = null;\r\n this.isBlinded = false;\r\n this.blindExpireTime = null;\r\n this.combatDetails.combatStats.damageTaken = 0;\r\n }\r\n\r\n getBuffBoosts(type) {\r\n let boosts = [];\r\n Object.values(this.combatBuffs)\r\n .filter((buff) => buff.typeHrid == type)\r\n .forEach((buff) => {\r\n boosts.push({ ratioBoost: buff.ratioBoost, flatBoost: buff.flatBoost });\r\n });\r\n\r\n return boosts;\r\n }\r\n\r\n getBuffBoost(type) {\r\n let boosts = this.getBuffBoosts(type);\r\n\r\n let boost = {\r\n ratioBoost: 0,\r\n flatBoost: 0,\r\n };\r\n\r\n for (let i = 0; i < boosts.length; i++) {\r\n boost.ratioBoost += boosts[i]?.ratioBoost ?? 0;\r\n boost.flatBoost += boosts[i]?.flatBoost ?? 0;\r\n }\r\n\r\n return boost;\r\n }\r\n\r\n reset(currentTime = 0) {\r\n this.clearCCs();\r\n this.clearBuffs();\r\n this.updateCombatDetails();\r\n this.resetCooldowns(currentTime);\r\n\r\n this.combatDetails.currentHitpoints = this.combatDetails.maxHitpoints;\r\n this.combatDetails.currentManapoints = this.combatDetails.maxManapoints;\r\n }\r\n\r\n resetCooldowns(currentTime = 0) {\r\n this.food.filter((food) => food != null).forEach((food) => (food.lastUsed = Number.MIN_SAFE_INTEGER));\r\n this.drinks.filter((drink) => drink != null).forEach((drink) => (drink.lastUsed = Number.MIN_SAFE_INTEGER));\r\n\r\n let haste = this.combatDetails.combatStats.abilityHaste;\r\n\r\n this.abilities\r\n .filter((ability) => ability != null)\r\n .forEach((ability) => {\r\n if (this.isPlayer) {\r\n ability.lastUsed = Number.MIN_SAFE_INTEGER;\r\n } else {\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n ability.lastUsed = currentTime - Math.floor(cooldownDuration * 0.5) + Math.floor(Math.random() * cooldownDuration * 0.5);\r\n }\r\n });\r\n }\r\n\r\n addHitpoints(hitpoints) {\r\n let hitpointsAdded = 0;\r\n\r\n if (this.combatDetails.currentHitpoints >= this.combatDetails.maxHitpoints) {\r\n return hitpointsAdded;\r\n }\r\n\r\n let newHitpoints = Math.min(this.combatDetails.currentHitpoints + hitpoints, this.combatDetails.maxHitpoints);\r\n hitpointsAdded = newHitpoints - this.combatDetails.currentHitpoints;\r\n this.combatDetails.currentHitpoints = newHitpoints;\r\n\r\n return hitpointsAdded;\r\n }\r\n\r\n addManapoints(manapoints) {\r\n let manapointsAdded = 0;\r\n\r\n if (this.combatDetails.currentManapoints >= this.combatDetails.maxManapoints) {\r\n return manapointsAdded;\r\n }\r\n\r\n let newManapoints = Math.min(\r\n this.combatDetails.currentManapoints + manapoints,\r\n this.combatDetails.maxManapoints\r\n );\r\n manapointsAdded = newManapoints - this.combatDetails.currentManapoints;\r\n this.combatDetails.currentManapoints = newManapoints;\r\n\r\n return manapointsAdded;\r\n }\r\n}\r\n\r\nexport default CombatUnit;\r\n","class CombatUtilities {\r\n static getTarget(enemies) {\r\n if (!enemies) {\r\n return null;\r\n }\r\n let target = enemies.find((enemy) => enemy.combatDetails.currentHitpoints > 0);\r\n\r\n return target ?? null;\r\n }\r\n\r\n static randomInt(min, max) {\r\n if (max < min) {\r\n let temp = min;\r\n min = max;\r\n max = temp;\r\n }\r\n\r\n let minCeil = Math.ceil(min);\r\n let maxFloor = Math.floor(max);\r\n\r\n if (Math.floor(min) == maxFloor) {\r\n return Math.floor((min + max) / 2 + Math.random());\r\n }\r\n\r\n let minTail = -1 * (min - minCeil);\r\n let maxTail = max - maxFloor;\r\n\r\n let balancedWeight = 2 * minTail + (maxFloor - minCeil);\r\n let balancedAverage = (maxFloor + minCeil) / 2;\r\n let average = (max + min) / 2;\r\n let extraTailWeight = (balancedWeight * (average - balancedAverage)) / (maxFloor + 1 - average);\r\n let extraTailChance = Math.abs(extraTailWeight / (extraTailWeight + balancedWeight));\r\n\r\n if (Math.random() < extraTailChance) {\r\n if (maxTail > minTail) {\r\n return Math.floor(maxFloor + 1);\r\n } else {\r\n return Math.floor(minCeil - 1);\r\n }\r\n }\r\n\r\n if (maxTail > minTail) {\r\n return Math.floor(min + Math.random() * (maxFloor + minTail - min + 1));\r\n } else {\r\n return Math.floor(minCeil - maxTail + Math.random() * (max - (minCeil - maxTail) + 1));\r\n }\r\n }\r\n\r\n static processAttack(source, target, abilityEffect = null) {\r\n let combatStyle = abilityEffect\r\n ? abilityEffect.combatStyleHrid\r\n : source.combatDetails.combatStats.combatStyleHrid;\r\n let damageType = abilityEffect ? abilityEffect.damageType : source.combatDetails.combatStats.damageType;\r\n\r\n let sourceAccuracyRating = 1;\r\n let sourceAutoAttackMaxDamage = 1;\r\n let targetEvasionRating = 1;\r\n\r\n switch (combatStyle) {\r\n case \"/combat_styles/stab\":\r\n sourceAccuracyRating = source.combatDetails.stabAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.stabMaxDamage;\r\n targetEvasionRating = target.combatDetails.stabEvasionRating;\r\n break;\r\n case \"/combat_styles/slash\":\r\n sourceAccuracyRating = source.combatDetails.slashAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.slashMaxDamage;\r\n targetEvasionRating = target.combatDetails.slashEvasionRating;\r\n break;\r\n case \"/combat_styles/smash\":\r\n sourceAccuracyRating = source.combatDetails.smashAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.smashMaxDamage;\r\n targetEvasionRating = target.combatDetails.smashEvasionRating;\r\n break;\r\n case \"/combat_styles/ranged\":\r\n sourceAccuracyRating = source.combatDetails.rangedAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.rangedMaxDamage;\r\n targetEvasionRating = target.combatDetails.rangedEvasionRating;\r\n break;\r\n case \"/combat_styles/magic\":\r\n sourceAccuracyRating = source.combatDetails.magicAccuracyRating;\r\n sourceAutoAttackMaxDamage = source.combatDetails.magicMaxDamage;\r\n targetEvasionRating = target.combatDetails.magicEvasionRating;\r\n break;\r\n default:\r\n throw new Error(\"Unknown combat style: \" + combatStyle);\r\n }\r\n\r\n let sourceDamageMultiplier = 1;\r\n let sourceResistance = 0;\r\n let sourcePenetration = 0;\r\n let targetResistance = 0;\r\n let targetThornPower = 0;\r\n let targetPenetration = 0;\r\n let thornType;\r\n\r\n switch (damageType) {\r\n case \"/damage_types/physical\":\r\n sourceDamageMultiplier = 1 + source.combatDetails.combatStats.physicalAmplify;\r\n sourceResistance = source.combatDetails.totalArmor;\r\n sourcePenetration = source.combatDetails.combatStats.armorPenetration;\r\n targetResistance = target.combatDetails.totalArmor;\r\n targetThornPower = target.combatDetails.combatStats.physicalThorns;\r\n targetPenetration = target.combatDetails.combatStats.armorPenetration;\r\n thornType = \"physicalThorns\";\r\n break;\r\n case \"/damage_types/water\":\r\n sourceDamageMultiplier = 1 + source.combatDetails.combatStats.waterAmplify;\r\n sourceResistance = source.combatDetails.totalWaterResistance;\r\n sourcePenetration = source.combatDetails.combatStats.waterPenetration;\r\n targetResistance = target.combatDetails.totalWaterResistance;\r\n targetThornPower = target.combatDetails.combatStats.elementalThorns;\r\n targetPenetration = target.combatDetails.combatStats.waterPenetration;\r\n thornType = \"elementalThorns\";\r\n break;\r\n case \"/damage_types/nature\":\r\n sourceDamageMultiplier = 1 + source.combatDetails.combatStats.natureAmplify;\r\n sourceResistance = source.combatDetails.totalNatureResistance;\r\n sourcePenetration = source.combatDetails.combatStats.naturePenetration;\r\n targetResistance = target.combatDetails.totalNatureResistance;\r\n targetThornPower = target.combatDetails.combatStats.elementalThorns;\r\n targetPenetration = target.combatDetails.combatStats.naturePenetration;\r\n thornType = \"elementalThorns\";\r\n break;\r\n case \"/damage_types/fire\":\r\n sourceDamageMultiplier = 1 + source.combatDetails.combatStats.fireAmplify;\r\n sourceResistance = source.combatDetails.totalFireResistance;\r\n sourcePenetration = source.combatDetails.combatStats.firePenetration;\r\n targetResistance = target.combatDetails.totalFireResistance;\r\n targetThornPower = target.combatDetails.combatStats.elementalThorns;\r\n targetPenetration = target.combatDetails.combatStats.firePenetration;\r\n thornType = \"elementalThorns\";\r\n break;\r\n default:\r\n throw new Error(\"Unknown damage type: \" + damageType);\r\n }\r\n\r\n let hitChance = 1;\r\n let critChance = 0;\r\n let isCrit = false;\r\n let bonusCritChance = source.combatDetails.combatStats.criticalRate;\r\n let bonusCritDamage = source.combatDetails.combatStats.criticalDamage;\r\n\r\n if (abilityEffect) {\r\n sourceAccuracyRating *= (1 + abilityEffect.bonusAccuracyRatio);\r\n }\r\n\r\n if (source.isWeakened) {\r\n sourceAccuracyRating = sourceAccuracyRating - (source.weakenPercentage * sourceAccuracyRating);\r\n }\r\n\r\n hitChance =\r\n Math.pow(sourceAccuracyRating, 1.4) /\r\n (Math.pow(sourceAccuracyRating, 1.4) + Math.pow(targetEvasionRating, 1.4));\r\n\r\n if (combatStyle == \"/combat_styles/ranged\") {\r\n critChance = 0.3 * hitChance;\r\n }\r\n\r\n critChance = critChance + bonusCritChance;\r\n\r\n let baseDamageFlat = abilityEffect ? abilityEffect.damageFlat : 0;\r\n let baseDamageRatio = abilityEffect ? abilityEffect.damageRatio : 1;\r\n\r\n let armorDamageRatioFlat = abilityEffect ? abilityEffect.armorDamageRatio * source.combatDetails.totalArmor : 0;\r\n\r\n let sourceMinDamage = sourceDamageMultiplier * (1 + baseDamageFlat + armorDamageRatioFlat);\r\n let sourceMaxDamage = sourceDamageMultiplier * (baseDamageRatio * sourceAutoAttackMaxDamage + baseDamageFlat + armorDamageRatioFlat);\r\n\r\n if (Math.random() < critChance) {\r\n sourceMaxDamage = sourceMaxDamage * (1 + bonusCritDamage);\r\n sourceMinDamage = sourceMaxDamage;\r\n isCrit = true;\r\n }\r\n\r\n let damageRoll = CombatUtilities.randomInt(sourceMinDamage, sourceMaxDamage);\r\n damageRoll *= (1 + source.combatDetails.combatStats.taskDamage);\r\n damageRoll *= (1 + target.combatDetails.combatStats.damageTaken);\r\n if (!abilityEffect) {\r\n damageRoll += damageRoll * source.combatDetails.combatStats.autoAttackDamage;\r\n } else {\r\n damageRoll *= (1 + source.combatDetails.combatStats.abilityDamage);\r\n }\r\n\r\n let damageDone = 0;\r\n let thornDamageDone = 0;\r\n\r\n let didHit = false;\r\n if (Math.random() < hitChance) {\r\n didHit = true;\r\n let penetratedTargetResistance = targetResistance;\r\n\r\n if (sourcePenetration > 0 && targetResistance > 0) {\r\n penetratedTargetResistance = targetResistance / (1 + sourcePenetration);\r\n }\r\n\r\n let targetDamageTakenRatio = 100 / (100 + penetratedTargetResistance);\r\n if (penetratedTargetResistance < 0) {\r\n targetDamageTakenRatio = (100 - penetratedTargetResistance) / 100;\r\n }\r\n\r\n let mitigatedDamage = Math.ceil(targetDamageTakenRatio * damageRoll);\r\n damageDone = Math.min(mitigatedDamage, target.combatDetails.currentHitpoints);\r\n target.combatDetails.currentHitpoints -= damageDone;\r\n }\r\n\r\n if (targetThornPower > 0.0 && targetResistance > -99.0) {\r\n let penetratedSourceResistance = sourceResistance\r\n\r\n if (sourceResistance > 0) {\r\n penetratedSourceResistance = sourceResistance / (1 + targetPenetration);\r\n }\r\n\r\n let sourceDamageTakenRatio = 100.0 / (100 + penetratedSourceResistance);\r\n if (penetratedSourceResistance < 0) {\r\n sourceDamageTakenRatio = (100 - penetratedSourceResistance) / 100;\r\n }\r\n\r\n let targetTaskDamageMultiplier = 1.0 + target.combatDetails.combatStats.taskDamage;\r\n let sourceDamageTakenMultiplier = 1.0 + source.combatDetails.combatStats.damageTaken;\r\n let targetDamageMultiplier = targetTaskDamageMultiplier * sourceDamageTakenMultiplier;\r\n\r\n let thornsDamageRoll = CombatUtilities.randomInt(1,\r\n targetDamageMultiplier\r\n * target.combatDetails.defensiveMaxDamage\r\n * (1.0 + targetResistance / 100.0)\r\n * targetThornPower);\r\n\r\n let mitigatedThornsDamage = Math.ceil(sourceDamageTakenRatio * thornsDamageRoll);\r\n\r\n thornDamageDone = Math.min(mitigatedThornsDamage, source.combatDetails.currentHitpoints);\r\n source.combatDetails.currentHitpoints -= thornDamageDone;\r\n }\r\n\r\n let retaliationDamageDone = 0;\r\n if (target.combatDetails.combatStats.retaliation > 0) {\r\n let retaliationHitChance = \r\n Math.pow(target.combatDetails.smashAccuracyRating, 1.4) /\r\n (Math.pow(target.combatDetails.smashAccuracyRating, 1.4) + Math.pow(source.combatDetails.smashEvasionRating, 1.4));\r\n\r\n if (retaliationHitChance > Math.random()) {\r\n let sourceEffectiveArmor = source.combatDetails.totalArmor;\r\n if (sourceEffectiveArmor > 0) {\r\n sourceEffectiveArmor = sourceEffectiveArmor / (1.0 + target.combatDetails.combatStats.armorPenetration);\r\n }\r\n\r\n let sourceDamageTakenRatio = 100.0 / (100.0 + sourceEffectiveArmor);\r\n if (sourceEffectiveArmor < 0) {\r\n sourceDamageTakenRatio = (100.0 - sourceEffectiveArmor) / 100.0;\r\n }\r\n\r\n let targetTaskDamageMultiplier = 1.0 + target.combatDetails.combatStats.taskDamage;\r\n let sourceDamageTakenMultiplier = 1.0 + source.combatDetails.combatStats.damageTaken;\r\n let retaliationDamageMultiplier = targetTaskDamageMultiplier * sourceDamageTakenMultiplier;\r\n\r\n let premitigatedDamage = damageRoll;\r\n premitigatedDamage = Math.min(premitigatedDamage, target.combatDetails.defensiveMaxDamage * 5);\r\n\r\n let retaliationMinDamage = retaliationDamageMultiplier * target.combatDetails.combatStats.retaliation * premitigatedDamage;\r\n let retaliationMaxDamage = retaliationDamageMultiplier * target.combatDetails.combatStats.retaliation * (target.combatDetails.defensiveMaxDamage + premitigatedDamage);\r\n\r\n let retaliationDamageRoll = CombatUtilities.randomInt(retaliationMinDamage, retaliationMaxDamage);\r\n let mitigatedRetaliationDamage = Math.ceil(sourceDamageTakenRatio * retaliationDamageRoll);\r\n retaliationDamageDone = Math.min(mitigatedRetaliationDamage, source.combatDetails.currentHitpoints);\r\n source.combatDetails.currentHitpoints -= retaliationDamageDone;\r\n }\r\n }\r\n\r\n let lifeStealHeal = 0;\r\n if (!abilityEffect && didHit && source.combatDetails.combatStats.lifeSteal > 0) {\r\n lifeStealHeal = source.addHitpoints(Math.floor(source.combatDetails.combatStats.lifeSteal * damageDone));\r\n }\r\n\r\n let hpDrain = 0;\r\n if (abilityEffect && didHit && abilityEffect.hpDrainRatio > 0) {\r\n let healingAmplify = 1 + source.combatDetails.combatStats.healingAmplify;\r\n hpDrain = source.addHitpoints(Math.floor(abilityEffect.hpDrainRatio * damageDone * healingAmplify));\r\n }\r\n\r\n let manaLeechMana = 0;\r\n if (!abilityEffect && didHit && source.combatDetails.combatStats.manaLeech > 0) {\r\n manaLeechMana = source.addManapoints(Math.floor(source.combatDetails.combatStats.manaLeech * damageDone));\r\n }\r\n\r\n return { damageDone, didHit, thornDamageDone, thornType, retaliationDamageDone, lifeStealHeal, hpDrain, manaLeechMana, isCrit};\r\n }\r\n\r\n static processHeal(source, abilityEffect, target) {\r\n if (abilityEffect.combatStyleHrid != \"/combat_styles/magic\") {\r\n throw new Error(\"Heal ability effect not supported for combat style: \" + abilityEffect.combatStyleHrid);\r\n }\r\n\r\n let healingAmplify = 1 + source.combatDetails.combatStats.healingAmplify;\r\n let magicMaxDamage = source.combatDetails.magicMaxDamage;\r\n\r\n let baseHealFlat = abilityEffect.damageFlat;\r\n let baseHealRatio = abilityEffect.damageRatio;\r\n\r\n let minHeal = healingAmplify * (1 + baseHealFlat);\r\n let maxHeal = healingAmplify * (baseHealRatio * magicMaxDamage + baseHealFlat);\r\n\r\n let heal = this.randomInt(minHeal, maxHeal);\r\n let amountHealed = target.addHitpoints(heal);\r\n\r\n return amountHealed;\r\n }\r\n\r\n static processRevive(source, abilityEffect, target) {\r\n if (abilityEffect.combatStyleHrid != \"/combat_styles/magic\") {\r\n throw new Error(\"Heal ability effect not supported for combat style: \" + abilityEffect.combatStyleHrid);\r\n }\r\n\r\n let healingAmplify = 1 + source.combatDetails.combatStats.healingAmplify;\r\n let magicMaxDamage = source.combatDetails.magicMaxDamage;\r\n\r\n let baseHealFlat = abilityEffect.damageFlat;\r\n let baseHealRatio = abilityEffect.damageRatio;\r\n\r\n let minHeal = healingAmplify * (1 + baseHealFlat);\r\n let maxHeal = healingAmplify * (baseHealRatio * magicMaxDamage + baseHealFlat);\r\n\r\n let heal = this.randomInt(minHeal, maxHeal);\r\n let amountHealed = target.addHitpoints(heal);\r\n target.combatDetails.currentManapoints = target.combatDetails.maxManapoints;\r\n target.clearCCs();\r\n\r\n // target.clearBuffs();\r\n\r\n return amountHealed;\r\n }\r\n\r\n static processSpendHp(source, abilityEffect) {\r\n let currentHp = source.combatDetails.currentHitpoints;\r\n let spendHpRatio = abilityEffect.spendHpRatio;\r\n\r\n let spentHp = Math.floor(currentHp * spendHpRatio);\r\n\r\n source.combatDetails.currentHitpoints -= spentHp;\r\n\r\n return spentHp;\r\n }\r\n\r\n static calculateTickValue(totalValue, totalTicks, currentTick) {\r\n let currentSum = Math.floor((currentTick * totalValue) / totalTicks);\r\n let previousSum = Math.floor(((currentTick - 1) * totalValue) / totalTicks);\r\n\r\n return currentSum - previousSum;\r\n }\r\n}\r\n\r\nexport default CombatUtilities;\r\n","import Buff from \"./buff\";\r\nimport itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nclass Consumable {\r\n constructor(hrid, triggers = null) {\r\n this.hrid = hrid;\r\n\r\n let gameConsumable = itemDetailMap[this.hrid];\r\n if (!gameConsumable) {\r\n throw new Error(\"No consumable found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.cooldownDuration = gameConsumable.consumableDetail.cooldownDuration;\r\n this.hitpointRestore = gameConsumable.consumableDetail.hitpointRestore;\r\n this.manapointRestore = gameConsumable.consumableDetail.manapointRestore;\r\n this.recoveryDuration = gameConsumable.consumableDetail.recoveryDuration;\r\n this.catagoryHrid = gameConsumable.categoryHrid;\r\n\r\n this.buffs = [];\r\n if (gameConsumable.consumableDetail.buffs) {\r\n for (const consumableBuff of gameConsumable.consumableDetail.buffs) {\r\n let buff = new Buff(consumableBuff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameConsumable.consumableDetail.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let consumable = new Consumable(dto.hrid, triggers);\r\n\r\n return consumable;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n let consumableHaste;\r\n if (this.catagoryHrid.includes(\"food\")) {\r\n consumableHaste = source.combatDetails.combatStats.foodHaste\r\n } else {\r\n consumableHaste = source.combatDetails.combatStats.drinkConcentration;\r\n }\r\n let cooldownDuration = this.cooldownDuration;\r\n if (consumableHaste > 0) {\r\n cooldownDuration = cooldownDuration / (1 + consumableHaste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Consumable;\r\n","class Drops {\r\n\r\n constructor(itemHrid, dropRate, minCount, maxCount, difficultyTier) {\r\n this.itemHrid = itemHrid;\r\n this.dropRate = dropRate;\r\n this.minCount = minCount;\r\n this.maxCount = maxCount;\r\n this.difficultyTier = difficultyTier;\r\n }\r\n}\r\n\r\nexport default Drops;\r\n","import itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport enhancementLevelTotalMultiplierTable from \"./data/enhancementLevelTotalBonusMultiplierTable.json\";\r\n\r\nclass Equipment {\r\n constructor(hrid, enhancementLevel) {\r\n this.hrid = hrid;\r\n let gameItem = itemDetailMap[this.hrid];\r\n if (!gameItem) {\r\n throw new Error(\"No equipment found for hrid: \" + this.hrid);\r\n }\r\n this.gameItem = gameItem;\r\n this.enhancementLevel = enhancementLevel;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let equipment = new Equipment(dto.hrid, dto.enhancementLevel);\r\n\r\n return equipment;\r\n }\r\n\r\n getCombatStat(combatStat) {\r\n let multiplier = enhancementLevelTotalMultiplierTable[this.enhancementLevel];\r\n if(this.gameItem.equipmentDetail.combatStats[combatStat]) {\r\n let enhancementBonus = this.gameItem.equipmentDetail.combatEnhancementBonuses[combatStat] || 0;\r\n let stat = this.gameItem.equipmentDetail.combatStats[combatStat] + multiplier * enhancementBonus;\r\n return stat;\r\n }\r\n return 0;\r\n }\r\n\r\n getCombatStyle() {\r\n return this.gameItem.equipmentDetail.combatStats.combatStyleHrids[0];\r\n }\r\n\r\n getDamageType() {\r\n return this.gameItem.equipmentDetail.combatStats.damageType;\r\n }\r\n\r\n getPrimaryTraining() {\r\n return this.gameItem.equipmentDetail.combatStats.primaryTraining;\r\n }\r\n\r\n getFocusTraining(){\r\n return this.gameItem.equipmentDetail.combatStats.focusTraining;\r\n }\r\n}\r\n\r\nexport default Equipment;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass AbilityCastEndEvent extends CombatEvent {\r\n static type = \"abilityCastEndEvent\";\r\n\r\n constructor(time, source, ability) {\r\n super(AbilityCastEndEvent.type, time);\r\n\r\n this.source = source;\r\n this.ability = ability;\r\n }\r\n}\r\n\r\nexport default AbilityCastEndEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass AutoAttackEvent extends CombatEvent {\r\n static type = \"autoAttack\";\r\n\r\n constructor(time, source) {\r\n super(AutoAttackEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default AutoAttackEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass AwaitCooldownEvent extends CombatEvent {\r\n static type = \"awaitCooldownEvent\";\r\n\r\n constructor(time, source) {\r\n super(AwaitCooldownEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default AwaitCooldownEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass BlindExpirationEvent extends CombatEvent {\r\n static type = \"blindExpiration\";\r\n\r\n constructor(time, source) {\r\n super(BlindExpirationEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default BlindExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass CheckBuffExpirationEvent extends CombatEvent {\r\n static type = \"checkBuffExpiration\";\r\n\r\n constructor(time, source) {\r\n super(CheckBuffExpirationEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default CheckBuffExpirationEvent;\r\n","class CombatEvent {\r\n constructor(type, time) {\r\n this.type = type;\r\n this.time = time;\r\n }\r\n}\r\n\r\nexport default CombatEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass CombatStartEvent extends CombatEvent {\r\n static type = \"combatStart\";\r\n\r\n constructor(time) {\r\n super(CombatStartEvent.type, time);\r\n }\r\n}\r\n\r\nexport default CombatStartEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass ConsumableTickEvent extends CombatEvent {\r\n static type = \"consumableTick\";\r\n\r\n constructor(time, source, consumable, totalTicks, currentTick) {\r\n super(ConsumableTickEvent.type, time);\r\n\r\n this.source = source;\r\n this.consumable = consumable;\r\n this.totalTicks = totalTicks;\r\n this.currentTick = currentTick;\r\n }\r\n}\r\n\r\nexport default ConsumableTickEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass CooldownReadyEvent extends CombatEvent {\r\n static type = \"cooldownReady\";\r\n\r\n constructor(time) {\r\n super(CooldownReadyEvent.type, time);\r\n }\r\n}\r\n\r\nexport default CooldownReadyEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass CurseExpirationEvent extends CombatEvent {\r\n static type = \"curseExpiration\";\r\n static maxCurseStacks = 5;\r\n\r\n constructor(time, curseAmount, source) {\r\n super(CurseExpirationEvent.type, time);\r\n\r\n this.curseAmount = Math.min(curseAmount + 1, CurseExpirationEvent.maxCurseStacks);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default CurseExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass DamageOverTimeEvent extends CombatEvent {\r\n static type = \"damageOverTime\";\r\n\r\n constructor(time, sourceRef, target, damage, totalTicks, currentTick, combatStyleHrid) {\r\n super(DamageOverTimeEvent.type, time);\r\n\r\n // Calling it 'source' would wrongly clear Damage Over Time when the source dies\r\n this.sourceRef = sourceRef;\r\n this.target = target;\r\n this.damage = damage;\r\n this.totalTicks = totalTicks;\r\n this.currentTick = currentTick;\r\n this.combatStyleHrid = combatStyleHrid;\r\n }\r\n}\r\n\r\nexport default DamageOverTimeEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass EnemyRespawnEvent extends CombatEvent {\r\n static type = \"enemyRespawn\";\r\n\r\n constructor(time) {\r\n super(EnemyRespawnEvent.type, time);\r\n }\r\n}\r\n\r\nexport default EnemyRespawnEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass EnrageTickEvent extends CombatEvent {\r\n static type = \"enrageTick\";\r\n\r\n constructor(time, encounterTime) {\r\n\r\n super(EnrageTickEvent.type, time);\r\n\r\n this.encounterTime = encounterTime;\r\n }\r\n}\r\n\r\nexport default EnrageTickEvent;\r\n","import Heap from \"heap-js\";\r\n\r\nclass EventQueue {\r\n constructor() {\r\n this.minHeap = new Heap((a, b) => a.time - b.time);\r\n }\r\n\r\n addEvent(event) {\r\n this.minHeap.push(event);\r\n }\r\n\r\n getNextEvent() {\r\n return this.minHeap.pop();\r\n }\r\n\r\n containsEventOfType(type) {\r\n let heapEvents = this.minHeap.toArray();\r\n\r\n return heapEvents.some((event) => event.type == type);\r\n }\r\n\r\n containsEventOfTypeAndHrid(type, hrid) {\r\n let heapEvents = this.minHeap.toArray();\r\n return heapEvents.some((event) => event.type == type && event.hrid == hrid);\r\n }\r\n\r\n clear() {\r\n this.minHeap = new Heap((a, b) => a.time - b.time);\r\n }\r\n\r\n clearEventsForUnit(unit) {\r\n this.clearMatching((event) => event.source == unit || event.target == unit);\r\n }\r\n\r\n clearEventsOfType(type) {\r\n this.clearMatching((event) => event.type == type);\r\n }\r\n\r\n clearMatching(fn) {\r\n let cleared = false;\r\n let heapEvents = this.minHeap.toArray();\r\n\r\n for (const event of heapEvents) {\r\n if (fn(event)) {\r\n this.minHeap.remove(event);\r\n cleared = true;\r\n }\r\n }\r\n return cleared;\r\n }\r\n\r\n getMatching(fn) {\r\n let heapEvents = this.minHeap.toArray(); \r\n \r\n for (const event of heapEvents) {\r\n if (fn(event)) {\r\n return event; \r\n }\r\n }\r\n \r\n return null; \r\n }\r\n}\r\n\r\nexport default EventQueue;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass FuryExpirationEvent extends CombatEvent {\r\n static type = \"furyExpiration\";\r\n\r\n constructor(time, furyAmount, source) {\r\n super(FuryExpirationEvent.type, time);\r\n \r\n this.furyAmount = furyAmount;\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default FuryExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass PlayerRespawnEvent extends CombatEvent {\r\n static type = \"playerRespawn\";\r\n\r\n constructor(time, hrid) {\r\n super(PlayerRespawnEvent.type, time);\r\n this.hrid = hrid;\r\n }\r\n}\r\n\r\nexport default PlayerRespawnEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass RegenTickEvent extends CombatEvent {\r\n static type = \"regenTick\";\r\n\r\n constructor(time) {\r\n super(RegenTickEvent.type, time);\r\n }\r\n}\r\n\r\nexport default RegenTickEvent;\r\n","import CombatEvent from \"./combatEvent\";\r\n\r\nclass SilenceExpirationEvent extends CombatEvent {\r\n static type = \"silenceExpiration\";\r\n\r\n constructor(time, source) {\r\n super(SilenceExpirationEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default SilenceExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass StunExpirationEvent extends CombatEvent {\r\n static type = \"stunExpiration\";\r\n\r\n constructor(time, source) {\r\n super(StunExpirationEvent.type, time);\r\n\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default StunExpirationEvent;","import CombatEvent from \"./combatEvent\";\r\n\r\nclass WeakenExpirationEvent extends CombatEvent {\r\n static type = \"weakenExpiration\";\r\n static maxWeakenStacks = 5;\r\n\r\n constructor(time, weakenAmount, source) {\r\n super(WeakenExpirationEvent.type, time);\r\n this.weakenAmount = Math.min(\r\n weakenAmount + 1,\r\n WeakenExpirationEvent.maxWeakenStacks\r\n );\r\n this.source = source;\r\n }\r\n}\r\n\r\nexport default WeakenExpirationEvent;","import Buff from \"./buff\";\r\nimport houseRoomDetailMap from \"./data/houseRoomDetailMap.json\";\r\n\r\nclass HouseRoom {\r\n constructor(hrid, level) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameHouseRoom = houseRoomDetailMap[this.hrid];\r\n if (!gameHouseRoom) {\r\n throw new Error(\"No house room found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.buffs = [];\r\n if (gameHouseRoom.actionBuffs) {\r\n for (const actionBuff of gameHouseRoom.actionBuffs) {\r\n let buff = new Buff(actionBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n if (gameHouseRoom.globalBuffs) {\r\n for (const globalBuff of gameHouseRoom.globalBuffs) {\r\n let buff = new Buff(globalBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default HouseRoom;","import Ability from \"./ability\";\r\nimport CombatUnit from \"./combatUnit\";\r\nimport combatMonsterDetailMap from \"./data/combatMonsterDetailMap.json\";\r\nimport Drops from \"./drops\";\r\n\r\nclass Monster extends CombatUnit {\r\n\r\n difficultyTier = 0;\r\n\r\n constructor(hrid, difficultyTier = 0) {\r\n super();\r\n\r\n this.isPlayer = false;\r\n this.hrid = hrid;\r\n this.difficultyTier = difficultyTier;\r\n\r\n let gameMonster = combatMonsterDetailMap[this.hrid];\r\n if (!gameMonster) {\r\n throw new Error(\"No monster found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.enrageTime = gameMonster.enrageTime;\r\n\r\n for (let i = 0; i < gameMonster.abilities.length; i++) {\r\n if (gameMonster.abilities[i].minDifficultyTier > this.difficultyTier) {\r\n continue;\r\n }\r\n this.abilities[i] = new Ability(gameMonster.abilities[i].abilityHrid, gameMonster.abilities[i].level);\r\n }\r\n if(gameMonster.dropTable)\r\n for (let i = 0; i < gameMonster.dropTable.length; i++) {\r\n this.dropTable[i] = new Drops(gameMonster.dropTable[i].itemHrid, gameMonster.dropTable[i].dropRate, gameMonster.dropTable[i].minCount, gameMonster.dropTable[i].maxCount, gameMonster.dropTable[i].difficultyTier);\r\n }\r\n for (let i = 0; i < gameMonster.rareDropTable.length; i++) {\r\n let dropTableItem = (gameMonster.dropTable && i < gameMonster.dropTable.length) ? gameMonster.dropTable[i] : null;\r\n let difficultyTier = dropTableItem?.difficultyTier ?? gameMonster.rareDropTable[i].minDifficultyTier;\r\n\r\n this.rareDropTable[i] = new Drops(gameMonster.rareDropTable[i].itemHrid, gameMonster.rareDropTable[i].dropRate, gameMonster.rareDropTable[i].minCount, difficultyTier);\r\n }\r\n }\r\n\r\n updateCombatDetails() {\r\n let gameMonster = combatMonsterDetailMap[this.hrid];\r\n\r\n let levelMultiplier = 1.0 + 0.25 * this.difficultyTier;\r\n let defLevelMultiplier = 1.0 + 0.15 * this.difficultyTier;\r\n let levelBonus = 20.0 * this.difficultyTier;\r\n\r\n this.staminaLevel = levelMultiplier * (gameMonster.combatDetails.staminaLevel + levelBonus);\r\n this.intelligenceLevel = levelMultiplier * (gameMonster.combatDetails.intelligenceLevel + levelBonus);\r\n this.attackLevel = levelMultiplier * (gameMonster.combatDetails.attackLevel + levelBonus);\r\n this.meleeLevel = levelMultiplier * (gameMonster.combatDetails.meleeLevel + levelBonus);\r\n this.defenseLevel = defLevelMultiplier * (gameMonster.combatDetails.defenseLevel + levelBonus);\r\n this.rangedLevel = levelMultiplier * (gameMonster.combatDetails.rangedLevel + levelBonus);\r\n this.magicLevel = levelMultiplier * (gameMonster.combatDetails.magicLevel + levelBonus);\r\n\r\n \r\n let expMultiplier = 1.0 + 0.5 * this.difficultyTier;\r\n let expBonus = 5.0 * this.difficultyTier;\r\n\r\n this.experience = expMultiplier * (gameMonster.experience + expBonus);\r\n\r\n this.combatDetails.combatStats.combatStyleHrid = gameMonster.combatDetails.combatStats.combatStyleHrids[0];\r\n\r\n for (const [key, value] of Object.entries(gameMonster.combatDetails.combatStats)) {\r\n this.combatDetails.combatStats[key] = value;\r\n }\r\n\r\n [\r\n \"stabAccuracy\",\r\n \"slashAccuracy\",\r\n \"smashAccuracy\",\r\n \"rangedAccuracy\",\r\n \"magicAccuracy\",\r\n \"stabDamage\",\r\n \"slashDamage\",\r\n \"smashDamage\",\r\n \"rangedDamage\",\r\n \"magicDamage\",\r\n \"defensiveDamage\",\r\n \"taskDamage\",\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"stabEvasion\",\r\n \"slashEvasion\",\r\n \"smashEvasion\",\r\n \"rangedEvasion\",\r\n \"magicEvasion\",\r\n \"armor\",\r\n \"waterResistance\",\r\n \"natureResistance\",\r\n \"fireResistance\",\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"combatDropRate\",\r\n \"combatRareFind\",\r\n \"combatDropQuantity\",\r\n \"combatExperience\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"abilityHaste\",\r\n \"tenacity\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"threat\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"foodHaste\",\r\n \"drinkConcentration\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"retaliation\"\r\n ].forEach((stat) => {\r\n if (gameMonster.combatDetails.combatStats[stat] == null) {\r\n this.combatDetails.combatStats[stat] = 0;\r\n }\r\n });\r\n\r\n if (this.combatDetails.combatStats.attackInterval == 0) {\r\n this.combatDetails.combatStats.attackInterval = gameMonster.combatDetails.attackInterval;\r\n }\r\n\r\n super.updateCombatDetails();\r\n }\r\n}\r\n\r\nexport default Monster;\r\n","import Ability from \"./ability\";\r\nimport CombatUnit from \"./combatUnit\";\r\nimport Consumable from \"./consumable\";\r\nimport Equipment from \"./equipment\";\r\nimport HouseRoom from \"./houseRoom\";\r\nimport Achievement from \"./achievement\";\r\n\r\nclass Player extends CombatUnit {\r\n equipment = {\r\n \"/equipment_types/head\": null,\r\n \"/equipment_types/body\": null,\r\n \"/equipment_types/legs\": null,\r\n \"/equipment_types/feet\": null,\r\n \"/equipment_types/hands\": null,\r\n \"/equipment_types/main_hand\": null,\r\n \"/equipment_types/two_hand\": null,\r\n \"/equipment_types/off_hand\": null,\r\n \"/equipment_types/pouch\": null,\r\n \"/equipment_types/back\": null,\r\n };\r\n\r\n constructor() {\r\n super();\r\n\r\n this.isPlayer = true;\r\n this.hrid = \"player\";\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let player = new Player();\r\n\r\n player.staminaLevel = dto.staminaLevel;\r\n player.intelligenceLevel = dto.intelligenceLevel;\r\n player.attackLevel = dto.attackLevel;\r\n player.meleeLevel = dto.meleeLevel;\r\n player.defenseLevel = dto.defenseLevel;\r\n player.rangedLevel = dto.rangedLevel;\r\n player.magicLevel = dto.magicLevel;\r\n\r\n player.hrid = dto.hrid;\r\n\r\n for (const [key, value] of Object.entries(dto.equipment)) {\r\n player.equipment[key] = value ? Equipment.createFromDTO(value) : null;\r\n }\r\n\r\n player.food = dto.food.map((food) => (food ? Consumable.createFromDTO(food) : null));\r\n player.drinks = dto.drinks.map((drink) => (drink ? Consumable.createFromDTO(drink) : null));\r\n player.abilities = dto.abilities.map((ability) => (ability ? Ability.createFromDTO(ability) : null));\r\n Object.entries(dto.houseRooms).forEach(houseRoom => {\r\n if (houseRoom[1] > 0) {\r\n player.houseRooms.push(new HouseRoom(houseRoom[0], houseRoom[1]))\r\n }\r\n });\r\n\r\n player.achievements = new Achievement(dto.achievements);\r\n\r\n player.debuffOnLevelGap = dto.debuffOnLevelGap;\r\n\r\n return player;\r\n }\r\n\r\n updateCombatDetails() {\r\n if (this.equipment[\"/equipment_types/main_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/main_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/main_hand\"].getPrimaryTraining();\r\n } else if (this.equipment[\"/equipment_types/two_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/two_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/two_hand\"].getPrimaryTraining();\r\n } else {\r\n this.combatDetails.combatStats.combatStyleHrid = \"/combat_styles/smash\";\r\n this.combatDetails.combatStats.damageType = \"/damage_types/physical\";\r\n this.combatDetails.combatStats.attackInterval = 3000000000;\r\n this.combatDetails.combatStats.primaryTraining = \"/skills/melee\";\r\n }\r\n\r\n if (this.equipment[\"/equipment_types/charm\"]) {\r\n this.combatDetails.combatStats.focusTraining = this.equipment[\"/equipment_types/charm\"].getFocusTraining();\r\n } else {\r\n this.combatDetails.combatStats.focusTraining = \"\";\r\n }\r\n\r\n [\r\n \"stabAccuracy\",\r\n \"slashAccuracy\",\r\n \"smashAccuracy\",\r\n \"rangedAccuracy\",\r\n \"magicAccuracy\",\r\n \"stabDamage\",\r\n \"slashDamage\",\r\n \"smashDamage\",\r\n \"rangedDamage\",\r\n \"magicDamage\",\r\n \"defensiveDamage\",\r\n \"taskDamage\",\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"stabEvasion\",\r\n \"slashEvasion\",\r\n \"smashEvasion\",\r\n \"rangedEvasion\",\r\n \"magicEvasion\",\r\n \"armor\",\r\n \"waterResistance\",\r\n \"natureResistance\",\r\n \"fireResistance\",\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"combatDropRate\",\r\n \"combatRareFind\",\r\n \"combatDropQuantity\",\r\n \"combatExperience\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"abilityHaste\",\r\n \"tenacity\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"threat\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"foodHaste\",\r\n \"drinkConcentration\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\",\r\n \"retaliation\"\r\n ].forEach((stat) => {\r\n this.combatDetails.combatStats[stat] = Object.values(this.equipment)\r\n .filter((equipment) => equipment != null)\r\n .map((equipment) => equipment.getCombatStat(stat))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n });\r\n\r\n if (this.equipment[\"/equipment_types/pouch\"]) {\r\n this.combatDetails.combatStats.foodSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"foodSlots\");\r\n this.combatDetails.combatStats.drinkSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"drinkSlots\");\r\n } else {\r\n this.combatDetails.combatStats.foodSlots = 1;\r\n this.combatDetails.combatStats.drinkSlots = 1;\r\n }\r\n\r\n super.updateCombatDetails();\r\n }\r\n}\r\n\r\nexport default Player;\r\n","import combatStyleDetailMap from \"./data/combatStyleDetailMap.json\"\r\n\r\nclass SimResult {\r\n constructor(zone, numberOfPlayers) {\r\n this.deaths = {};\r\n this.experienceGained = {};\r\n this.encounters = 0;\r\n this.attacks = {};\r\n this.consumablesUsed = {};\r\n this.hitpointsGained = {};\r\n this.manapointsGained = {};\r\n this.debuffOnLevelGap = {};\r\n this.dropRateMultiplier = {};\r\n this.rareFindMultiplier = {};\r\n this.combatDropQuantity = {};\r\n this.playerRanOutOfMana = {\r\n \"player1\": false,\r\n \"player2\": false,\r\n \"player3\": false,\r\n \"player4\": false,\r\n \"player5\": false\r\n };\r\n this.playerRanOutOfManaTime = {};\r\n this.manaUsed = {};\r\n this.timeSpentAlive = [];\r\n this.bossSpawns = [];\r\n this.hitpointsSpent = {};\r\n this.zoneName = zone.hrid;\r\n this.difficultyTier = zone.difficultyTier;\r\n this.isDungeon = false;\r\n this.dungeonsCompleted = 0;\r\n this.dungeonsFailed = 0;\r\n this.maxWaveReached = 0;\r\n this.numberOfPlayers = numberOfPlayers;\r\n this.maxEnrageStack = 0;\r\n this.minDungenonTime = 0;\r\n\r\n this.wipeEvents = [];\r\n }\r\n\r\n addWipeEvent(logs, simulationTime, wave) {\r\n this.wipeEvents.push({\r\n simulationTime: simulationTime,\r\n logs: logs,\r\n wave: wave,\r\n timestamp: new Date().toISOString()\r\n });\r\n }\r\n \r\n addDeath(unit) {\r\n if (!this.deaths[unit.hrid]) {\r\n this.deaths[unit.hrid] = 0;\r\n }\r\n\r\n this.deaths[unit.hrid] += 1;\r\n }\r\n\r\n updateTimeSpentAlive(name, alive, time) {\r\n const i = this.timeSpentAlive.findIndex(e => e.name === name);\r\n if (alive) {\r\n if (i !== -1) {\r\n this.timeSpentAlive[i].alive = true;\r\n this.timeSpentAlive[i].spawnedAt = time;\r\n } else {\r\n this.timeSpentAlive.push({ name: name, timeSpentAlive: 0, spawnedAt: time, alive: true, count: 0 });\r\n }\r\n } else {\r\n const timeAlive = time - this.timeSpentAlive[i].spawnedAt;\r\n this.timeSpentAlive[i].alive = false;\r\n this.timeSpentAlive[i].timeSpentAlive += timeAlive;\r\n this.timeSpentAlive[i].count += 1;\r\n }\r\n }\r\n\r\n updateDungenonFinish(beginFlag, finishTime) {\r\n const i = this.timeSpentAlive.findIndex(e => e.name === beginFlag); \r\n if (i == -1) {\r\n return;\r\n }\r\n\r\n const currentDungenonTime = finishTime - this.timeSpentAlive[i].spawnedAt;\r\n\r\n if (this.minDungenonTime == 0 || this.minDungenonTime > currentDungenonTime) {\r\n this.minDungenonTime = currentDungenonTime;\r\n }\r\n }\r\n\r\n addExperienceGain(unit, experience) {\r\n if (!unit.isPlayer) {\r\n return;\r\n }\r\n\r\n if (!this.experienceGained[unit.hrid]) {\r\n this.experienceGained[unit.hrid] = {\r\n stamina: 0,\r\n intelligence: 0,\r\n attack: 0,\r\n melee: 0,\r\n defense: 0,\r\n ranged: 0,\r\n magic: 0,\r\n };\r\n }\r\n\r\n let experienceGainedRate = {\r\n \"stamina\": 0,\r\n \"intelligence\": 0,\r\n \"attack\": 0,\r\n \"melee\": 0,\r\n \"defense\": 0,\r\n \"ranged\": 0,\r\n \"magic\": 0,\r\n };\r\n\r\n const primaryTraining = unit.combatDetails.combatStats.primaryTraining;\r\n experienceGainedRate[primaryTraining.split(\"/\")[2]] = .3;\r\n\r\n const skillExpMap = combatStyleDetailMap[unit.combatDetails.combatStats.combatStyleHrid].skillExpMap;\r\n const skillExpMapLength = Object.keys(skillExpMap).length;\r\n\r\n const focusTraining = unit.combatDetails.combatStats.focusTraining;\r\n if (focusTraining && skillExpMap[focusTraining]) {\r\n experienceGainedRate[focusTraining.split(\"/\")[2]] += .7;\r\n } else {\r\n Object.keys(skillExpMap).forEach(skillHrid => {\r\n experienceGainedRate[skillHrid.split(\"/\")[2]] += .7 / skillExpMapLength;\r\n });\r\n }\r\n\r\n for (const [type, rate] of Object.entries(experienceGainedRate)) {\r\n if (rate <= 0) continue;\r\n\r\n const skillExperience = rate * (1 + unit.combatDetails.combatStats[type + \"Experience\"]);\r\n\r\n this.experienceGained[unit.hrid][type] += (\r\n experience\r\n * (1 + unit.combatDetails.combatStats.combatExperience)\r\n * skillExperience\r\n * (1 + unit.debuffOnLevelGap)\r\n\r\n );\r\n }\r\n }\r\n\r\n addEncounterEnd() {\r\n this.encounters++;\r\n }\r\n\r\n addAttack(source, target, ability, hit) {\r\n if (!this.attacks[source.hrid]) {\r\n this.attacks[source.hrid] = {};\r\n }\r\n if (!this.attacks[source.hrid][target.hrid]) {\r\n this.attacks[source.hrid][target.hrid] = {};\r\n }\r\n if (!this.attacks[source.hrid][target.hrid][ability]) {\r\n this.attacks[source.hrid][target.hrid][ability] = {};\r\n }\r\n\r\n if (!this.attacks[source.hrid][target.hrid][ability][hit]) {\r\n this.attacks[source.hrid][target.hrid][ability][hit] = 0;\r\n }\r\n\r\n this.attacks[source.hrid][target.hrid][ability][hit] += 1;\r\n }\r\n\r\n addConsumableUse(unit, consumable) {\r\n if (!this.consumablesUsed[unit.hrid]) {\r\n this.consumablesUsed[unit.hrid] = {};\r\n }\r\n if (!this.consumablesUsed[unit.hrid][consumable.hrid]) {\r\n this.consumablesUsed[unit.hrid][consumable.hrid] = 0;\r\n }\r\n\r\n this.consumablesUsed[unit.hrid][consumable.hrid] += 1;\r\n }\r\n\r\n addHitpointsGained(unit, source, amount) {\r\n if (!this.hitpointsGained[unit.hrid]) {\r\n this.hitpointsGained[unit.hrid] = {};\r\n }\r\n if (!this.hitpointsGained[unit.hrid][source]) {\r\n this.hitpointsGained[unit.hrid][source] = 0;\r\n }\r\n\r\n this.hitpointsGained[unit.hrid][source] += amount;\r\n }\r\n\r\n addManapointsGained(unit, source, amount) {\r\n if (!this.manapointsGained[unit.hrid]) {\r\n this.manapointsGained[unit.hrid] = {};\r\n }\r\n if (!this.manapointsGained[unit.hrid][source]) {\r\n this.manapointsGained[unit.hrid][source] = 0;\r\n }\r\n\r\n this.manapointsGained[unit.hrid][source] += amount;\r\n }\r\n\r\n setDropRateMultipliers(unit) {\r\n if (!this.dropRateMultiplier[unit.hrid]) {\r\n this.dropRateMultiplier[unit.hrid] = {};\r\n }\r\n this.dropRateMultiplier[unit.hrid] = 1 + unit.combatDetails.combatStats.combatDropRate;\r\n\r\n if (!this.rareFindMultiplier[unit.hrid]) {\r\n this.rareFindMultiplier[unit.hrid] = {};\r\n }\r\n this.rareFindMultiplier[unit.hrid] = 1 + unit.combatDetails.combatStats.combatRareFind;\r\n\r\n if (!this.combatDropQuantity[unit.hrid]) {\r\n this.combatDropQuantity[unit.hrid] = {};\r\n }\r\n this.combatDropQuantity[unit.hrid] = unit.combatDetails.combatStats.combatDropQuantity;\r\n\r\n if (!this.debuffOnLevelGap[unit.hrid]) {\r\n this.debuffOnLevelGap[unit.hrid] = {};\r\n }\r\n this.debuffOnLevelGap[unit.hrid] = unit.debuffOnLevelGap;\r\n }\r\n\r\n setManaUsed(unit) {\r\n this.manaUsed[unit.hrid] = {};\r\n for (let [key, value] of unit.abilityManaCosts.entries()) {\r\n this.manaUsed[unit.hrid][key] = value;\r\n }\r\n }\r\n\r\n addHitpointsSpent(unit, source, amount) {\r\n if (!this.hitpointsSpent[unit.hrid]) {\r\n this.hitpointsSpent[unit.hrid] = {};\r\n }\r\n if (!this.hitpointsSpent[unit.hrid][source]) {\r\n this.hitpointsSpent[unit.hrid][source] = 0;\r\n }\r\n\r\n this.hitpointsSpent[unit.hrid][source] += amount;\r\n }\r\n\r\n addRanOutOfManaCount(unit, isOutOfMana, time) {\r\n if (isOutOfMana) this.playerRanOutOfMana[unit.hrid] = true;\r\n\r\n if (!this.playerRanOutOfManaTime[unit.hrid]) {\r\n this.playerRanOutOfManaTime[unit.hrid] = {isOutOfMana: false, startTimeForOutOfMana:0, totalTimeForOutOfMana:0};\r\n }\r\n\r\n if (isOutOfMana) {\r\n if (!this.playerRanOutOfManaTime[unit.hrid].isOutOfMana) {\r\n this.playerRanOutOfManaTime[unit.hrid].isOutOfMana = true;\r\n this.playerRanOutOfManaTime[unit.hrid].startTimeForOutOfMana = time;\r\n }\r\n } else {\r\n if (this.playerRanOutOfManaTime[unit.hrid].isOutOfMana) {\r\n this.playerRanOutOfManaTime[unit.hrid].isOutOfMana = false;\r\n this.playerRanOutOfManaTime[unit.hrid].totalTimeForOutOfMana += time - this.playerRanOutOfManaTime[unit.hrid].startTimeForOutOfMana;\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default SimResult;\r\n","import combatTriggerDependencyDetailMap from \"./data/combatTriggerDependencyDetailMap.json\";\r\n\r\nclass Trigger {\r\n constructor(dependencyHrid, conditionHrid, comparatorHrid, value = 0) {\r\n this.dependencyHrid = dependencyHrid;\r\n this.conditionHrid = conditionHrid;\r\n this.comparatorHrid = comparatorHrid;\r\n this.value = value;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let trigger = new Trigger(dto.dependencyHrid, dto.conditionHrid, dto.comparatorHrid, dto.value);\r\n\r\n return trigger;\r\n }\r\n\r\n isActive(source, target, friendlies, enemies, currentTime) {\r\n if (combatTriggerDependencyDetailMap[this.dependencyHrid].isSingleTarget) {\r\n return this.isActiveSingleTarget(source, target, currentTime);\r\n } else {\r\n return this.isActiveMultiTarget(friendlies, enemies, currentTime);\r\n }\r\n }\r\n\r\n isActiveSingleTarget(source, target, currentTime) {\r\n let dependencyValue;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/self\":\r\n dependencyValue = this.getDependencyValue(source, currentTime);\r\n break;\r\n case \"/combat_trigger_dependencies/targeted_enemy\":\r\n if (!target) {\r\n return false;\r\n }\r\n dependencyValue = this.getDependencyValue(target, currentTime);\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n isActiveMultiTarget(friendlies, enemies, currentTime) {\r\n let dependency;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/all_allies\":\r\n dependency = friendlies;\r\n break;\r\n case \"/combat_trigger_dependencies/all_enemies\":\r\n if (!enemies) {\r\n return false;\r\n }\r\n dependency = enemies;\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n let dependencyValue;\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/number_of_active_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints > 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/number_of_dead_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints <= 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/lowest_hp_percentage\":\r\n dependencyValue = dependency.reduce((prev, curr) => {\r\n let currentHpPercentage = curr.combatDetails.currentHitpoints / curr.combatDetails.maxHitpoints;\r\n return currentHpPercentage < prev ? currentHpPercentage : prev;\r\n }, 2) * 100;\r\n break;\r\n default:\r\n dependencyValue = dependency\r\n .map((unit) => this.getDependencyValue(unit, currentTime))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n break;\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n getDependencyValue(source, currentTime) {\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/berserk\":\r\n case \"/combat_trigger_conditions/frenzy\":\r\n case \"/combat_trigger_conditions/precision\":\r\n case \"/combat_trigger_conditions/vampirism\":\r\n case \"/combat_trigger_conditions/attack_coffee\":\r\n case \"/combat_trigger_conditions/defense_coffee\":\r\n case \"/combat_trigger_conditions/lucky_coffee\":\r\n case \"/combat_trigger_conditions/magic_coffee\":\r\n case \"/combat_trigger_conditions/melee_coffee\":\r\n case \"/combat_trigger_conditions/ranged_coffee\":\r\n case \"/combat_trigger_conditions/swiftness_coffee\":\r\n case \"/combat_trigger_conditions/wisdom_coffee\":\r\n case \"/combat_trigger_conditions/ice_spear\":\r\n case \"/combat_trigger_conditions/puncture\":\r\n case \"/combat_trigger_conditions/frost_surge\":\r\n case \"/combat_trigger_conditions/elusiveness\":\r\n case \"/combat_trigger_conditions/channeling_coffee\":\r\n case \"/combat_trigger_conditions/fierce_aura\":\r\n case \"/combat_trigger_conditions/invincible_armor\":\r\n case \"/combat_trigger_conditions/invincible_fire_resistance\":\r\n case \"/combat_trigger_conditions/invincible_nature_resistance\":\r\n case \"/combat_trigger_conditions/invincible_water_resistance\":\r\n case \"/combat_trigger_conditions/provoke\":\r\n case \"/combat_trigger_conditions/taunt\":\r\n case \"/combat_trigger_conditions/crippling_slash\":\r\n case \"/combat_trigger_conditions/mana_spring\":\r\n case \"/combat_trigger_conditions/retribution\":\r\n case \"/combat_trigger_conditions/fracturing_impact\":\r\n case \"/combat_trigger_conditions/maim\":\r\n case \"/combat_trigger_conditions/curse\":\r\n case \"/combat_trigger_conditions/weaken\":\r\n let buffHrid = \"/buff_uniques\";\r\n buffHrid += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n return source.combatBuffs[buffHrid];\r\n case \"/combat_trigger_conditions/critical_aura\":\r\n case \"/combat_trigger_conditions/critical_coffee\":\r\n case \"/combat_trigger_conditions/intelligence_coffee\":\r\n case \"/combat_trigger_conditions/stamina_coffee\":\r\n case \"/combat_trigger_conditions/elemental_affinity\":\r\n case \"/combat_trigger_conditions/fury\":\r\n case \"/combat_trigger_conditions/guardian_aura\":\r\n case \"/combat_trigger_conditions/insanity\":\r\n case \"/combat_trigger_conditions/spike_shell\":\r\n case \"/combat_trigger_conditions/toxic_pollen\":\r\n case \"/combat_trigger_conditions/invincible\":\r\n case \"/combat_trigger_conditions/mystic_aura\":\r\n case \"/combat_trigger_conditions/pestilent_shot\":\r\n case \"/combat_trigger_conditions/smoke_burst\":\r\n case \"/combat_trigger_conditions/speed_aura\":\r\n case \"/combat_trigger_conditions/toughness\":\r\n case \"/combat_trigger_conditions/enrage\":\r\n let buffPrefix = \"/buff_uniques\";\r\n buffPrefix += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n let buffs = Object.keys(source.combatBuffs).filter(buff => buff.startsWith(buffPrefix));\r\n return source.combatBuffs[buffs?.[0]];\r\n case \"/combat_trigger_conditions/current_hp\":\r\n return source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/current_mp\":\r\n return source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/missing_hp\":\r\n return source.combatDetails.maxHitpoints - source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/missing_mp\":\r\n return source.combatDetails.maxManapoints - source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/stun_status\":\r\n // Replicate the game's behaviour of \"stun status active\" triggers activating\r\n // immediately after the stun has worn off\r\n return source.isStunned || source.stunExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/blind_status\":\r\n return source.isBlinded || source.blindExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/silence_status\":\r\n return source.isSilenced || source.silenceExpireTime == currentTime;\r\n default:\r\n throw new Error(\"Unknown conditionHrid in trigger: \" + this.conditionHrid);\r\n }\r\n }\r\n\r\n compareValue(dependencyValue) {\r\n switch (this.comparatorHrid) {\r\n case \"/combat_trigger_comparators/greater_than_equal\":\r\n return dependencyValue >= this.value;\r\n case \"/combat_trigger_comparators/less_than_equal\":\r\n return dependencyValue <= this.value;\r\n case \"/combat_trigger_comparators/is_active\":\r\n return !!dependencyValue;\r\n case \"/combat_trigger_comparators/is_inactive\":\r\n return !dependencyValue;\r\n default:\r\n throw new Error(\"Unknown comparatorHrid in trigger: \" + this.comparatorHrid);\r\n }\r\n }\r\n}\r\n\r\nexport default Trigger;\r\n","import actionDetailMap from \"./data/actionDetailMap.json\";\r\nimport Monster from \"./monster\";\r\n\r\nclass Zone {\r\n constructor(hrid, difficultyTier) {\r\n this.hrid = hrid;\r\n this.difficultyTier = difficultyTier;\r\n\r\n let gameZone = actionDetailMap[this.hrid];\r\n this.monsterSpawnInfo = gameZone.combatZoneInfo.fightInfo;\r\n this.dungeonSpawnInfo = gameZone.combatZoneInfo.dungeonInfo;\r\n this.encountersKilled = 1;\r\n this.monsterSpawnInfo.battlesPerBoss = 10;\r\n this.buffs = gameZone.buffs;\r\n this.isDungeon = gameZone.combatZoneInfo.isDungeon;\r\n this.dungeonsCompleted = 0;\r\n this.dungeonsFailed = 0;\r\n this.finalWave = false;\r\n }\r\n\r\n getRandomEncounter() {\r\n\r\n if (this.monsterSpawnInfo.bossSpawns && this.encountersKilled == this.monsterSpawnInfo.battlesPerBoss) {\r\n this.encountersKilled = 1;\r\n return this.monsterSpawnInfo.bossSpawns.map((monster) => new Monster(monster.combatMonsterHrid, monster.difficultyTier + this.difficultyTier));\r\n }\r\n\r\n let totalWeight = this.monsterSpawnInfo.randomSpawnInfo.spawns.reduce((prev, cur) => prev + cur.rate, 0);\r\n\r\n let encounterHrids = [];\r\n let totalStrength = 0;\r\n\r\n outer: for (let i = 0; i < this.monsterSpawnInfo.randomSpawnInfo.maxSpawnCount; i++) {\r\n let randomWeight = totalWeight * Math.random();\r\n let cumulativeWeight = 0;\r\n\r\n for (const spawn of this.monsterSpawnInfo.randomSpawnInfo.spawns) {\r\n cumulativeWeight += spawn.rate;\r\n if (randomWeight <= cumulativeWeight) {\r\n totalStrength += spawn.strength;\r\n\r\n if (totalStrength <= this.monsterSpawnInfo.randomSpawnInfo.maxTotalStrength) {\r\n encounterHrids.push({ 'hrid': spawn.combatMonsterHrid, 'difficultyTier': spawn.difficultyTier});\r\n\r\n } else {\r\n break outer;\r\n }\r\n break;\r\n }\r\n }\r\n }\r\n this.encountersKilled++;\r\n return encounterHrids.map((hrid) => new Monster(hrid.hrid, hrid.difficultyTier + this.difficultyTier));\r\n }\r\n\r\n failWave() {\r\n this.dungeonsFailed++;\r\n this.encountersKilled = 1;\r\n }\r\n\r\n getNextWave() {\r\n if (this.encountersKilled > this.dungeonSpawnInfo.maxWaves) {\r\n this.dungeonsCompleted++;\r\n this.encountersKilled = 1;\r\n }\r\n // console.log(\"Wave #\" + this.encountersKilled);\r\n if (this.dungeonSpawnInfo.fixedSpawnsMap.hasOwnProperty(this.encountersKilled.toString())) {\r\n let currentMonsters = this.dungeonSpawnInfo.fixedSpawnsMap[(this.encountersKilled).toString()];\r\n this.encountersKilled++;\r\n return currentMonsters.map((monster) => new Monster(monster.combatMonsterHrid, monster.difficultyTier + this.difficultyTier));\r\n } else {\r\n let monsterSpawns = {};\r\n const waveKeys = Object.keys(this.dungeonSpawnInfo.randomSpawnInfoMap).map(Number).sort((a, b) => a - b);\r\n if (this.encountersKilled > waveKeys[waveKeys.length - 1]) {\r\n monsterSpawns = this.dungeonSpawnInfo.randomSpawnInfoMap[waveKeys[waveKeys.length - 1]];\r\n } else {\r\n for (let i = 0; i < waveKeys.length - 1; i++) {\r\n if (this.encountersKilled >= waveKeys[i] && this.encountersKilled <= waveKeys[i + 1]) {\r\n monsterSpawns = this.dungeonSpawnInfo.randomSpawnInfoMap[waveKeys[i]];\r\n break;\r\n }\r\n }\r\n }\r\n let totalWeight = monsterSpawns.spawns.reduce((prev, cur) => prev + cur.rate, 0);\r\n\r\n let encounterHrids = [];\r\n let totalStrength = 0;\r\n\r\n outer: for (let i = 0; i < monsterSpawns.maxSpawnCount; i++) {\r\n let randomWeight = totalWeight * Math.random();\r\n let cumulativeWeight = 0;\r\n\r\n for (const spawn of monsterSpawns.spawns) {\r\n cumulativeWeight += spawn.rate;\r\n if (randomWeight <= cumulativeWeight) {\r\n totalStrength += spawn.strength;\r\n\r\n if (totalStrength <= monsterSpawns.maxTotalStrength) {\r\n encounterHrids.push({ 'hrid': spawn.combatMonsterHrid, 'difficultyTier': spawn.difficultyTier});\r\n\r\n } else {\r\n break outer;\r\n }\r\n break;\r\n }\r\n }\r\n }\r\n this.encountersKilled++;\r\n return encounterHrids.map((hrid) => new Monster(hrid.hrid, hrid.difficultyTier + this.difficultyTier));\r\n }\r\n }\r\n}\r\n\r\nexport default Zone;\r\n","import CombatSimulator from \"./combatsimulator/combatSimulator\";\r\nimport Player from \"./combatsimulator/player\";\r\nimport Zone from \"./combatsimulator/zone\";\r\n\r\n\r\nonmessage = async function (event) {\r\n switch (event.data.type) {\r\n case \"start_simulation\":\r\n let extraBuffs = [];\r\n if (event.data.extra.mooPass) {\r\n const mooPassBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/experience_moo_pass_buff\",\r\n \"typeHrid\": \"/buff_types/wisdom\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0.05,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": 0\r\n };\r\n extraBuffs.push(mooPassBuff);\r\n }\r\n if (event.data.extra.comExp > 0) {\r\n const comExpBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/experience_community_buff\",\r\n \"typeHrid\": \"/buff_types/wisdom\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0.005 * (event.data.extra.comExp - 1) + 0.2,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": 0\r\n };\r\n extraBuffs.push(comExpBuff);\r\n }\r\n if (event.data.extra.comDrop > 0) {\r\n const comDropBuff = {\r\n \"uniqueHrid\": \"/buff_uniques/combat_community_buff\",\r\n \"typeHrid\": \"/buff_types/combat_drop_quantity\",\r\n \"ratioBoost\": 0,\r\n \"ratioBoostLevelBonus\": 0,\r\n \"flatBoost\": 0.005 * (event.data.extra.comDrop - 1) + 0.2,\r\n \"flatBoostLevelBonus\": 0,\r\n \"startTime\": \"0001-01-01T00:00:00Z\",\r\n \"duration\": 0\r\n };\r\n extraBuffs.push(comDropBuff);\r\n }\r\n\r\n let playersData = event.data.players;\r\n let players = [];\r\n let zone = new Zone(event.data.zone.zoneHrid, event.data.zone.difficultyTier);\r\n for (let i = 0; i < playersData.length; i++) {\r\n let currentPlayer = Player.createFromDTO(structuredClone(playersData[i]));\r\n currentPlayer.zoneBuffs = zone.buffs;\r\n currentPlayer.extraBuffs = extraBuffs;\r\n players.push(currentPlayer);\r\n }\r\n let simulationTimeLimit = event.data.simulationTimeLimit;\r\n let combatSimulator = new CombatSimulator(players, zone);\r\n combatSimulator.addEventListener(\"progress\", (event) => {\r\n this.postMessage({ type: \"simulation_progress\", progress: event.detail.progress, zone: event.detail.zone, difficultyTier: event.detail.difficultyTier });\r\n });\r\n\r\n try {\r\n let simResult = await combatSimulator.simulate(simulationTimeLimit);\r\n this.postMessage({ type: \"simulation_result\", simResult: simResult });\r\n } catch (e) {\r\n console.log(e);\r\n this.postMessage({ type: \"simulation_error\", error: e });\r\n }\r\n break;\r\n }\r\n};\r\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/locales/en/common.json b/locales/en/common.json index b8137743..0c8280db 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -71,6 +71,7 @@ "dungeonsCompleted": "Dungeons Completed", "dungeonsFailed": "Dungeons Failed", "averageTime": "Average Time", + "minimumTime": "Minimum Time", "maxEnrageStack": "Max Stack of Enrage", "debuffOnLevelGap": "Debuff on Level Gap" }, diff --git a/locales/zh/common.json b/locales/zh/common.json index 668cb56f..1e709046 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -70,7 +70,8 @@ "maxWaveReached" : "已完成最大波次", "dungeonsCompleted": "地下城的完成次数", "dungeonsFailed": "地下城的团灭次数", - "averageTime": "完成平均时间", + "averageTime": "平均完成时间", + "minimumTime": "最短完成时间", "maxEnrageStack": "最大狂暴层数", "debuffOnLevelGap": "等级差距惩罚" }, diff --git a/patchNote.json b/patchNote.json index c8160696..8396b351 100644 --- a/patchNote.json +++ b/patchNote.json @@ -1,4 +1,8 @@ { + "2025年12月30日": + [ + "地下城增加最短完成时间记录" + ], "2025年12月24日": [ "修复技能释放选择的缺陷,之前可能存在异常缺蓝等情况" diff --git a/src/combatsimulator/combatSimulator.js b/src/combatsimulator/combatSimulator.js index a9c03d3e..9e390928 100644 --- a/src/combatsimulator/combatSimulator.js +++ b/src/combatsimulator/combatSimulator.js @@ -668,6 +668,9 @@ class CombatSimulator extends EventTarget { if (this.zone.isDungeon) { this.simResult.updateTimeSpentAlive("#" + (this.zone.encountersKilled - 1).toString(), false, this.simulationTime); + if (this.zone.encountersKilled > this.zone.dungeonSpawnInfo.maxWaves) { + this.simResult.updateDungenonFinish("#1", this.simulationTime); + } } this.simResult.addEncounterEnd(); // console.log("All enemies died"); diff --git a/src/combatsimulator/simResult.js b/src/combatsimulator/simResult.js index 4e44403a..d19b99a2 100644 --- a/src/combatsimulator/simResult.js +++ b/src/combatsimulator/simResult.js @@ -33,6 +33,7 @@ class SimResult { this.maxWaveReached = 0; this.numberOfPlayers = numberOfPlayers; this.maxEnrageStack = 0; + this.minDungenonTime = 0; this.wipeEvents = []; } @@ -71,6 +72,19 @@ class SimResult { } } + updateDungenonFinish(beginFlag, finishTime) { + const i = this.timeSpentAlive.findIndex(e => e.name === beginFlag); + if (i == -1) { + return; + } + + const currentDungenonTime = finishTime - this.timeSpentAlive[i].spawnedAt; + + if (this.minDungenonTime == 0 || this.minDungenonTime > currentDungenonTime) { + this.minDungenonTime = currentDungenonTime; + } + } + addExperienceGain(unit, experience) { if (!unit.isPlayer) { return; diff --git a/src/main.js b/src/main.js index 8459c8e0..e45a2d52 100644 --- a/src/main.js +++ b/src/main.js @@ -1655,6 +1655,12 @@ function showKills(simResult, playerToDisplay) { let averageTime = (hoursSimulated * 60 / simResult.dungeonsCompleted).toFixed(1); encountersRow = createRow(["col-md-6", "col-md-6 text-end"], ["Average Time", averageTime]); encountersRow.firstElementChild.setAttribute("data-i18n", "common:simulationResults.averageTime"); + if (simResult.minDungenonTime > 0) { + let minimumTime = (simResult.minDungenonTime / ONE_SECOND / 60).toFixed(1); + let minimumTimeRow = createRow(["col-md-6", "col-md-6 text-end"], ["Minimum Time", minimumTime]); + minimumTimeRow.firstElementChild.setAttribute("data-i18n", "common:simulationResults.minimumTime"); + newChildren.push(minimumTimeRow); + } } else { encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1); encountersRow = createRow(["col-md-6", "col-md-6 text-end"], ["Encounters", encountersPerHour]); From f78c165c7949ac2df211be5d4528bfbd4aa88f24 Mon Sep 17 00:00:00 2001 From: LuHaiyang Date: Mon, 5 Jan 2026 19:07:41 +0800 Subject: [PATCH 07/46] add two experimental features and fix a bug where the damageRatioBoost was incorrectly calculated for bulwark --- dist/bundle.js | 913 +++++++++++++++++-- dist/bundle.js.map | 2 +- dist/index.html | 55 ++ dist/locales/en/common.json | 12 +- dist/locales/zh/common.json | 12 +- dist/src_multiWorker_js-_004d0.bundle.js.map | 2 +- dist/src_multiWorker_js-_004d1.bundle.js.map | 2 +- dist/src_multiWorker_js-_004d2.bundle.js.map | 2 +- dist/src_multiWorker_js-_004d3.bundle.js.map | 2 +- dist/src_worker_js.bundle.js | 55 +- dist/src_worker_js.bundle.js.map | 2 +- index.html | 55 ++ locales/en/common.json | 12 +- locales/zh/common.json | 12 +- src/combatsimulator/combatSimulator.js | 10 +- src/combatsimulator/combatUnit.js | 6 +- src/combatsimulator/simResult.js | 28 + src/main.js | 793 +++++++++++++++- src/worker.js | 11 +- 19 files changed, 1883 insertions(+), 103 deletions(-) diff --git a/dist/bundle.js b/dist/bundle.js index 13783845..cea11ccf 100644 --- a/dist/bundle.js +++ b/dist/bundle.js @@ -532,7 +532,11 @@ class CombatUnit { } }); - this.combatDetails.defensiveMaxDamage = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.defensiveDamage); + this.combatDetails.defensiveMaxDamage = + (10 + this.combatDetails.defenseLevel) * + (1 + this.combatDetails.combatStats.defensiveDamage) * + (1 + damageRatioBoost) * + (1 + damageRatioBoostFromFury); // when equiped bulwark if (this.equipment?.['/equipment_types/two_hand']?.hrid.includes("bulwark")) { @@ -1762,18 +1766,22 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _combatsimulator_ability_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./combatsimulator/ability.js */ "./src/combatsimulator/ability.js"); /* harmony import */ var _combatsimulator_consumable_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./combatsimulator/consumable.js */ "./src/combatsimulator/consumable.js"); /* harmony import */ var _combatsimulator_houseRoom__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./combatsimulator/houseRoom */ "./src/combatsimulator/houseRoom.js"); -/* harmony import */ var _combatsimulator_data_combatTriggerDependencyDetailMap_json__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./combatsimulator/data/combatTriggerDependencyDetailMap.json */ "./src/combatsimulator/data/combatTriggerDependencyDetailMap.json"); -/* harmony import */ var _combatsimulator_data_combatTriggerConditionDetailMap_json__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./combatsimulator/data/combatTriggerConditionDetailMap.json */ "./src/combatsimulator/data/combatTriggerConditionDetailMap.json"); -/* harmony import */ var _combatsimulator_data_combatTriggerComparatorDetailMap_json__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./combatsimulator/data/combatTriggerComparatorDetailMap.json */ "./src/combatsimulator/data/combatTriggerComparatorDetailMap.json"); -/* harmony import */ var _combatsimulator_data_abilitySlotsLevelRequirementList_json__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./combatsimulator/data/abilitySlotsLevelRequirementList.json */ "./src/combatsimulator/data/abilitySlotsLevelRequirementList.json"); -/* harmony import */ var _combatsimulator_data_actionDetailMap_json__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./combatsimulator/data/actionDetailMap.json */ "./src/combatsimulator/data/actionDetailMap.json"); -/* harmony import */ var _combatsimulator_data_combatMonsterDetailMap_json__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./combatsimulator/data/combatMonsterDetailMap.json */ "./src/combatsimulator/data/combatMonsterDetailMap.json"); -/* harmony import */ var _combatsimulator_data_damageTypeDetailMap_json__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./combatsimulator/data/damageTypeDetailMap.json */ "./src/combatsimulator/data/damageTypeDetailMap.json"); -/* harmony import */ var _combatsimulator_data_combatStyleDetailMap_json__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./combatsimulator/data/combatStyleDetailMap.json */ "./src/combatsimulator/data/combatStyleDetailMap.json"); -/* harmony import */ var _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./combatsimulator/data/openableLootDropMap.json */ "./src/combatsimulator/data/openableLootDropMap.json"); -/* harmony import */ var _combatsimulator_data_achievementTierDetailMap_json__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./combatsimulator/data/achievementTierDetailMap.json */ "./src/combatsimulator/data/achievementTierDetailMap.json"); -/* harmony import */ var _combatsimulator_data_achievementDetailMap_json__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./combatsimulator/data/achievementDetailMap.json */ "./src/combatsimulator/data/achievementDetailMap.json"); -/* harmony import */ var _patchNote_json__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! ../patchNote.json */ "./patchNote.json"); +/* harmony import */ var _combatsimulator_buff_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./combatsimulator/buff.js */ "./src/combatsimulator/buff.js"); +/* harmony import */ var _combatsimulator_achievement_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./combatsimulator/achievement.js */ "./src/combatsimulator/achievement.js"); +/* harmony import */ var _combatsimulator_data_combatTriggerDependencyDetailMap_json__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./combatsimulator/data/combatTriggerDependencyDetailMap.json */ "./src/combatsimulator/data/combatTriggerDependencyDetailMap.json"); +/* harmony import */ var _combatsimulator_data_combatTriggerConditionDetailMap_json__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./combatsimulator/data/combatTriggerConditionDetailMap.json */ "./src/combatsimulator/data/combatTriggerConditionDetailMap.json"); +/* harmony import */ var _combatsimulator_data_combatTriggerComparatorDetailMap_json__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./combatsimulator/data/combatTriggerComparatorDetailMap.json */ "./src/combatsimulator/data/combatTriggerComparatorDetailMap.json"); +/* harmony import */ var _combatsimulator_data_abilitySlotsLevelRequirementList_json__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./combatsimulator/data/abilitySlotsLevelRequirementList.json */ "./src/combatsimulator/data/abilitySlotsLevelRequirementList.json"); +/* harmony import */ var _combatsimulator_data_actionDetailMap_json__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./combatsimulator/data/actionDetailMap.json */ "./src/combatsimulator/data/actionDetailMap.json"); +/* harmony import */ var _combatsimulator_data_combatMonsterDetailMap_json__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./combatsimulator/data/combatMonsterDetailMap.json */ "./src/combatsimulator/data/combatMonsterDetailMap.json"); +/* harmony import */ var _combatsimulator_data_damageTypeDetailMap_json__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./combatsimulator/data/damageTypeDetailMap.json */ "./src/combatsimulator/data/damageTypeDetailMap.json"); +/* harmony import */ var _combatsimulator_data_combatStyleDetailMap_json__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./combatsimulator/data/combatStyleDetailMap.json */ "./src/combatsimulator/data/combatStyleDetailMap.json"); +/* harmony import */ var _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./combatsimulator/data/openableLootDropMap.json */ "./src/combatsimulator/data/openableLootDropMap.json"); +/* harmony import */ var _combatsimulator_data_achievementTierDetailMap_json__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! ./combatsimulator/data/achievementTierDetailMap.json */ "./src/combatsimulator/data/achievementTierDetailMap.json"); +/* harmony import */ var _combatsimulator_data_achievementDetailMap_json__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! ./combatsimulator/data/achievementDetailMap.json */ "./src/combatsimulator/data/achievementDetailMap.json"); +/* harmony import */ var _patchNote_json__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(/*! ../patchNote.json */ "./patchNote.json"); + + @@ -1850,6 +1858,10 @@ function onWorkerMessage(event) { let progress = Math.floor(100 * event.data.progress); progressbar.style.width = progress + "%"; progressbar.innerHTML = progress + "% (" + ((Date.now() - simStartTime) / 1000).toFixed(2) + "s)"; + // 实时更新图表 + if (event.data.timeSeriesData && document.getElementById('hpMpVisualizationToggle').checked) { + updateChartsRealtime(event.data.timeSeriesData); + } break; case "simulation_error": showErrorModal(event.data.error.toString()); @@ -1937,6 +1949,10 @@ function initHouseRoomsModal() { let inputValue = e.target.value; const hrid = e.target.dataset.houseHrid; player.houseRooms[hrid] = parseInt(inputValue); + // 如果开启入战属性模式,实时更新 + if (isCombatReadyMode) { + updateCombatStatsUI(); + } }); levelCol.appendChild(levelInput); @@ -1962,7 +1978,7 @@ function createHouseInput(hrid) { } function refreshAchievementStatics() { - let tierMap = Object.values(_combatsimulator_data_achievementTierDetailMap_json__WEBPACK_IMPORTED_MODULE_17__).sort((a, b) => a.sortIndex - b.sortIndex); + let tierMap = Object.values(_combatsimulator_data_achievementTierDetailMap_json__WEBPACK_IMPORTED_MODULE_19__).sort((a, b) => a.sortIndex - b.sortIndex); for(const tier of Object.values(tierMap)) { const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier="${tier.sortIndex}"]`); const done = Array.from(checks).filter(cb => cb.checked).length; @@ -1987,9 +2003,9 @@ function initAchievementsModal(){ let newChildren = []; player.achievements = {}; - let tierMap = Object.values(_combatsimulator_data_achievementTierDetailMap_json__WEBPACK_IMPORTED_MODULE_17__).sort((a, b) => a.sortIndex - b.sortIndex); + let tierMap = Object.values(_combatsimulator_data_achievementTierDetailMap_json__WEBPACK_IMPORTED_MODULE_19__).sort((a, b) => a.sortIndex - b.sortIndex); for(const tier of Object.values(tierMap)) { - let detailMap = Object.values(_combatsimulator_data_achievementDetailMap_json__WEBPACK_IMPORTED_MODULE_18__).filter((detail) => detail.tierHrid == tier.hrid).sort((a, b) => a.sortIndex - b.sortIndex); + let detailMap = Object.values(_combatsimulator_data_achievementDetailMap_json__WEBPACK_IMPORTED_MODULE_20__).filter((detail) => detail.tierHrid == tier.hrid).sort((a, b) => a.sortIndex - b.sortIndex); let detailMapCount = detailMap.length; if (detailMapCount <= 0) continue; @@ -2023,6 +2039,10 @@ function initAchievementsModal(){ } cardStatics.dataset.checked = cardStatics.dataset.checked == "true" ? "false" : "true"; refreshAchievementStatics(); + // 如果开启入战属性模式,实时更新 + if (isCombatReadyMode) { + updateCombatStatsUI(); + } }); cardHeader.appendChild(cardStatics); @@ -2046,6 +2066,10 @@ function initAchievementsModal(){ player.achievements[hrid] = e.target.checked; refreshAchievementStatics(); + // 如果开启入战属性模式,实时更新 + if (isCombatReadyMode) { + updateCombatStatsUI(); + } }); formCheck.appendChild(input); @@ -2200,29 +2224,334 @@ function changeEquipmentSetListener() { // #region Combat Stats -function updateCombatStatsUI() { +// 存储基础属性和入战属性的对比数据 +let baseStats = {}; +let combatReadyStats = {}; +let isCombatReadyMode = false; + +// 光环技能列表(按照后放覆盖先放的逻辑) +const AURA_ABILITIES = [ + "/abilities/critical_aura", + "/abilities/fierce_aura", + "/abilities/guardian_aura", + "/abilities/mystic_aura", + "/abilities/speed_aura" +]; + +// 咖啡类饮料列表(用于识别咖啡) +function isCoffee(hrid) { + return hrid && hrid.includes("coffee"); +} + +// 计算入战时的所有永久buff +function calculateCombatReadyBuffs() { + let buffs = []; + + // 1. 房屋buff + Object.entries(player.houseRooms).forEach(([hrid, level]) => { + if (level > 0) { + try { + let houseRoom = new _combatsimulator_houseRoom__WEBPACK_IMPORTED_MODULE_7__["default"](hrid, level); + houseRoom.buffs.forEach(buff => { + buffs.push(buff); + }); + } catch (e) { + // 跳过无效的房间 + } + } + }); + + // 2. 成就buff + if (player.achievements && Object.keys(player.achievements).length > 0) { + let achievement = new _combatsimulator_achievement_js__WEBPACK_IMPORTED_MODULE_9__["default"](player.achievements); + achievement.buffs.forEach(buff => { + buffs.push(buff); + }); + } + + // 3. 社区经验buff + if (document.getElementById("comExpToggle").checked) { + let comExp = Number(document.getElementById("comExpInput").value); + if (comExp > 0) { + const comExpBuff = { + uniqueHrid: "/buff_uniques/experience_community_buff", + typeHrid: "/buff_types/wisdom", + ratioBoost: 0, + ratioBoostLevelBonus: 0, + flatBoost: 0.005 * (comExp - 1) + 0.2, + flatBoostLevelBonus: 0, + duration: 0 + }; + buffs.push(new _combatsimulator_buff_js__WEBPACK_IMPORTED_MODULE_8__["default"](comExpBuff)); + } + } + + // 4. 社区掉落buff + if (document.getElementById("comDropToggle").checked) { + let comDrop = Number(document.getElementById("comDropInput").value); + if (comDrop > 0) { + const comDropBuff = { + uniqueHrid: "/buff_uniques/combat_community_buff", + typeHrid: "/buff_types/combat_drop_quantity", + ratioBoost: 0, + ratioBoostLevelBonus: 0, + flatBoost: 0.005 * (comDrop - 1) + 0.2, + flatBoostLevelBonus: 0, + duration: 0 + }; + buffs.push(new _combatsimulator_buff_js__WEBPACK_IMPORTED_MODULE_8__["default"](comDropBuff)); + } + } + + // 5. MooPass buff + if (document.getElementById("mooPassToggle").checked) { + const mooPassBuff = { + uniqueHrid: "/buff_uniques/experience_moo_pass_buff", + typeHrid: "/buff_types/wisdom", + ratioBoost: 0, + ratioBoostLevelBonus: 0, + flatBoost: 0.05, + flatBoostLevelBonus: 0, + duration: 0 + }; + buffs.push(new _combatsimulator_buff_js__WEBPACK_IMPORTED_MODULE_8__["default"](mooPassBuff)); + } + + // 6. 咖啡buff(考虑饮料浓度) + let drinkConcentration = player.combatDetails.combatStats.drinkConcentration; + for (let i = 0; i < 3; i++) { + let drinkSelect = document.getElementById("selectDrink_" + i); + if (drinkSelect && drinkSelect.value && isCoffee(drinkSelect.value)) { + let drinkHrid = drinkSelect.value; + let gameItem = _combatsimulator_data_itemDetailMap_json__WEBPACK_IMPORTED_MODULE_3__[drinkHrid]; + if (gameItem && gameItem.consumableDetail && gameItem.consumableDetail.buffs) { + for (const buffData of gameItem.consumableDetail.buffs) { + let buffCopy = structuredClone(buffData); + // 应用饮料浓度加成 + if (drinkConcentration > 0) { + buffCopy.ratioBoost = buffCopy.ratioBoost * (1 + drinkConcentration); + buffCopy.flatBoost = buffCopy.flatBoost * (1 + drinkConcentration); + } + buffs.push(new _combatsimulator_buff_js__WEBPACK_IMPORTED_MODULE_8__["default"](buffCopy)); + } + } + } + } + + // 7. 光环技能buff(从所有选中的玩家收集,按顺序处理,后放覆盖先放) + let auraBuffMap = {}; // 用于处理覆盖 + + // 获取所有选中的玩家(如果没有选中则默认只看当前玩家) + let playersToCheck = []; + const checkboxes = document.querySelectorAll('.player-checkbox'); + checkboxes.forEach(checkbox => { + if (checkbox.checked) { + const playerNumber = parseInt(checkbox.id.replace('player', '')); + playersToCheck.push(playerNumber); + } + }); + + // 如果没有选中任何玩家,只检查当前玩家 + if (playersToCheck.length === 0) { + playersToCheck.push(parseInt(currentPlayerTabId)); + } + + // 遍历所有选中的玩家,按玩家顺序收集光环(模拟战斗时的释放顺序) + for (const playerNumber of playersToCheck) { + let playerData; + + // 如果是当前显示的玩家,从UI读取 + if (playerNumber.toString() === currentPlayerTabId) { + for (let i = 0; i < 5; i++) { + let abilitySelect = document.getElementById("selectAbility_" + i); + if (abilitySelect && abilitySelect.value && AURA_ABILITIES.includes(abilitySelect.value)) { + let abilityHrid = abilitySelect.value; + let abilityLevel = Number(document.getElementById("inputAbilityLevel_" + i).value) || 1; + let gameAbility = _combatsimulator_data_abilityDetailMap_json__WEBPACK_IMPORTED_MODULE_2__[abilityHrid]; + if (gameAbility && gameAbility.abilityEffects) { + for (const effect of gameAbility.abilityEffects) { + if (effect.buffs) { + for (const buffData of effect.buffs) { + let buff = new _combatsimulator_buff_js__WEBPACK_IMPORTED_MODULE_8__["default"](buffData, abilityLevel); + // 使用uniqueHrid作为key,后放覆盖先放 + auraBuffMap[buff.uniqueHrid] = buff; + } + } + } + } + } + } + } else { + // 从playerDataMap读取其他玩家的配置 + try { + playerData = JSON.parse(playerDataMap[playerNumber]); + if (playerData && playerData.abilities) { + for (let i = 0; i < playerData.abilities.length; i++) { + let abilityConfig = playerData.abilities[i]; + if (abilityConfig && abilityConfig.abilityHrid && AURA_ABILITIES.includes(abilityConfig.abilityHrid)) { + let abilityHrid = abilityConfig.abilityHrid; + let abilityLevel = Number(abilityConfig.level) || 1; + let gameAbility = _combatsimulator_data_abilityDetailMap_json__WEBPACK_IMPORTED_MODULE_2__[abilityHrid]; + if (gameAbility && gameAbility.abilityEffects) { + for (const effect of gameAbility.abilityEffects) { + if (effect.buffs) { + for (const buffData of effect.buffs) { + let buff = new _combatsimulator_buff_js__WEBPACK_IMPORTED_MODULE_8__["default"](buffData, abilityLevel); + // 使用uniqueHrid作为key,后放覆盖先放 + auraBuffMap[buff.uniqueHrid] = buff; + } + } + } + } + } + } + } + } catch (e) { + // 跳过无法解析的玩家数据 + } + } + } + + // 添加光环buff + Object.values(auraBuffMap).forEach(buff => { + buffs.push(buff); + }); + + return buffs; +} + +// 创建一个临时player来计算入战属性 +function calculateCombatReadyPlayer() { + // 深拷贝当前player的基础数据 + let tempPlayer = new _combatsimulator_player_js__WEBPACK_IMPORTED_MODULE_1__["default"](); + tempPlayer.staminaLevel = player.staminaLevel; + tempPlayer.intelligenceLevel = player.intelligenceLevel; + tempPlayer.attackLevel = player.attackLevel; + tempPlayer.meleeLevel = player.meleeLevel; + tempPlayer.defenseLevel = player.defenseLevel; + tempPlayer.rangedLevel = player.rangedLevel; + tempPlayer.magicLevel = player.magicLevel; + tempPlayer.equipment = player.equipment; + tempPlayer.food = player.food; + tempPlayer.drinks = player.drinks; + tempPlayer.abilities = player.abilities; + tempPlayer.houseRooms = []; + tempPlayer.achievements = null; + + // 计算基础属性 + tempPlayer.updateCombatDetails(); + + // 收集所有入战buff + let combatBuffs = calculateCombatReadyBuffs(); + + // 将buff添加到permanentBuffs + combatBuffs.forEach(buff => { + if (tempPlayer.permanentBuffs[buff.typeHrid]) { + tempPlayer.permanentBuffs[buff.typeHrid].flatBoost += buff.flatBoost; + tempPlayer.permanentBuffs[buff.typeHrid].ratioBoost += buff.ratioBoost; + } else { + tempPlayer.permanentBuffs[buff.typeHrid] = buff; + } + }); + + // 复制到combatBuffs + tempPlayer.combatBuffs = structuredClone(tempPlayer.permanentBuffs); + + // 重新计算战斗属性 + tempPlayer.updateCombatDetails(); + + return tempPlayer; +} + +// 保存基础属性快照 +function saveBaseStats() { player.updateCombatDetails(); + baseStats = { + combatDetails: structuredClone(player.combatDetails) + }; +} + +// 保存入战属性快照 +function saveCombatReadyStats() { + let combatReadyPlayer = calculateCombatReadyPlayer(); + combatReadyStats = { + combatDetails: structuredClone(combatReadyPlayer.combatDetails) + }; +} + +// 判断属性是否被增益(用于高亮显示) +function isStatBoosted(statName, isFloor = false, isCombatStats = false) { + if (!isCombatReadyMode) return false; + if (!baseStats.combatDetails || !combatReadyStats.combatDetails) return false; + + let baseValue, combatValue; + if (isCombatStats) { + baseValue = baseStats.combatDetails.combatStats[statName]; + combatValue = combatReadyStats.combatDetails.combatStats[statName]; + } else { + baseValue = baseStats.combatDetails[statName]; + combatValue = combatReadyStats.combatDetails[statName]; + } + + if (baseValue === undefined || combatValue === undefined) return false; + + if (isFloor) { + return Math.floor(combatValue) > Math.floor(baseValue); + } + return combatValue > baseValue + 0.0001; // 浮点数比较容差 +} + +// 获取当前应该显示的属性源 +function getCurrentDisplayStats() { + return isCombatReadyMode ? combatReadyStats : baseStats; +} + +function updateCombatStatsUI() { + // 先保存基础属性 + saveBaseStats(); + + // 如果开启入战属性模式,计算入战属性 + if (isCombatReadyMode) { + saveCombatReadyStats(); + } + + // 获取要显示的属性 + let displayStats = isCombatReadyMode ? combatReadyStats.combatDetails : player.combatDetails; + if (!displayStats) { + player.updateCombatDetails(); + displayStats = player.combatDetails; + } let combatStyleElement = document.getElementById("combatStat_combatStyleHrid"); - let combatStyle = player.combatDetails.combatStats.combatStyleHrid; + let combatStyle = displayStats.combatStats.combatStyleHrid; combatStyleElement.setAttribute("data-i18n", "combatStyleNames." + combatStyle); - combatStyleElement.innerHTML = _combatsimulator_data_combatStyleDetailMap_json__WEBPACK_IMPORTED_MODULE_15__[combatStyle].name; + combatStyleElement.innerHTML = _combatsimulator_data_combatStyleDetailMap_json__WEBPACK_IMPORTED_MODULE_17__[combatStyle].name; let damageTypeElement = document.getElementById("combatStat_damageType"); - let damageType = _combatsimulator_data_damageTypeDetailMap_json__WEBPACK_IMPORTED_MODULE_14__[player.combatDetails.combatStats.damageType]; + let damageType = _combatsimulator_data_damageTypeDetailMap_json__WEBPACK_IMPORTED_MODULE_16__[displayStats.combatStats.damageType]; damageTypeElement.setAttribute("data-i18n", "damageTypeNames." + damageType.hrid); damageTypeElement.innerHTML = damageType.name; let attackIntervalElement = document.getElementById("combatStat_attackInterval"); - attackIntervalElement.innerHTML = (player.combatDetails.combatStats.attackInterval / 1e9).toLocaleString() + "s"; + let attackIntervalValue = (displayStats.combatStats.attackInterval / 1e9).toLocaleString() + "s"; + attackIntervalElement.innerHTML = attackIntervalValue; + // 攻速变快时也高亮(数值变小) + if (isCombatReadyMode && baseStats.combatDetails && + displayStats.combatStats.attackInterval < baseStats.combatDetails.combatStats.attackInterval - 1000) { + attackIntervalElement.style.color = "#0d6efd"; + attackIntervalElement.style.fontWeight = "bold"; + } else { + attackIntervalElement.style.color = ""; + attackIntervalElement.style.fontWeight = ""; + } let primaryTrainingElement = document.getElementById("combatStat_primaryTraining"); - let primaryTraining = player.combatDetails.combatStats.primaryTraining; + let primaryTraining = displayStats.combatStats.primaryTraining; primaryTrainingElement.setAttribute("data-i18n", "skillNames." + primaryTraining); primaryTrainingElement.innerHTML = primaryTraining; let focusTrainingElement = document.getElementById("combatStat_focusTraining"); - let focusTraining = player.combatDetails.combatStats.focusTraining; + let focusTraining = displayStats.combatStats.focusTraining; if (focusTraining) { focusTrainingElement.setAttribute("data-i18n", "skillNames." + focusTraining); } else { @@ -2256,7 +2585,14 @@ function updateCombatStatsUI() { "totalThreat" ].forEach((stat) => { let element = document.getElementById("combatStat_" + stat); - element.innerHTML = Math.floor(player.combatDetails[stat]); + element.innerHTML = Math.floor(displayStats[stat]); + if (isStatBoosted(stat, true, false)) { + element.style.color = "#0d6efd"; + element.style.fontWeight = "bold"; + } else { + element.style.color = ""; + element.style.fontWeight = ""; + } }); [ @@ -2264,7 +2600,14 @@ function updateCombatStatsUI() { "tenacity" ].forEach((stat) => { let element = document.getElementById("combatStat_" + stat); - element.innerHTML = Math.floor(player.combatDetails.combatStats[stat]); + element.innerHTML = Math.floor(displayStats.combatStats[stat]); + if (isStatBoosted(stat, true, true)) { + element.style.color = "#0d6efd"; + element.style.fontWeight = "bold"; + } else { + element.style.color = ""; + element.style.fontWeight = ""; + } }); [ @@ -2312,11 +2655,37 @@ function updateCombatStatsUI() { ].forEach((stat) => { let element = document.getElementById("combatStat_" + stat); - let value = (100 * player.combatDetails.combatStats[stat]).toLocaleString([], { + let value = (100 * displayStats.combatStats[stat]).toLocaleString([], { minimumFractionDigits: 0, maximumFractionDigits: 4, }); element.innerHTML = value + "%"; + if (isStatBoosted(stat, false, true)) { + element.style.color = "#0d6efd"; + element.style.fontWeight = "bold"; + } else { + element.style.color = ""; + element.style.fontWeight = ""; + } + }); +} + +// 初始化入战属性开关 +function initCombatReadyStatsToggle() { + let toggle = document.getElementById("combatReadyStatsToggle"); + if (!toggle) return; + + // 从localStorage读取状态 + let savedState = localStorage.getItem('combatReadyStatsEnabled'); + if (savedState === 'true') { + toggle.checked = true; + isCombatReadyMode = true; + } + + toggle.addEventListener('change', () => { + isCombatReadyMode = toggle.checked; + localStorage.setItem('combatReadyStatsEnabled', toggle.checked); + updateUI(); }); } @@ -2440,6 +2809,10 @@ function initDrinksSection() { function drinkSelectHandler() { updateDrinksState(); updateDrinksUI(); + // 如果开启入战属性模式,实时更新(咖啡会影响入战属性) + if (isCombatReadyMode) { + updateCombatStatsUI(); + } } function updateDrinksState() { @@ -2489,12 +2862,25 @@ function initAbilitiesSection() { } selectElement.addEventListener("change", abilitySelectHandler); + // 技能等级改变时也需要更新入战属性 + inputElement.addEventListener("input", abilityLevelInputHandler); + } +} + +function abilityLevelInputHandler() { + // 如果开启入战属性模式,实时更新(光环技能等级会影响入战属性) + if (isCombatReadyMode) { + updateCombatStatsUI(); } } function abilitySelectHandler() { updateAbilityState(); updateAbilityUI(); + // 如果开启入战属性模式,实时更新(光环技能会影响入战属性) + if (isCombatReadyMode) { + updateCombatStatsUI(); + } } function updateAbilityState() { @@ -2514,9 +2900,9 @@ function updateAbilityUI() { let inputElement = document.getElementById("inputAbilityLevel_" + i); let triggerButton = document.getElementById("buttonAbilityTrigger_" + i); - selectElement.disabled = player.intelligenceLevel < _combatsimulator_data_abilitySlotsLevelRequirementList_json__WEBPACK_IMPORTED_MODULE_11__[i + 1]; - inputElement.disabled = player.intelligenceLevel < _combatsimulator_data_abilitySlotsLevelRequirementList_json__WEBPACK_IMPORTED_MODULE_11__[i + 1]; - triggerButton.disabled = player.intelligenceLevel < _combatsimulator_data_abilitySlotsLevelRequirementList_json__WEBPACK_IMPORTED_MODULE_11__[i + 1] || !main_abilities[i]; + selectElement.disabled = player.intelligenceLevel < _combatsimulator_data_abilitySlotsLevelRequirementList_json__WEBPACK_IMPORTED_MODULE_13__[i + 1]; + inputElement.disabled = player.intelligenceLevel < _combatsimulator_data_abilitySlotsLevelRequirementList_json__WEBPACK_IMPORTED_MODULE_13__[i + 1]; + triggerButton.disabled = player.intelligenceLevel < _combatsimulator_data_abilitySlotsLevelRequirementList_json__WEBPACK_IMPORTED_MODULE_13__[i + 1] || !main_abilities[i]; let moveUpButton = document.getElementById("selectAbilityMoveUp_" + i); moveUpButton.onclick = () => swapAbilityOrder(i, -1); } @@ -2735,7 +3121,7 @@ function updateTriggerModal() { triggerComparatorSelect.value = modalTriggers[i].comparatorHrid; - if (_combatsimulator_data_combatTriggerComparatorDetailMap_json__WEBPACK_IMPORTED_MODULE_10__[modalTriggers[i].comparatorHrid].allowValue) { + if (_combatsimulator_data_combatTriggerComparatorDetailMap_json__WEBPACK_IMPORTED_MODULE_12__[modalTriggers[i].comparatorHrid].allowValue) { showElement(triggerValueInput); triggerValueInput.value = modalTriggers[i].value; } else { @@ -2753,7 +3139,7 @@ function fillTriggerDependencySelect(element) { element.length = 0; element.add(new Option("", "")); - for (const dependency of Object.values(_combatsimulator_data_combatTriggerDependencyDetailMap_json__WEBPACK_IMPORTED_MODULE_8__).sort( + for (const dependency of Object.values(_combatsimulator_data_combatTriggerDependencyDetailMap_json__WEBPACK_IMPORTED_MODULE_10__).sort( (a, b) => a.sortIndex - b.sortIndex )) { let opt = new Option(dependency.name, dependency.hrid); @@ -2763,13 +3149,13 @@ function fillTriggerDependencySelect(element) { } function fillTriggerConditionSelect(element, dependencyHrid) { - let dependency = _combatsimulator_data_combatTriggerDependencyDetailMap_json__WEBPACK_IMPORTED_MODULE_8__[dependencyHrid]; + let dependency = _combatsimulator_data_combatTriggerDependencyDetailMap_json__WEBPACK_IMPORTED_MODULE_10__[dependencyHrid]; let conditions; if (dependency.isSingleTarget) { - conditions = Object.values(_combatsimulator_data_combatTriggerConditionDetailMap_json__WEBPACK_IMPORTED_MODULE_9__).filter((condition) => condition.isSingleTarget); + conditions = Object.values(_combatsimulator_data_combatTriggerConditionDetailMap_json__WEBPACK_IMPORTED_MODULE_11__).filter((condition) => condition.isSingleTarget); } else { - conditions = Object.values(_combatsimulator_data_combatTriggerConditionDetailMap_json__WEBPACK_IMPORTED_MODULE_9__).filter((condition) => condition.isMultiTarget); + conditions = Object.values(_combatsimulator_data_combatTriggerConditionDetailMap_json__WEBPACK_IMPORTED_MODULE_11__).filter((condition) => condition.isMultiTarget); } element.length = 0; @@ -2783,9 +3169,9 @@ function fillTriggerConditionSelect(element, dependencyHrid) { } function fillTriggerComparatorSelect(element, conditionHrid) { - let condition = _combatsimulator_data_combatTriggerConditionDetailMap_json__WEBPACK_IMPORTED_MODULE_9__[conditionHrid]; + let condition = _combatsimulator_data_combatTriggerConditionDetailMap_json__WEBPACK_IMPORTED_MODULE_11__[conditionHrid]; - let comparators = condition.allowedComparatorHrids.map((hrid) => _combatsimulator_data_combatTriggerComparatorDetailMap_json__WEBPACK_IMPORTED_MODULE_10__[hrid]); + let comparators = condition.allowedComparatorHrids.map((hrid) => _combatsimulator_data_combatTriggerComparatorDetailMap_json__WEBPACK_IMPORTED_MODULE_12__[hrid]); element.length = 0; element.add(new Option("", "")); @@ -2815,7 +3201,7 @@ function initZones() { let zoneSelect = document.getElementById("selectZone"); // TOOD dungeon wave spawns - let gameZones = Object.values(_combatsimulator_data_actionDetailMap_json__WEBPACK_IMPORTED_MODULE_12__) + let gameZones = Object.values(_combatsimulator_data_actionDetailMap_json__WEBPACK_IMPORTED_MODULE_14__) .filter((action) => action.type == "/action_types/combat" && action.category != "/action_categories/combat/dungeons") .sort((a, b) => a.sortIndex - b.sortIndex); @@ -2840,7 +3226,7 @@ function initZones() { } }); - let zoneHrids = Object.values(_combatsimulator_data_actionDetailMap_json__WEBPACK_IMPORTED_MODULE_12__) + let zoneHrids = Object.values(_combatsimulator_data_actionDetailMap_json__WEBPACK_IMPORTED_MODULE_14__) .filter((action) => action.type == "/action_types/combat" && action.category != "/action_categories/combat/dungeons" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1) .sort((a, b) => a.sortIndex - b.sortIndex) .flat(); @@ -2883,7 +3269,7 @@ function initZones() { } }); - let soloHrids = Object.values(_combatsimulator_data_actionDetailMap_json__WEBPACK_IMPORTED_MODULE_12__) + let soloHrids = Object.values(_combatsimulator_data_actionDetailMap_json__WEBPACK_IMPORTED_MODULE_14__) .filter((action) => action.type == "/action_types/combat" && action.category != "/action_categories/combat/dungeons" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount == 1) .sort((a, b) => a.sortIndex - b.sortIndex) .flat(); @@ -2915,7 +3301,7 @@ function initZones() { function initDungeons() { let dungeonSelect = document.getElementById("selectDungeon"); - let gameDungeons = Object.values(_combatsimulator_data_actionDetailMap_json__WEBPACK_IMPORTED_MODULE_12__) + let gameDungeons = Object.values(_combatsimulator_data_actionDetailMap_json__WEBPACK_IMPORTED_MODULE_14__) .filter((action) => action.type == "/action_types/combat" && action.category == "/action_categories/combat/dungeons") .sort((a, b) => a.sortIndex - b.sortIndex); @@ -3056,6 +3442,11 @@ function showSimulationResult(simResult) { window.noRngProfit = window.noRngRevenue - window.expenses; document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString(); document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString(); + + // 显示战斗图表 + if (document.getElementById('hpMpVisualizationToggle').checked) { + renderCombatCharts(simResult); + } } function showAllSimulationResults(simResults) { @@ -3063,6 +3454,325 @@ function showAllSimulationResults(simResults) { updateAllSimsModal(displaySimResults); } +// #region 战斗图表功能 + +let combatCharts = { + hpChart: null, + mpChart: null +}; + +let lastUpdateTime = 0; +const UPDATE_INTERVAL = 1000; // 每秒更新一次图表 + +// 实时更新图表 +function updateChartsRealtime(timeSeriesData) { + // 节流:避免过于频繁的更新 + const now = Date.now(); + if (now - lastUpdateTime < UPDATE_INTERVAL) { + return; + } + lastUpdateTime = now; + + if (!timeSeriesData || !timeSeriesData.timestamps || timeSeriesData.timestamps.length === 0) { + return; + } + + // 显示图表容器 + const container = document.getElementById('combatChartsContainer'); + if (container) { + container.classList.remove('d-none'); + } + + // 如果图表不存在,先创建 + if (!combatCharts.hpChart || !combatCharts.mpChart) { + initializeRealtimeCharts(); + // 等待下一次更新周期再更新数据 + return; + } + + const timeLabels = timeSeriesData.timestamps.map(t => (t / ONE_SECOND).toFixed(1)); + const playerIds = Object.keys(timeSeriesData.players); + + // 生成颜色方案 + const colors = [ + { border: 'rgb(75, 192, 192)', bg: 'rgba(75, 192, 192, 0.2)' }, + { border: 'rgb(255, 99, 132)', bg: 'rgba(255, 99, 132, 0.2)' }, + { border: 'rgb(54, 162, 235)', bg: 'rgba(54, 162, 235, 0.2)' }, + { border: 'rgb(255, 206, 86)', bg: 'rgba(255, 206, 86, 0.2)' }, + { border: 'rgb(153, 102, 255)', bg: 'rgba(153, 102, 255, 0.2)' } + ]; + + // 重建datasets以确保完整更新 + const hpDatasets = playerIds.map((playerId, index) => { + const playerData = timeSeriesData.players[playerId]; + return { + label: playerId + ' HP', + data: playerData.hp, + borderColor: colors[index % colors.length].border, + backgroundColor: colors[index % colors.length].bg, + borderWidth: 2, + pointRadius: 0, + tension: 0.1 + }; + }); + + const mpDatasets = playerIds.map((playerId, index) => { + const playerData = timeSeriesData.players[playerId]; + return { + label: playerId + ' MP', + data: playerData.mp, + borderColor: colors[index % colors.length].border, + backgroundColor: colors[index % colors.length].bg, + borderWidth: 2, + pointRadius: 0, + tension: 0.1 + }; + }); + + // 更新HP图表 + combatCharts.hpChart.data.labels = timeLabels; + combatCharts.hpChart.data.datasets = hpDatasets; + combatCharts.hpChart.options.plugins.legend.display = true; + combatCharts.hpChart.options.plugins.title.text = i18next.t('common:Experiment.hpOverTime'); + combatCharts.hpChart.update('none'); + + // 更新MP图表 + combatCharts.mpChart.data.labels = timeLabels; + combatCharts.mpChart.data.datasets = mpDatasets; + combatCharts.mpChart.options.plugins.legend.display = true; + combatCharts.mpChart.options.plugins.title.text = i18next.t('common:Experiment.mpOverTime'); + combatCharts.mpChart.update('none'); +} + +function renderCombatCharts(simResult) { + // 显示图表容器 + const container = document.getElementById('combatChartsContainer'); + if (container) { + container.classList.remove('d-none'); + } + + if (!simResult.timeSeriesData || !simResult.timeSeriesData.timestamps || simResult.timeSeriesData.timestamps.length === 0) { + // 显示空状态 + showEmptyCharts(); + return; + } + + const timeLabels = simResult.timeSeriesData.timestamps.map(t => (t / ONE_SECOND).toFixed(1)); + + // 获取所有玩家 + const playerIds = Object.keys(simResult.timeSeriesData.players); + + // 生成颜色方案 + const colors = [ + { border: 'rgb(75, 192, 192)', bg: 'rgba(75, 192, 192, 0.2)' }, + { border: 'rgb(255, 99, 132)', bg: 'rgba(255, 99, 132, 0.2)' }, + { border: 'rgb(54, 162, 235)', bg: 'rgba(54, 162, 235, 0.2)' }, + { border: 'rgb(255, 206, 86)', bg: 'rgba(255, 206, 86, 0.2)' }, + { border: 'rgb(153, 102, 255)', bg: 'rgba(153, 102, 255, 0.2)' } + ]; + + // HP图表 + destroyChart('hpChart'); + const hpDatasets = playerIds.map((playerId, index) => { + const playerData = simResult.timeSeriesData.players[playerId]; + return { + label: playerId + ' HP', + data: playerData.hp, + borderColor: colors[index % colors.length].border, + backgroundColor: colors[index % colors.length].bg, + borderWidth: 2, + pointRadius: 0, + tension: 0.1 + }; + }); + + combatCharts.hpChart = new Chart(document.getElementById('hpChart'), { + type: 'line', + data: { + labels: timeLabels, + datasets: hpDatasets + }, + options: getChartOptions(i18next.t('common:Experiment.hpOverTime'), i18next.t('common:Experiment.timeInSeconds'), 'HP') + }); + + // MP图表 + destroyChart('mpChart'); + const mpDatasets = playerIds.map((playerId, index) => { + const playerData = simResult.timeSeriesData.players[playerId]; + return { + label: playerId + ' MP', + data: playerData.mp, + borderColor: colors[index % colors.length].border, + backgroundColor: colors[index % colors.length].bg, + borderWidth: 2, + pointRadius: 0, + tension: 0.1 + }; + }); + + combatCharts.mpChart = new Chart(document.getElementById('mpChart'), { + type: 'line', + data: { + labels: timeLabels, + datasets: mpDatasets + }, + options: getChartOptions(i18next.t('common:Experiment.mpOverTime'), i18next.t('common:Experiment.timeInSeconds'), 'MP') + }); +} + +function destroyChart(chartName) { + if (combatCharts[chartName]) { + combatCharts[chartName].destroy(); + combatCharts[chartName] = null; + } +} + +function getChartOptions(title, xLabel, yLabel) { + return { + responsive: true, + maintainAspectRatio: true, + plugins: { + legend: { + display: true, + position: 'top', + labels: { + color: '#eee', + font: { + size: 11 + } + } + }, + title: { + display: true, + text: title, + color: '#eee', + font: { + size: 14 + } + } + }, + scales: { + x: { + display: true, + title: { + display: true, + text: xLabel, + color: '#eee' + }, + ticks: { + color: '#ccc', + maxTicksLimit: 10 + }, + grid: { + color: 'rgba(255, 255, 255, 0.1)' + } + }, + y: { + display: true, + title: { + display: true, + text: yLabel, + color: '#eee' + }, + ticks: { + color: '#ccc' + }, + grid: { + color: 'rgba(255, 255, 255, 0.1)' + } + } + }, + interaction: { + intersect: false, + mode: 'index' + } + }; +} + +// 初始化实时图表(用于模拟过程中更新) +function initializeRealtimeCharts() { + // 销毁现有图表 + destroyChart('hpChart'); + destroyChart('mpChart'); + + const hpCanvas = document.getElementById('hpChart'); + const mpCanvas = document.getElementById('mpChart'); + + if (!hpCanvas || !mpCanvas) { + console.warn('图表canvas元素未找到'); + return; + } + + // 显示等待状态 + const emptyOptions = { + responsive: true, + maintainAspectRatio: true, + plugins: { + legend: { display: false }, + title: { + display: true, + text: i18next.t('common:Experiment.waitingForData'), + color: '#888', + font: { size: 14 } + } + }, + scales: { + x: { + display: true, + ticks: { color: '#555' }, + grid: { color: 'rgba(255, 255, 255, 0.05)' } + }, + y: { + display: true, + ticks: { color: '#555' }, + grid: { color: 'rgba(255, 255, 255, 0.05)' } + } + } + }; + + try { + combatCharts.hpChart = new Chart(hpCanvas, { + type: 'line', + data: { labels: [], datasets: [] }, + options: emptyOptions + }); + + combatCharts.mpChart = new Chart(mpCanvas, { + type: 'line', + data: { labels: [], datasets: [] }, + options: emptyOptions + }); + } catch (e) { + console.error('创建图表时出错:', e); + } +} + +// 显示空图表状态 +function showEmptyCharts() { + initializeRealtimeCharts(); +} + +// 初始化HP/MP可视化开关事件 +function initHpMpVisualization() { + const toggle = document.getElementById('hpMpVisualizationToggle'); + const container = document.getElementById('combatChartsContainer'); + + if (toggle && container) { + toggle.addEventListener('change', function() { + if (this.checked) { + container.classList.remove('d-none'); + showEmptyCharts(); + } else { + container.classList.add('d-none'); + destroyChart('hpChart'); + destroyChart('mpChart'); + } + }); + } +} + +// #endregion + function manipulateSimResultsDataForDisplay(simResults) { let displaySimResults = []; for (let i = 0; i < simResults.length; i++) { @@ -3138,8 +3848,8 @@ function calcDropMaps(simResult, playerToDisplay) { for (const monster of monsters) { const dropMap = new Map(); const rareDropMap = new Map(); - if (_combatsimulator_data_combatMonsterDetailMap_json__WEBPACK_IMPORTED_MODULE_13__[monster].dropTable) { - for (const drop of _combatsimulator_data_combatMonsterDetailMap_json__WEBPACK_IMPORTED_MODULE_13__[monster].dropTable) { + if (_combatsimulator_data_combatMonsterDetailMap_json__WEBPACK_IMPORTED_MODULE_15__[monster].dropTable) { + for (const drop of _combatsimulator_data_combatMonsterDetailMap_json__WEBPACK_IMPORTED_MODULE_15__[monster].dropTable) { if (drop.minDifficultyTier > simResult.difficultyTier) { continue; } @@ -3150,8 +3860,8 @@ function calcDropMaps(simResult, playerToDisplay) { dropMap.set(drop.itemHrid, { "dropRate": Math.min(1.0, dropRate * dropRateMultiplier), "number": 0, "dropMin": drop.minCount, "dropMax": drop.maxCount, "noRngDropAmount": 0 }); } - if (_combatsimulator_data_combatMonsterDetailMap_json__WEBPACK_IMPORTED_MODULE_13__[monster].rareDropTable) - for (const drop of _combatsimulator_data_combatMonsterDetailMap_json__WEBPACK_IMPORTED_MODULE_13__[monster].rareDropTable) { + if (_combatsimulator_data_combatMonsterDetailMap_json__WEBPACK_IMPORTED_MODULE_15__[monster].rareDropTable) + for (const drop of _combatsimulator_data_combatMonsterDetailMap_json__WEBPACK_IMPORTED_MODULE_15__[monster].rareDropTable) { if (drop.minDifficultyTier > simResult.difficultyTier) { continue; } @@ -3464,7 +4174,7 @@ function showKills(simResult, playerToDisplay) { let killsPerHour = (simResult.deaths[monster] / hoursSimulated).toFixed(1); let monsterRow = createRow( ["col-md-6", "col-md-6 text-end"], - [_combatsimulator_data_combatMonsterDetailMap_json__WEBPACK_IMPORTED_MODULE_13__[monster].name, killsPerHour] + [_combatsimulator_data_combatMonsterDetailMap_json__WEBPACK_IMPORTED_MODULE_15__[monster].name, killsPerHour] ); monsterRow.firstElementChild.setAttribute("data-i18n", "monsterNames." + monster); newChildren.push(monsterRow); @@ -3946,7 +4656,7 @@ function showDamageDone(simResult, playerToDisplay) { let resultAccordionButton = document.getElementById( "buttonSimulationResultDamageDoneAccordionEnemy" + enemyIndex ); - let targetName = _combatsimulator_data_combatMonsterDetailMap_json__WEBPACK_IMPORTED_MODULE_13__[target].name; + let targetName = _combatsimulator_data_combatMonsterDetailMap_json__WEBPACK_IMPORTED_MODULE_15__[target].name; resultAccordionButton.innerHTML = "Damage Done (" + "" + targetName + "" + ")"; if (simResult.bossSpawns.includes(target)) { @@ -4043,7 +4753,7 @@ function showDamageTaken(simResult, playerToDisplay) { let resultAccordionButton = document.getElementById( "buttonSimulationResultDamageTakenAccordionEnemy" + enemyIndex ); - let sourceName = _combatsimulator_data_combatMonsterDetailMap_json__WEBPACK_IMPORTED_MODULE_13__[source].name; + let sourceName = _combatsimulator_data_combatMonsterDetailMap_json__WEBPACK_IMPORTED_MODULE_15__[source].name; resultAccordionButton.innerHTML = "Damage Taken (" + "" + sourceName + "" + ")"; enemyIndex++; @@ -4151,6 +4861,21 @@ document.addEventListener('DOMContentLoaded', function () { const simDungeonToggle = document.getElementById('simDungeonToggle'); const playerContainer = document.getElementById('playerCheckBox'); + // 处理玩家 checkbox 变化时更新入战属性 + function onPlayerCheckboxChange() { + if (isCombatReadyMode) { + updateUI(); + } + } + + // 给 checkbox 添加事件监听器 + function addCheckboxListener(checkbox) { + checkbox.addEventListener('change', onPlayerCheckboxChange); + } + + // 给初始的 player1, player2, player3 checkbox 添加事件监听器 + document.querySelectorAll('.player-checkbox').forEach(addCheckboxListener); + function addPlayers() { const player4 = document.createElement('div'); player4.classList.add('form-check'); @@ -4172,6 +4897,10 @@ document.addEventListener('DOMContentLoaded', function () { playerContainer.appendChild(player4); playerContainer.appendChild(player5); + + // 给新添加的 checkbox 添加事件监听器 + addCheckboxListener(document.getElementById('player4')); + addCheckboxListener(document.getElementById('player5')); } function removePlayers() { @@ -4194,6 +4923,10 @@ document.addEventListener('DOMContentLoaded', function () { function updatePlayersCheckbox(isCheck) { const boxes = playerContainer.querySelectorAll('.player-checkbox'); boxes.forEach((checkBox) => { checkBox.checked = isCheck }); + // 勾选状态变化后更新入战属性 + if (isCombatReadyMode) { + updateUI(); + } } function updateDifficultySelect(isCheck) { @@ -4322,7 +5055,7 @@ function startSimulation(selectedPlayers) { } for (let i = 0; i < 5; i++) { - if (main_abilities[i] && player.intelligenceLevel >= _combatsimulator_data_abilitySlotsLevelRequirementList_json__WEBPACK_IMPORTED_MODULE_11__[i + 1]) { + if (main_abilities[i] && player.intelligenceLevel >= _combatsimulator_data_abilitySlotsLevelRequirementList_json__WEBPACK_IMPORTED_MODULE_13__[i + 1]) { let abilityLevelInput = document.getElementById("inputAbilityLevel_" + i); let ability = new _combatsimulator_ability_js__WEBPACK_IMPORTED_MODULE_5__["default"](main_abilities[i], Number(abilityLevelInput.value), triggerMap[main_abilities[i]]); player.abilities[i] = ability; @@ -4368,6 +5101,7 @@ function startSimulation(selectedPlayers) { if (document.getElementById("comDropToggle").checked) { extra.comDrop = Number(document.getElementById("comDropInput").value); } + extra.enableHpMpVisualization = document.getElementById("hpMpVisualizationToggle").checked; let simAllZonesToggle = document.getElementById("simAllZoneToggle"); let simAllSoloToggle = document.getElementById("simAllSoloToggle"); @@ -4402,7 +5136,7 @@ function startSimulation(selectedPlayers) { let targetHrids = {}; if (simAllZonesToggle.checked) { - Object.values(_combatsimulator_data_actionDetailMap_json__WEBPACK_IMPORTED_MODULE_12__) + Object.values(_combatsimulator_data_actionDetailMap_json__WEBPACK_IMPORTED_MODULE_14__) .filter(a => a.type === "/action_types/combat" && a.category !== "/action_categories/combat/dungeons" && @@ -4413,7 +5147,7 @@ function startSimulation(selectedPlayers) { } if (simAllSoloToggle.checked) { - Object.values(_combatsimulator_data_actionDetailMap_json__WEBPACK_IMPORTED_MODULE_12__) + Object.values(_combatsimulator_data_actionDetailMap_json__WEBPACK_IMPORTED_MODULE_14__) .filter(a => a.type === "/action_types/combat" && a.category !== "/action_categories/combat/dungeons" && @@ -4563,7 +5297,7 @@ document.getElementById("buttonUploadJSONSimulate").addEventListener("click", (e let targetHrids = {}; if (simAllZonesToggle.checked) { - Object.values(_combatsimulator_data_actionDetailMap_json__WEBPACK_IMPORTED_MODULE_12__) + Object.values(_combatsimulator_data_actionDetailMap_json__WEBPACK_IMPORTED_MODULE_14__) .filter(a => a.type === "/action_types/combat" && a.category !== "/action_categories/combat/dungeons" && @@ -5064,7 +5798,7 @@ function fixTriggerMap(triggerMap) { triggerMap[key] = []; } for (const trigger of triggerMap[key]) { - if (!_combatsimulator_data_combatTriggerConditionDetailMap_json__WEBPACK_IMPORTED_MODULE_9__[trigger.conditionHrid]) { + if (!_combatsimulator_data_combatTriggerConditionDetailMap_json__WEBPACK_IMPORTED_MODULE_11__[trigger.conditionHrid]) { err = true; break; } @@ -5171,7 +5905,7 @@ function loadEquipmentSetIntoUI(equipmentSet) { player.achievements[achievement] = field.checked; } } else { - let achievements = Object.values(_combatsimulator_data_achievementDetailMap_json__WEBPACK_IMPORTED_MODULE_18__); + let achievements = Object.values(_combatsimulator_data_achievementDetailMap_json__WEBPACK_IMPORTED_MODULE_20__); for (const detail of Object.values(achievements)) { const field = document.querySelector('[data-achievement-hrid="' + detail.hrid + '"]'); field.checked = false; @@ -5315,16 +6049,45 @@ function setPlayerData(playerId, inputElementId) { function doGroupImport() { let needUpdateCurrentTab = false; + let importedPlayers = []; // 跟踪导入了哪些玩家的数据 const value = document.getElementById("inputSetGroupCombatAll")?.value || ""; if (!value.trim()) { for (let i of ['1', '2', '3', '4', '5']) { - if (setPlayerData(i, "inputSetGroupCombatplayer" + i) && currentPlayerTabId == i) { - needUpdateCurrentTab = true; + if (setPlayerData(i, "inputSetGroupCombatplayer" + i)) { + importedPlayers.push(i); + if (currentPlayerTabId == i) { + needUpdateCurrentTab = true; + } } } } else { playerDataMap = JSON.parse(value); needUpdateCurrentTab = true; + // 检查导入的组数据中哪些玩家有有效数据 + for (let i of ['1', '2', '3', '4', '5']) { + if (playerDataMap[i]) { + try { + let data = JSON.parse(playerDataMap[i]); + // 检查是否有有效的玩家数据(至少有等级设置) + if (data && data.player && (data.player.attackLevel > 1 || data.player.meleeLevel > 1 || + data.player.defenseLevel > 1 || data.player.rangedLevel > 1 || data.player.magicLevel > 1)) { + importedPlayers.push(i); + } + } catch (e) { + // 跳过无法解析的数据 + } + } + } + } + + // 自动勾选导入了有效数据的玩家的 checkbox + if (importedPlayers.length > 0) { + for (let i of ['1', '2', '3', '4', '5']) { + let checkbox = document.getElementById('player' + i); + if (checkbox) { + checkbox.checked = importedPlayers.includes(i); + } + } } if (needUpdateCurrentTab) { @@ -5454,7 +6217,7 @@ function doSoloImport() { player.achievements[achievement] = field.checked; } } else { - let achievements = Object.values(_combatsimulator_data_achievementDetailMap_json__WEBPACK_IMPORTED_MODULE_18__); + let achievements = Object.values(_combatsimulator_data_achievementDetailMap_json__WEBPACK_IMPORTED_MODULE_20__); for (const detail of Object.values(achievements)) { const field = document.querySelector('[data-achievement-hrid="' + detail.hrid + '"]'); field.checked = false; @@ -5643,7 +6406,7 @@ function updateNextPlayer(currentPlayerNumber) { } { // reset all achievements - let achievements = Object.values(_combatsimulator_data_achievementDetailMap_json__WEBPACK_IMPORTED_MODULE_18__); + let achievements = Object.values(_combatsimulator_data_achievementDetailMap_json__WEBPACK_IMPORTED_MODULE_20__); for (const detail of Object.values(achievements)) { const field = document.querySelector('[data-achievement-hrid="' + detail.hrid + '"]'); field.checked = false; @@ -5752,37 +6515,37 @@ async function fetchPrices() { window.prices["/items/coin"] = { "ask": 1, "bid": 1, "vendor": 1 }; window.prices["/items/small_treasure_chest"] = { - "ask": _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_16__["/items/small_treasure_chest"].map((item) => { + "ask": _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_18__["/items/small_treasure_chest"].map((item) => { return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0; }).reduce((a, b) => a + b, 0), - "bid": _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_16__["/items/small_treasure_chest"].map((item) => { + "bid": _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_18__["/items/small_treasure_chest"].map((item) => { return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0; }).reduce((a, b) => a + b, 0), - "vendor": _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_16__["/items/small_treasure_chest"].map((item) => { + "vendor": _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_18__["/items/small_treasure_chest"].map((item) => { return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0; }).reduce((a, b) => a + b, 0), }; window.prices["/items/medium_treasure_chest"] = { - "ask": _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_16__["/items/medium_treasure_chest"].map((item) => { + "ask": _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_18__["/items/medium_treasure_chest"].map((item) => { return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0; }).reduce((a, b) => a + b, 0), - "bid": _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_16__["/items/medium_treasure_chest"].map((item) => { + "bid": _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_18__["/items/medium_treasure_chest"].map((item) => { return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0; }).reduce((a, b) => a + b, 0), - "vendor": _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_16__["/items/medium_treasure_chest"].map((item) => { + "vendor": _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_18__["/items/medium_treasure_chest"].map((item) => { return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0; }).reduce((a, b) => a + b, 0), }; window.prices["/items/large_treasure_chest"] = { - "ask": _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_16__["/items/large_treasure_chest"].map((item) => { + "ask": _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_18__["/items/large_treasure_chest"].map((item) => { return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0; }).reduce((a, b) => a + b, 0), - "bid": _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_16__["/items/large_treasure_chest"].map((item) => { + "bid": _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_18__["/items/large_treasure_chest"].map((item) => { return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0; }).reduce((a, b) => a + b, 0), - "vendor": _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_16__["/items/large_treasure_chest"].map((item) => { + "vendor": _combatsimulator_data_openableLootDropMap_json__WEBPACK_IMPORTED_MODULE_18__["/items/large_treasure_chest"].map((item) => { return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0; }).reduce((a, b) => a + b, 0), }; @@ -5882,14 +6645,14 @@ function updateTable(tableId, item, price) { function initPatchNotes() { const patchNotesRows = document.getElementById("patchNotes"); - for (const pn in _patchNote_json__WEBPACK_IMPORTED_MODULE_19__) { + for (const pn in _patchNote_json__WEBPACK_IMPORTED_MODULE_21__) { const patchNoteContainer = document.createElement("div"); patchNotesRows.setAttribute('class', 'col-12 mb-4'); const patchNoteElement = document.createElement("h6"); patchNoteElement.innerHTML = pn; const patchNoteList = document.createElement("ul"); - for (const note of _patchNote_json__WEBPACK_IMPORTED_MODULE_19__[pn]) { + for (const note of _patchNote_json__WEBPACK_IMPORTED_MODULE_21__[pn]) { const noteElement = document.createElement("li"); noteElement.innerHTML = note; patchNoteList.appendChild(noteElement); @@ -5910,6 +6673,10 @@ function initExtraBuffSection() { } mooPassToggle.onchange = () => { localStorage.setItem('mooPass', mooPassToggle.checked); + // 如果开启入战属性模式,实时更新 + if (isCombatReadyMode) { + updateCombatStatsUI(); + } } // comExp @@ -5935,6 +6702,10 @@ function initExtraBuffSection() { localStorage.setItem('comExp', 0); comExpInput.disabled = true; } + // 如果开启入战属性模式,实时更新 + if (isCombatReadyMode) { + updateCombatStatsUI(); + } } comExpToggle.onchange = updateComExp; comExpInput.onchange = updateComExp; @@ -5962,6 +6733,10 @@ function initExtraBuffSection() { localStorage.setItem('comDrop', 0); comDropInput.disabled = true; } + // 如果开启入战属性模式,实时更新 + if (isCombatReadyMode) { + updateCombatStatsUI(); + } } comDropToggle.onchange = updateComDrop; comDropInput.onchange = updateComDrop; @@ -6046,6 +6821,8 @@ initImportExportModal(); initDamageDoneTaken(); initPatchNotes(); initExtraBuffSection(); +initHpMpVisualization(); +initCombatReadyStatsToggle(); updateState(); updateUI(); diff --git a/dist/bundle.js.map b/dist/bundle.js.map index 5601a3a2..0a83db5a 100644 --- a/dist/bundle.js.map +++ b/dist/bundle.js.map @@ -1 +1 @@ -{"version":3,"file":"bundle.js","mappings":";;;;;;;;;;;;;;;;;AAA0B;AACkC;AAC5B;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,wDAAgB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,6CAAI;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;ACxNG;AACkD;AACR;AACpE;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,gEAAwB;AAChE;AACA,0CAA0C,4DAAoB;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,WAAW;;;;;;;;;;;;;;AC1B1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,IAAI,EAAC;;;;;;;;;;;;;;;ACdpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wDAAwD;AACtF,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,mBAAmB;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;;AC/gBA;AAC4B;AACtB;AAChC;AACA;AACA;AACA;AACA;AACA,6BAA6B,qDAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,gDAAO;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;ACtF4B;AACmD;AACzG;AACA;AACA;AACA;AACA,uBAAuB,qDAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,iFAAoC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS,EAAC;;;;;;;;;;;;;;;;;AC/CC;AACsC;AAChE;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,0DAAkB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,SAAS;;;;;;;;;;;;;;;;;;;;AC7BQ;AACM;AACA;AACF;AACA;AACI;AACxC;AACA,qBAAqB,mDAAU;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,kDAAS;AACrD;AACA;AACA,qDAAqD,mDAAU;AAC/D,2DAA2D,mDAAU;AACrE,qEAAqE,gDAAO;AAC5E;AACA;AACA,2CAA2C,kDAAS;AACpD;AACA,SAAS;AACT;AACA,kCAAkC,oDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,MAAM,EAAC;;;;;;;;;;;;;;;;ACvLsE;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,wEAAgC;AAC5C;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UCjLvB;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;UAEA;UACA;;;;;WCzBA;WACA;WACA;WACA;WACA,yCAAyC,wCAAwC;WACjF;WACA;WACA;;;;;WCPA;WACA;WACA;WACA;WACA;;;;;WCJA;WACA;WACA;WACA;WACA,GAAG;WACH;WACA;WACA,CAAC;;;;;WCPD;;;;;WCAA;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D;;;;;WCNA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;;;;WClBA;;WAEA;WACA;WACA;WACA;WACA;WACA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrBuD;AACN;AAC2B;AACN;AACU;AAC7B;AACM;AACN;AACyD;AACF;AACE;AACA;AAClC;AACc;AACN;AACE;AACF;AACG;AACF;AACnF;AAC0C;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,2FAA4B;AAC5D,qCAAqC,qHAAiC;AACtE;AACA;AACA;AACA,iBAAiB,kEAAM;AACvB;AACA,IAAI,SAAI;AACR,IAAI,WAAM;AACV,IAAI,cAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA,sCAAsC,qEAAa;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,mCAAmC,0EAAkB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,iFAAkB;AAClD;AACA,4FAA4F,eAAe;AAC3G;AACA;AACA;AACA,uDAAuD,eAAe;AACtE,6BAA6B,KAAK,GAAG,MAAM;AAC3C;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,iFAAkB;AAClD;AACA,sCAAsC,6EAAoB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,eAAe;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oEAAoE,eAAe;AACnF,mCAAmC,eAAe;AAClD;AACA;AACA,gGAAgG,eAAe;AAC/G;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,gCAAgC,eAAe;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,iBAAiB;AACpD;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,iDAAiD,iBAAiB;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,qEAAa;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,qEAAa;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,qEAAS;AACvD,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,6EAAoB;AACvD;AACA;AACA,qBAAqB,4EAAmB;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,sCAAsC,qEAAa;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,SAAI;AACZ,YAAY,SAAI,mBAAmB,SAAI;AACvC,2BAA2B,qEAAa,CAAC,SAAI;AAC7C,uBAAuB,SAAI;AAC3B;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,qFAAqF,SAAI;AACzF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,uCAAuC,qEAAa;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,WAAM;AACd,YAAY,WAAM,mBAAmB,WAAM;AAC3C,2BAA2B,qEAAa,CAAC,WAAM;AAC/C,uBAAuB,WAAM;AAC7B;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,sFAAsF,WAAM;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D,UAAU;AACV,0CAA0C,wEAAgB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,cAAS;AACjB,YAAY,cAAS,mBAAmB,cAAS;AACjD,8BAA8B,wEAAgB,CAAC,cAAS;AACxD,uBAAuB,cAAS;AAChC;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,4DAA4D,yFAAgC;AAC5F,2DAA2D,yFAAgC;AAC3F,4DAA4D,yFAAgC,YAAY,cAAS;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAS;AACjC,IAAI,cAAS,iBAAiB,cAAS;AACvC,IAAI,cAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,cAAS;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,SAAI;AAChC;AACA;AACA,4BAA4B,WAAM;AAClC;AACA;AACA,4BAA4B,cAAS;AACrC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,qEAAa;AACrD,MAAM;AACN,wCAAwC,wEAAgB;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,yFAAgC;AAC5C;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,wFAAgC;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,wFAAgC;AACrD;AACA;AACA;AACA,mCAAmC,uFAA+B;AAClE,MAAM;AACN,mCAAmC,uFAA+B;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uFAA+B;AACnD;AACA,qEAAqE,yFAAgC;AACrG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA,kCAAkC,wEAAe;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,wEAAe;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2GAA2G,WAAW;AACtH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,WAAW;AAC5C,yDAAyD,WAAW;AACpE;AACA;AACA;AACA,gEAAgE,WAAW;AAC3E;AACA;AACA,qGAAqG,WAAW;AAChH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qFAAqF,WAAW;AAChG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4GAA4G,WAAW;AACvH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,WAAW;AAC7C,0DAA0D,WAAW;AACrE;AACA;AACA;AACA,iEAAiE,WAAW;AAC5E;AACA;AACA,sGAAsG,WAAW;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sFAAsF,WAAW;AACjG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,OAAO;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uBAAuB;AAC3C,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,+EAAsB;AAClC,+BAA+B,+EAAsB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,iJAAiJ;AAC9L;AACA,gBAAgB,+EAAsB;AACtC,mCAAmC,+EAAsB;AACzD;AACA;AACA;AACA,qDAAqD,uIAAuI;AAC5L;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,+BAA+B;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,UAAU,kCAAkC;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uEAAuE,oDAAoD;AAC3H,iEAAiE,oDAAoD;AACrH,iFAAiF,oDAAoD;AACrI;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,eAAe;AACrC;AACA;AACA;AACA;AACA,0BAA0B,mBAAmB;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,SAAS;AAC3D;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,KAAK;AACL,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,iBAAiB;AACrC;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,+CAA+C,kBAAkB;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,+EAAsB;AACvC;AACA;AACA;AACA,SAAS;AACT;AACA,UAAU,kCAAkC,qEAAqE;AACjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2EAA2E,wEAAgB;AAC3F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa,qEAAa;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,qEAAa;AACjC,iCAAiC,qEAAa;AAC9C;AACA,kBAAkB,SAAS,wEAAgB;AAC3C,iCAAiC,wEAAgB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,qEAAa;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kFAAkF,IAAI;AACtF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,QAAQ;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,QAAQ;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wEAAgB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,6BAA6B;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qEAAqE,UAAU;AAC/E;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,sCAAsC,4BAA4B;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,qCAAqC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC,2FAA4B;AAChE;AACA;AACA;AACA;AACA,yCAAyC,qHAAiC;AAC1E;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,SAAI;AACxB,yCAAyC,sEAAU,CAAC,SAAI,gBAAgB,SAAI;AAC5E;AACA,kBAAkB;AAClB;AACA;AACA;AACA,oBAAoB,WAAM;AAC1B,yCAAyC,sEAAU,CAAC,WAAM,gBAAgB,WAAM;AAChF;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,cAAS,mCAAmC,yFAAgC;AAChG;AACA,sCAAsC,mEAAO,CAAC,cAAS,iDAAiD,cAAS;AACjH;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,oDAAoD;AACxE;AACA;AACA;AACA;AACA;AACA,wCAAwC,qHAAiC;AACzE;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;AACA;AACA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,wCAAwC;AACrF,kCAAkC,uDAAuD;AACzF;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,qHAAiC;AAC9E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,KAAK,SAAS,qEAAS;AAC5E;AACA,KAAK;AACL;AACA;AACA;AACA,yBAAyB,sEAAU;AACnC;AACA;AACA;AACA;AACA,0BAA0B,sEAAU;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,mEAAO;AAC1C;AACA;AACA;AACA,mBAAmB,kEAAM;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wEAAwE,UAAU;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,IAAI;AAC7D;AACA;AACA;AACA;AACA;AACA,sCAAsC,wEAAe;AACrD;AACA;AACA;AACA;AACA;AACA,4CAA4C,0BAA0B;AACtE;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,wCAAwC;AACjG,8CAA8C,uDAAuD;AACrG;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,oDAAoD;AACpF;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,WAAW,QAAQ,KAAK;AACzD;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,KAAK;AACxC,oCAAoC,KAAK,YAAY,UAAU;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,wBAAwB,WAAW,WAAW;AACnF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd,qEAAqE,WAAW;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd,sEAAsE,YAAY;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sEAAsE,cAAc,IAAI,YAAY;AACpG;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,YAAY,IAAI,eAAe,GAAG,WAAW;AACxF;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,kCAAkC;AAClC,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB,qBAAqB;AACrB,gBAAgB;AAChB,kBAAkB;AAClB,qBAAqB;AACrB,sBAAsB;AACtB,sBAAsB;AACtB,wBAAwB;AACxB;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,uFAA+B;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,SAAI;AAClB,gBAAgB,WAAM;AACtB,mBAAmB,cAAS;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,qEAAa;AACxC,yBAAyB,qEAAa;AACtC;AACA,wCAAwC,gCAAgC,qEAAa;AACrF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yCAAyC;AACzC;AACA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;AACA;AACA;AACA,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,sBAAsB,4GAAmD;AACzE;AACA,aAAa;AACb;AACA;AACA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,6CAAS;AAC9B;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,6CAAS;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sources":["webpack://mwicombatsimulator/./src/combatsimulator/ability.js","webpack://mwicombatsimulator/./src/combatsimulator/achievement.js","webpack://mwicombatsimulator/./src/combatsimulator/buff.js","webpack://mwicombatsimulator/./src/combatsimulator/combatUnit.js","webpack://mwicombatsimulator/./src/combatsimulator/consumable.js","webpack://mwicombatsimulator/./src/combatsimulator/equipment.js","webpack://mwicombatsimulator/./src/combatsimulator/houseRoom.js","webpack://mwicombatsimulator/./src/combatsimulator/player.js","webpack://mwicombatsimulator/./src/combatsimulator/trigger.js","webpack://mwicombatsimulator/webpack/bootstrap","webpack://mwicombatsimulator/webpack/runtime/define property getters","webpack://mwicombatsimulator/webpack/runtime/get javascript chunk filename","webpack://mwicombatsimulator/webpack/runtime/global","webpack://mwicombatsimulator/webpack/runtime/hasOwnProperty shorthand","webpack://mwicombatsimulator/webpack/runtime/make namespace object","webpack://mwicombatsimulator/webpack/runtime/publicPath","webpack://mwicombatsimulator/webpack/runtime/jsonp chunk loading","webpack://mwicombatsimulator/./src/main.js"],"sourcesContent":["import Buff from \"./buff\";\r\nimport abilityDetailMap from \"./data/abilityDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nconst abilityFromCombatStat = {\r\n \"blaze\":\r\n {\r\n \"hrid\": \"/abilities/blaze\",\r\n \"name\": \"Blaze\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"allEnemies\",\r\n \"effectType\": \"/ability_effect_types/damage\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"/damage_types/fire\",\r\n \"baseDamageFlat\": 0,\r\n \"baseDamageFlatLevelBonus\": 0.0,\r\n \"baseDamageRatio\": 0.3,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/number_of_active_units\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n },\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/current_hp\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\r\n \"value\": 1\r\n }\r\n ],\r\n },\r\n \"bloom\":\r\n {\r\n \"hrid\": \"/abilities/bloom\",\r\n \"name\": \"Bloom\",\r\n \"description\": \"\",\r\n \"isSpecialAbility\": false,\r\n \"manaCost\": 0,\r\n \"cooldownDuration\": 0,\r\n \"castDuration\": 0,\r\n \"abilityEffects\": [\r\n {\r\n \"targetType\": \"lowestHpAlly\",\r\n \"effectType\": \"/ability_effect_types/heal\",\r\n \"combatStyleHrid\": \"/combat_styles/magic\",\r\n \"damageType\": \"\",\r\n \"baseDamageFlat\": 10,\r\n \"baseDamageFlatLevelBonus\": 0,\r\n \"baseDamageRatio\": 0.15,\r\n \"baseDamageRatioLevelBonus\": 0,\r\n \"bonusAccuracyRatio\": 0,\r\n \"bonusAccuracyRatioLevelBonus\": 0,\r\n \"damageOverTimeRatio\": 0,\r\n \"damageOverTimeDuration\": 0,\r\n \"armorDamageRatio\": 0,\r\n \"armorDamageRatioLevelBonus\": 0,\r\n \"hpDrainRatio\": 0,\r\n \"pierceChance\": 0,\r\n \"blindChance\": 0,\r\n \"blindDuration\": 0,\r\n \"silenceChance\": 0,\r\n \"silenceDuration\": 0,\r\n \"stunChance\": 0,\r\n \"stunDuration\": 0,\r\n \"spendHpRatio\": 0,\r\n \"buffs\": null\r\n }\r\n ],\r\n \"defaultCombatTriggers\": [\r\n {\r\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_allies\",\r\n \"conditionHrid\": \"/combat_trigger_conditions/lowest_hp_percentage\",\r\n \"comparatorHrid\": \"/combat_trigger_comparators/less_than_equal\",\r\n \"value\": 100\r\n }\r\n ],\r\n }\r\n}\r\n\r\nclass Ability {\r\n constructor(hrid, level = 1, triggers = null) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameAbility = abilityDetailMap[hrid];\r\n if (!gameAbility) {\r\n gameAbility = abilityFromCombatStat[hrid];\r\n }\r\n if (!gameAbility) {\r\n throw new Error(\"No ability found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.manaCost = gameAbility.manaCost;\r\n this.cooldownDuration = gameAbility.cooldownDuration;\r\n this.castDuration = gameAbility.castDuration;\r\n this.isSpecialAbility = gameAbility.isSpecialAbility;\r\n\r\n this.abilityEffects = [];\r\n\r\n for (const effect of gameAbility.abilityEffects) {\r\n let abilityEffect = {\r\n targetType: effect.targetType,\r\n effectType: effect.effectType,\r\n combatStyleHrid: effect.combatStyleHrid,\r\n damageType: effect.damageType,\r\n damageFlat: effect.baseDamageFlat + (this.level - 1) * effect.baseDamageFlatLevelBonus,\r\n damageRatio: effect.baseDamageRatio + (this.level - 1) * effect.baseDamageRatioLevelBonus,\r\n bonusAccuracyRatio: effect.bonusAccuracyRatio + (this.level - 1) * effect.bonusAccuracyRatioLevelBonus,\r\n damageOverTimeRatio: effect.damageOverTimeRatio,\r\n damageOverTimeDuration: effect.damageOverTimeDuration,\r\n armorDamageRatio: effect.armorDamageRatio + (this.level - 1) * effect.armorDamageRatioLevelBonus,\r\n hpDrainRatio: effect.hpDrainRatio,\r\n pierceChance: effect.pierceChance,\r\n blindChance: effect.blindChance,\r\n blindDuration: effect.blindDuration,\r\n silenceChance: effect.silenceChance,\r\n silenceDuration: effect.silenceDuration,\r\n stunChance: effect.stunChance,\r\n stunDuration: effect.stunDuration,\r\n spendHpRatio: effect.spendHpRatio,\r\n buffs: null,\r\n };\r\n if (effect.buffs) {\r\n abilityEffect.buffs = [];\r\n for (const buff of effect.buffs) {\r\n abilityEffect.buffs.push(new Buff(buff, this.level));\r\n }\r\n }\r\n this.abilityEffects.push(abilityEffect);\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameAbility.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let ability = new Ability(dto.hrid, dto.level, triggers);\r\n\r\n return ability;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n\r\n if (source.isSilenced) {\r\n return false;\r\n }\r\n\r\n let haste = source.combatDetails.combatStats.abilityHaste;\r\n let cooldownDuration = this.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Ability;\r\n","import Buff from \"./buff\";\r\nimport achievementTierDetailMap from \"./data/achievementTierDetailMap.json\";\r\nimport achievementDetailMap from \"./data/achievementDetailMap.json\";\r\n\r\nclass Achievement {\r\n constructor(achievements) {\r\n this.achievements = achievements;\r\n this.buffs = [];\r\n\r\n for(const tier of Object.values(achievementTierDetailMap)) {\r\n let isGetAll = true;\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid)\r\n for(const achievement of Object.values(detailMap)) {\r\n if(!this.achievements[achievement.hrid] || this.achievements[achievement.hrid] == false) {\r\n isGetAll = false;\r\n break;\r\n }\r\n }\r\n if(isGetAll) {\r\n let buff = new Buff(tier.buff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default Achievement;","class Buff {\r\n startTime;\r\n\r\n constructor(buff, level = 1) {\r\n this.uniqueHrid = buff.uniqueHrid;\r\n this.typeHrid = buff.typeHrid;\r\n this.ratioBoost = buff.ratioBoost + (level - 1) * buff.ratioBoostLevelBonus;\r\n this.flatBoost = buff.flatBoost + (level - 1) * buff.flatBoostLevelBonus;\r\n this.duration = buff.duration;\r\n this.multiplierForSkillHrid = buff.multiplierForSkillHrid ?? \"\";\r\n this.multiplierPerSkillLevel = buff.multiplierPerSkillLevel ?? 0;\r\n }\r\n}\r\n\r\nexport default Buff;\r\n","class CombatUnit {\r\n isPlayer;\r\n isStunned = false;\r\n stunExpireTime = null;\r\n isBlinded = false;\r\n blindExpireTime = null;\r\n isSilenced = false;\r\n silenceExpireTime = null;\r\n\r\n isOutOfMana = false;\r\n\r\n // Base levels which don't change after initialization\r\n staminaLevel = 1;\r\n intelligenceLevel = 1;\r\n attackLevel = 1;\r\n meleeLevel = 1;\r\n defenseLevel = 1;\r\n rangedLevel = 1;\r\n magicLevel = 1;\r\n\r\n experience = 0;\r\n experienceRate = 0;\r\n enrageTime = 0;\r\n\r\n abilities = [null, null, null, null];\r\n food = [null, null, null];\r\n drinks = [null, null, null];\r\n houseRooms = [];\r\n achievements = null;\r\n dropTable = [];\r\n rareDropTable = [];\r\n abilityManaCosts = new Map();\r\n\r\n // Calculated combat stats including temporary buffs\r\n combatDetails = {\r\n staminaLevel: 1,\r\n intelligenceLevel: 1,\r\n attackLevel: 1,\r\n meleeLevel: 1,\r\n defenseLevel: 1,\r\n rangedLevel: 1,\r\n magicLevel: 1,\r\n maxHitpoints: 110,\r\n currentHitpoints: 110,\r\n maxManapoints: 110,\r\n currentManapoints: 110,\r\n stabAccuracyRating: 11,\r\n slashAccuracyRating: 11,\r\n smashAccuracyRating: 11,\r\n rangedAccuracyRating: 11,\r\n magicAccuracyRating: 11,\r\n stabMaxDamage: 11,\r\n slashMaxDamage: 11,\r\n smashMaxDamage: 11,\r\n rangedMaxDamage: 11,\r\n magicMaxDamage: 11,\r\n stabEvasionRating: 11,\r\n slashEvasionRating: 11,\r\n smashEvasionRating: 11,\r\n rangedEvasionRating: 11,\r\n magicEvasionRating: 11,\r\n defensiveMaxDamage: 0,\r\n totalArmor: 0.2,\r\n totalWaterResistance: 0.4,\r\n totalNatureResistance: 0.4,\r\n totalFireResistance: 0.4,\r\n abilityHaste: 0,\r\n tenacity: 0,\r\n totalThreat: 100,\r\n combatStats: {\r\n combatStyleHrid: \"/combat_styles/smash\",\r\n damageType: \"/damage_types/physical\",\r\n attackInterval: 3000000000,\r\n autoAttackDamage: 0,\r\n abilityDamage: 0,\r\n criticalRate: 0,\r\n criticalDamage: 0,\r\n stabAccuracy: 0,\r\n slashAccuracy: 0,\r\n smashAccuracy: 0,\r\n rangedAccuracy: 0,\r\n magicAccuracy: 0,\r\n stabDamage: 0,\r\n slashDamage: 0,\r\n smashDamage: 0,\r\n rangedDamage: 0,\r\n magicDamage: 0,\r\n defensiveDamage: 0,\r\n taskDamage: 0,\r\n physicalAmplify: 0,\r\n waterAmplify: 0,\r\n natureAmplify: 0,\r\n fireAmplify: 0,\r\n healingAmplify: 0,\r\n physicalThorns: 0,\r\n elementalThorns: 0,\r\n maxHitpoints: 0,\r\n maxManapoints: 0,\r\n stabEvasion: 0,\r\n slashEvasion: 0,\r\n smashEvasion: 0,\r\n rangedEvasion: 0,\r\n magicEvasion: 0,\r\n armor: 0,\r\n waterResistance: 0,\r\n natureResistance: 0,\r\n fireResistance: 0,\r\n lifeSteal: 0,\r\n hpRegenPer10: 0.01,\r\n mpRegenPer10: 0.01,\r\n combatDropRate: 0,\r\n combatDropQuantity: 0,\r\n combatRareFind: 0,\r\n combatExperience: 0,\r\n foodSlots: 1,\r\n drinkSlots: 1,\r\n armorPenetration: 0,\r\n waterPenetration: 0,\r\n naturePenetration: 0,\r\n firePenetration: 0,\r\n manaLeech: 0,\r\n castSpeed: 0,\r\n threat: 100,\r\n parry: 0,\r\n mayhem: 0,\r\n pierce: 0,\r\n curse: 0,\r\n ripple: 0,\r\n bloom: 0,\r\n blaze: 0,\r\n weaken: 0,\r\n fury: 0,\r\n foodHaste: 0,\r\n drinkConcentration: 0,\r\n damageTaken: 0,\r\n attackSpeed: 0,\r\n armorDamageRatio: 0,\r\n hpDrainRatio: 0,\r\n primaryTraining: \"\",\r\n focusTraining: \"\",\r\n staminaExperience: 0,\r\n intelligenceExperience: 0,\r\n attackExperience: 0,\r\n defenseExperience: 0,\r\n meleeExperience: 0,\r\n rangedExperience: 0,\r\n magicExperience: 0,\r\n retaliation: 0,\r\n },\r\n };\r\n combatBuffs = {};\r\n permanentBuffs = {};\r\n zoneBuffs = {};\r\n extraBuffs = {};\r\n\r\n constructor() { }\r\n\r\n updateCombatDetails() {\r\n if (this.isPlayer) {\r\n if (this.combatDetails.combatStats.hpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.hpRegenPer10 = 0.01 + this.combatDetails.combatStats.hpRegenPer10;\r\n }\r\n if (this.combatDetails.combatStats.mpRegenPer10 === 0) {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01;\r\n } else {\r\n this.combatDetails.combatStats.mpRegenPer10 = 0.01 + this.combatDetails.combatStats.mpRegenPer10;\r\n }\r\n }\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((stat) => {\r\n this.combatDetails[stat + \"Level\"] = this[stat + \"Level\"];\r\n let boosts = this.getBuffBoosts(\"/buff_types/\" + stat + \"_level\");\r\n boosts.forEach((buff) => {\r\n this.combatDetails[stat + \"Level\"] += (this[stat + \"Level\"] * buff.ratioBoost);\r\n this.combatDetails[stat + \"Level\"] += buff.flatBoost;\r\n });\r\n });\r\n\r\n this.combatDetails.maxHitpoints = Math.floor\r\n (10 * (10 + this.combatDetails.staminaLevel) + this.combatDetails.combatStats.maxHitpoints);\r\n this.combatDetails.maxManapoints = Math.floor\r\n (10 * (10 + this.combatDetails.intelligenceLevel) + this.combatDetails.combatStats.maxManapoints);\r\n\r\n let accuracyRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_accuracy\").ratioBoost;\r\n let damageRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_damage\").ratioBoost;\r\n // if (accuracyRatioBoostFromFury > 0) {\r\n // console.log(\"Fury Boost: \" + accuracyRatioBoostFromFury);\r\n // }\r\n\r\n let accuracyRatioBoost = this.getBuffBoost(\"/buff_types/accuracy\").ratioBoost;\r\n let damageRatioBoost = this.getBuffBoost(\"/buff_types/damage\").ratioBoost;\r\n\r\n [\"stab\", \"slash\", \"smash\"].forEach((style) => {\r\n this.combatDetails[style + \"AccuracyRating\"] =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Accuracy\"]) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails[style + \"MaxDamage\"] =\r\n (10 + this.combatDetails.meleeLevel) *\r\n (1 + this.combatDetails.combatStats[style + \"Damage\"]) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n let baseEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats[style + \"Evasion\"]);\r\n this.combatDetails[style + \"EvasionRating\"] = baseEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails[style + \"EvasionRating\"] += boost.flatBoost;\r\n this.combatDetails[style + \"EvasionRating\"] += baseEvasion * boost.ratioBoost;\r\n }\r\n });\r\n\r\n this.combatDetails.defensiveMaxDamage = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.defensiveDamage);\r\n\r\n // when equiped bulwark\r\n if (this.equipment?.['/equipment_types/two_hand']?.hrid.includes(\"bulwark\")) {\r\n this.combatDetails.smashMaxDamage += this.combatDetails.defensiveMaxDamage;\r\n }\r\n\r\n this.combatDetails.rangedAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.rangedAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.rangedMaxDamage =\r\n (10 + this.combatDetails.rangedLevel) *\r\n (1 + this.combatDetails.combatStats.rangedDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseRangedEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.rangedEvasion);\r\n this.combatDetails.rangedEvasionRating = baseRangedEvasion;\r\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.rangedEvasionRating += boost.flatBoost;\r\n this.combatDetails.rangedEvasionRating += baseRangedEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.damageTaken = this.getBuffBoost(\"/buff_types/damage_taken\").flatBoost;\r\n // if (this.combatDetails.combatStats.damageTaken > 0) {\r\n // console.log(\"Damage taken: \" + this.combatDetails.combatStats.damageTaken);\r\n // }\r\n\r\n this.combatDetails.magicAccuracyRating =\r\n (10 + this.combatDetails.attackLevel) *\r\n (1 + this.combatDetails.combatStats.magicAccuracy) *\r\n (1 + accuracyRatioBoost) *\r\n (1 + accuracyRatioBoostFromFury);\r\n this.combatDetails.magicMaxDamage =\r\n (10 + this.combatDetails.magicLevel) *\r\n (1 + this.combatDetails.combatStats.magicDamage) *\r\n (1 + damageRatioBoost) *\r\n (1 + damageRatioBoostFromFury);\r\n\r\n let baseMagicEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.magicEvasion);\r\n this.combatDetails.magicEvasionRating = baseMagicEvasion;\r\n for (const boost of evasionBoosts) {\r\n this.combatDetails.magicEvasionRating += boost.flatBoost;\r\n this.combatDetails.magicEvasionRating += baseMagicEvasion * boost.ratioBoost;\r\n }\r\n\r\n this.combatDetails.combatStats.physicalAmplify += this.getBuffBoost(\"/buff_types/physical_amplify\").flatBoost;\r\n this.combatDetails.combatStats.waterAmplify += this.getBuffBoost(\"/buff_types/water_amplify\").flatBoost;\r\n this.combatDetails.combatStats.natureAmplify += this.getBuffBoost(\"/buff_types/nature_amplify\").flatBoost;\r\n this.combatDetails.combatStats.fireAmplify += this.getBuffBoost(\"/buff_types/fire_amplify\").flatBoost;\r\n\r\n this.combatDetails.combatStats.attackInterval /= (1 + (this.combatDetails.attackLevel / 2000));\r\n\r\n let baseAttackSpeed = this.combatDetails.combatStats.attackSpeed;\r\n this.combatDetails.combatStats.attackInterval /= (1 + baseAttackSpeed);\r\n let attackIntervalBoosts = this.getBuffBoosts(\"/buff_types/attack_speed\");\r\n let attackIntervalRatioBoost = attackIntervalBoosts\r\n .map((boost) => boost.ratioBoost)\r\n .reduce((prev, cur) => prev + cur, 0);\r\n this.combatDetails.combatStats.attackInterval /= (1 + attackIntervalRatioBoost);\r\n\r\n let baseArmor = 0.2 * this.combatDetails.defenseLevel + this.combatDetails.combatStats.armor;\r\n this.combatDetails.totalArmor = baseArmor;\r\n let armorBoosts = this.getBuffBoosts(\"/buff_types/armor\");\r\n for (const boost of armorBoosts) {\r\n this.combatDetails.totalArmor += boost.flatBoost;\r\n this.combatDetails.totalArmor += baseArmor * boost.ratioBoost;\r\n }\r\n\r\n let baseWaterResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.waterResistance;\r\n this.combatDetails.totalWaterResistance = baseWaterResistance;\r\n let waterResistanceBoosts = this.getBuffBoosts(\"/buff_types/water_resistance\");\r\n for (const boost of waterResistanceBoosts) {\r\n this.combatDetails.totalWaterResistance += boost.flatBoost;\r\n this.combatDetails.totalWaterResistance += baseWaterResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseNatureResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.natureResistance;\r\n this.combatDetails.totalNatureResistance = baseNatureResistance;\r\n let natureResistanceBoosts = this.getBuffBoosts(\"/buff_types/nature_resistance\");\r\n for (const boost of natureResistanceBoosts) {\r\n this.combatDetails.totalNatureResistance += boost.flatBoost;\r\n this.combatDetails.totalNatureResistance += baseNatureResistance * boost.ratioBoost;\r\n }\r\n\r\n let baseFireResistance =\r\n 0.2 * this.combatDetails.defenseLevel +\r\n this.combatDetails.combatStats.fireResistance;\r\n this.combatDetails.totalFireResistance = baseFireResistance;\r\n let fireResistanceBoosts = this.getBuffBoosts(\"/buff_types/fire_resistance\");\r\n for (const boost of fireResistanceBoosts) {\r\n this.combatDetails.totalFireResistance += boost.flatBoost;\r\n this.combatDetails.totalFireResistance += baseFireResistance * boost.ratioBoost;\r\n }\r\n\r\n let hpRegenBoosts = this.getBuffBoost(\"/buff_types/hp_regen\");\r\n this.combatDetails.combatStats.hpRegenPer10 += this.combatDetails.combatStats.hpRegenPer10 * hpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.hpRegenPer10 += hpRegenBoosts.flatBoost;\r\n\r\n let mpRegenBoosts = this.getBuffBoost(\"/buff_types/mp_regen\");\r\n this.combatDetails.combatStats.mpRegenPer10 += this.combatDetails.combatStats.mpRegenPer10 * mpRegenBoosts.ratioBoost;\r\n this.combatDetails.combatStats.mpRegenPer10 += mpRegenBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.lifeSteal += this.getBuffBoost(\"/buff_types/life_steal\").flatBoost;\r\n this.combatDetails.combatStats.physicalThorns += this.getBuffBoost(\r\n \"/buff_types/physical_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.elementalThorns += this.getBuffBoost(\r\n \"/buff_types/elemental_thorns\"\r\n ).flatBoost;\r\n this.combatDetails.combatStats.combatExperience += this.getBuffBoost(\"/buff_types/wisdom\").flatBoost;\r\n this.combatDetails.combatStats.criticalRate += this.getBuffBoost(\"/buff_types/critical_rate\").flatBoost;\r\n this.combatDetails.combatStats.criticalDamage += this.getBuffBoost(\"/buff_types/critical_damage\").flatBoost;\r\n\r\n this.combatDetails.combatStats.castSpeed += this.getBuffBoost(\"/buff_types/cast_speed\").flatBoost;\r\n this.combatDetails.combatStats.castSpeed += this.combatDetails[\"attackLevel\"] / 2000;\r\n\r\n let combatDropRateBoosts = this.getBuffBoost(\"/buff_types/combat_drop_rate\");\r\n this.combatDetails.combatStats.combatDropRate += (1 + this.combatDetails.combatStats.combatDropRate) * combatDropRateBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropRate += combatDropRateBoosts.flatBoost;\r\n let combatRareFindBoosts = this.getBuffBoost(\"/buff_types/rare_find\");\r\n this.combatDetails.combatStats.combatRareFind += (1 + this.combatDetails.combatStats.combatRareFind) * combatRareFindBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatRareFind += combatRareFindBoosts.flatBoost;\r\n let combatDropQuantityBoosts = this.getBuffBoost(\"/buff_types/combat_drop_quantity\");\r\n this.combatDetails.combatStats.combatDropQuantity += (1 + this.combatDetails.combatStats.combatDropQuantity) * combatDropQuantityBoosts.ratioBoost;\r\n this.combatDetails.combatStats.combatDropQuantity += combatDropQuantityBoosts.flatBoost;\r\n\r\n let baseThreat = 100 + this.combatDetails.combatStats.threat;\r\n this.combatDetails.totalThreat = baseThreat;\r\n let threatBoosts = this.getBuffBoost(\"/buff_types/threat\");\r\n if (threatBoosts.ratioBoost !== 0) {\r\n this.combatDetails.combatStats.threat += baseThreat * threatBoosts.ratioBoost;\r\n } else {\r\n this.combatDetails.combatStats.threat = baseThreat;\r\n }\r\n this.combatDetails.combatStats.threat += threatBoosts.flatBoost;\r\n\r\n this.combatDetails.combatStats.retaliation += this.getBuffBoost(\"/buff_types/retaliation\").flatBoost;\r\n }\r\n\r\n addBuff(buff, currentTime) {\r\n buff.startTime = currentTime;\r\n this.combatBuffs[buff.uniqueHrid] = buff;\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n removeBuff(buff) {\r\n if (!this.combatBuffs[buff.uniqueHrid]) {\r\n return;\r\n }\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n addPermanentBuff(buff) {\r\n if (this.permanentBuffs[buff.typeHrid]) {\r\n this.permanentBuffs[buff.typeHrid].flatBoost += buff.flatBoost;\r\n this.permanentBuffs[buff.typeHrid].ratioBoost += buff.ratioBoost;\r\n } else {\r\n this.permanentBuffs[buff.typeHrid] = buff;\r\n }\r\n }\r\n\r\n generatePermanentBuffs() {\r\n for (let i = 0; i < this.houseRooms.length; i++) {\r\n const houseRoom = this.houseRooms[i];\r\n houseRoom.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n\r\n if (this.achievements) {\r\n this.achievements.buffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.zoneBuffs) {\r\n this.zoneBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n if (this.extraBuffs) {\r\n this.extraBuffs.forEach(buff => {\r\n this.addPermanentBuff(buff);\r\n });\r\n }\r\n }\r\n\r\n removeExpiredBuffs(currentTime) {\r\n let expiredBuffs = Object.values(this.combatBuffs).filter(\r\n (buff) => buff.startTime + buff.duration <= currentTime\r\n );\r\n expiredBuffs.forEach((buff) => {\r\n delete this.combatBuffs[buff.uniqueHrid];\r\n });\r\n\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearBuffs() {\r\n this.combatBuffs = structuredClone(this.permanentBuffs);\r\n this.updateCombatDetails();\r\n }\r\n\r\n clearCCs() {\r\n this.isStunned = false;\r\n this.stunExpireTime = null;\r\n this.isSilenced = false;\r\n this.silenceExpireTime = null;\r\n this.isBlinded = false;\r\n this.blindExpireTime = null;\r\n this.combatDetails.combatStats.damageTaken = 0;\r\n }\r\n\r\n getBuffBoosts(type) {\r\n let boosts = [];\r\n Object.values(this.combatBuffs)\r\n .filter((buff) => buff.typeHrid == type)\r\n .forEach((buff) => {\r\n boosts.push({ ratioBoost: buff.ratioBoost, flatBoost: buff.flatBoost });\r\n });\r\n\r\n return boosts;\r\n }\r\n\r\n getBuffBoost(type) {\r\n let boosts = this.getBuffBoosts(type);\r\n\r\n let boost = {\r\n ratioBoost: 0,\r\n flatBoost: 0,\r\n };\r\n\r\n for (let i = 0; i < boosts.length; i++) {\r\n boost.ratioBoost += boosts[i]?.ratioBoost ?? 0;\r\n boost.flatBoost += boosts[i]?.flatBoost ?? 0;\r\n }\r\n\r\n return boost;\r\n }\r\n\r\n reset(currentTime = 0) {\r\n this.clearCCs();\r\n this.clearBuffs();\r\n this.updateCombatDetails();\r\n this.resetCooldowns(currentTime);\r\n\r\n this.combatDetails.currentHitpoints = this.combatDetails.maxHitpoints;\r\n this.combatDetails.currentManapoints = this.combatDetails.maxManapoints;\r\n }\r\n\r\n resetCooldowns(currentTime = 0) {\r\n this.food.filter((food) => food != null).forEach((food) => (food.lastUsed = Number.MIN_SAFE_INTEGER));\r\n this.drinks.filter((drink) => drink != null).forEach((drink) => (drink.lastUsed = Number.MIN_SAFE_INTEGER));\r\n\r\n let haste = this.combatDetails.combatStats.abilityHaste;\r\n\r\n this.abilities\r\n .filter((ability) => ability != null)\r\n .forEach((ability) => {\r\n if (this.isPlayer) {\r\n ability.lastUsed = Number.MIN_SAFE_INTEGER;\r\n } else {\r\n let cooldownDuration = ability.cooldownDuration;\r\n if (haste > 0) {\r\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\r\n }\r\n ability.lastUsed = currentTime - Math.floor(cooldownDuration * 0.5) + Math.floor(Math.random() * cooldownDuration * 0.5);\r\n }\r\n });\r\n }\r\n\r\n addHitpoints(hitpoints) {\r\n let hitpointsAdded = 0;\r\n\r\n if (this.combatDetails.currentHitpoints >= this.combatDetails.maxHitpoints) {\r\n return hitpointsAdded;\r\n }\r\n\r\n let newHitpoints = Math.min(this.combatDetails.currentHitpoints + hitpoints, this.combatDetails.maxHitpoints);\r\n hitpointsAdded = newHitpoints - this.combatDetails.currentHitpoints;\r\n this.combatDetails.currentHitpoints = newHitpoints;\r\n\r\n return hitpointsAdded;\r\n }\r\n\r\n addManapoints(manapoints) {\r\n let manapointsAdded = 0;\r\n\r\n if (this.combatDetails.currentManapoints >= this.combatDetails.maxManapoints) {\r\n return manapointsAdded;\r\n }\r\n\r\n let newManapoints = Math.min(\r\n this.combatDetails.currentManapoints + manapoints,\r\n this.combatDetails.maxManapoints\r\n );\r\n manapointsAdded = newManapoints - this.combatDetails.currentManapoints;\r\n this.combatDetails.currentManapoints = newManapoints;\r\n\r\n return manapointsAdded;\r\n }\r\n}\r\n\r\nexport default CombatUnit;\r\n","import Buff from \"./buff\";\r\nimport itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport Trigger from \"./trigger\";\r\n\r\nclass Consumable {\r\n constructor(hrid, triggers = null) {\r\n this.hrid = hrid;\r\n\r\n let gameConsumable = itemDetailMap[this.hrid];\r\n if (!gameConsumable) {\r\n throw new Error(\"No consumable found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.cooldownDuration = gameConsumable.consumableDetail.cooldownDuration;\r\n this.hitpointRestore = gameConsumable.consumableDetail.hitpointRestore;\r\n this.manapointRestore = gameConsumable.consumableDetail.manapointRestore;\r\n this.recoveryDuration = gameConsumable.consumableDetail.recoveryDuration;\r\n this.catagoryHrid = gameConsumable.categoryHrid;\r\n\r\n this.buffs = [];\r\n if (gameConsumable.consumableDetail.buffs) {\r\n for (const consumableBuff of gameConsumable.consumableDetail.buffs) {\r\n let buff = new Buff(consumableBuff);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n\r\n if (triggers) {\r\n this.triggers = triggers;\r\n } else {\r\n this.triggers = [];\r\n for (const defaultTrigger of gameConsumable.consumableDetail.defaultCombatTriggers) {\r\n let trigger = new Trigger(\r\n defaultTrigger.dependencyHrid,\r\n defaultTrigger.conditionHrid,\r\n defaultTrigger.comparatorHrid,\r\n defaultTrigger.value\r\n );\r\n this.triggers.push(trigger);\r\n }\r\n }\r\n\r\n this.lastUsed = Number.MIN_SAFE_INTEGER;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\r\n let consumable = new Consumable(dto.hrid, triggers);\r\n\r\n return consumable;\r\n }\r\n\r\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\r\n if (source.isStunned) {\r\n return false;\r\n }\r\n let consumableHaste;\r\n if (this.catagoryHrid.includes(\"food\")) {\r\n consumableHaste = source.combatDetails.combatStats.foodHaste\r\n } else {\r\n consumableHaste = source.combatDetails.combatStats.drinkConcentration;\r\n }\r\n let cooldownDuration = this.cooldownDuration;\r\n if (consumableHaste > 0) {\r\n cooldownDuration = cooldownDuration / (1 + consumableHaste);\r\n }\r\n\r\n if (this.lastUsed + cooldownDuration > currentTime) {\r\n return false;\r\n }\r\n\r\n if (this.triggers.length == 0) {\r\n return true;\r\n }\r\n\r\n let shouldTrigger = true;\r\n for (const trigger of this.triggers) {\r\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\r\n shouldTrigger = false;\r\n }\r\n }\r\n\r\n return shouldTrigger;\r\n }\r\n}\r\n\r\nexport default Consumable;\r\n","import itemDetailMap from \"./data/itemDetailMap.json\";\r\nimport enhancementLevelTotalMultiplierTable from \"./data/enhancementLevelTotalBonusMultiplierTable.json\";\r\n\r\nclass Equipment {\r\n constructor(hrid, enhancementLevel) {\r\n this.hrid = hrid;\r\n let gameItem = itemDetailMap[this.hrid];\r\n if (!gameItem) {\r\n throw new Error(\"No equipment found for hrid: \" + this.hrid);\r\n }\r\n this.gameItem = gameItem;\r\n this.enhancementLevel = enhancementLevel;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let equipment = new Equipment(dto.hrid, dto.enhancementLevel);\r\n\r\n return equipment;\r\n }\r\n\r\n getCombatStat(combatStat) {\r\n let multiplier = enhancementLevelTotalMultiplierTable[this.enhancementLevel];\r\n if(this.gameItem.equipmentDetail.combatStats[combatStat]) {\r\n let enhancementBonus = this.gameItem.equipmentDetail.combatEnhancementBonuses[combatStat] || 0;\r\n let stat = this.gameItem.equipmentDetail.combatStats[combatStat] + multiplier * enhancementBonus;\r\n return stat;\r\n }\r\n return 0;\r\n }\r\n\r\n getCombatStyle() {\r\n return this.gameItem.equipmentDetail.combatStats.combatStyleHrids[0];\r\n }\r\n\r\n getDamageType() {\r\n return this.gameItem.equipmentDetail.combatStats.damageType;\r\n }\r\n\r\n getPrimaryTraining() {\r\n return this.gameItem.equipmentDetail.combatStats.primaryTraining;\r\n }\r\n\r\n getFocusTraining(){\r\n return this.gameItem.equipmentDetail.combatStats.focusTraining;\r\n }\r\n}\r\n\r\nexport default Equipment;\r\n","import Buff from \"./buff\";\r\nimport houseRoomDetailMap from \"./data/houseRoomDetailMap.json\";\r\n\r\nclass HouseRoom {\r\n constructor(hrid, level) {\r\n this.hrid = hrid;\r\n this.level = level;\r\n\r\n let gameHouseRoom = houseRoomDetailMap[this.hrid];\r\n if (!gameHouseRoom) {\r\n throw new Error(\"No house room found for hrid: \" + this.hrid);\r\n }\r\n\r\n this.buffs = [];\r\n if (gameHouseRoom.actionBuffs) {\r\n for (const actionBuff of gameHouseRoom.actionBuffs) {\r\n let buff = new Buff(actionBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n if (gameHouseRoom.globalBuffs) {\r\n for (const globalBuff of gameHouseRoom.globalBuffs) {\r\n let buff = new Buff(globalBuff, level);\r\n this.buffs.push(buff);\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport default HouseRoom;","import Ability from \"./ability\";\r\nimport CombatUnit from \"./combatUnit\";\r\nimport Consumable from \"./consumable\";\r\nimport Equipment from \"./equipment\";\r\nimport HouseRoom from \"./houseRoom\";\r\nimport Achievement from \"./achievement\";\r\n\r\nclass Player extends CombatUnit {\r\n equipment = {\r\n \"/equipment_types/head\": null,\r\n \"/equipment_types/body\": null,\r\n \"/equipment_types/legs\": null,\r\n \"/equipment_types/feet\": null,\r\n \"/equipment_types/hands\": null,\r\n \"/equipment_types/main_hand\": null,\r\n \"/equipment_types/two_hand\": null,\r\n \"/equipment_types/off_hand\": null,\r\n \"/equipment_types/pouch\": null,\r\n \"/equipment_types/back\": null,\r\n };\r\n\r\n constructor() {\r\n super();\r\n\r\n this.isPlayer = true;\r\n this.hrid = \"player\";\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let player = new Player();\r\n\r\n player.staminaLevel = dto.staminaLevel;\r\n player.intelligenceLevel = dto.intelligenceLevel;\r\n player.attackLevel = dto.attackLevel;\r\n player.meleeLevel = dto.meleeLevel;\r\n player.defenseLevel = dto.defenseLevel;\r\n player.rangedLevel = dto.rangedLevel;\r\n player.magicLevel = dto.magicLevel;\r\n\r\n player.hrid = dto.hrid;\r\n\r\n for (const [key, value] of Object.entries(dto.equipment)) {\r\n player.equipment[key] = value ? Equipment.createFromDTO(value) : null;\r\n }\r\n\r\n player.food = dto.food.map((food) => (food ? Consumable.createFromDTO(food) : null));\r\n player.drinks = dto.drinks.map((drink) => (drink ? Consumable.createFromDTO(drink) : null));\r\n player.abilities = dto.abilities.map((ability) => (ability ? Ability.createFromDTO(ability) : null));\r\n Object.entries(dto.houseRooms).forEach(houseRoom => {\r\n if (houseRoom[1] > 0) {\r\n player.houseRooms.push(new HouseRoom(houseRoom[0], houseRoom[1]))\r\n }\r\n });\r\n\r\n player.achievements = new Achievement(dto.achievements);\r\n\r\n player.debuffOnLevelGap = dto.debuffOnLevelGap;\r\n\r\n return player;\r\n }\r\n\r\n updateCombatDetails() {\r\n if (this.equipment[\"/equipment_types/main_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/main_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/main_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/main_hand\"].getPrimaryTraining();\r\n } else if (this.equipment[\"/equipment_types/two_hand\"]) {\r\n this.combatDetails.combatStats.combatStyleHrid =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStyle();\r\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/two_hand\"].getDamageType();\r\n this.combatDetails.combatStats.attackInterval =\r\n this.equipment[\"/equipment_types/two_hand\"].getCombatStat(\"attackInterval\");\r\n this.combatDetails.combatStats.primaryTraining = \r\n this.equipment[\"/equipment_types/two_hand\"].getPrimaryTraining();\r\n } else {\r\n this.combatDetails.combatStats.combatStyleHrid = \"/combat_styles/smash\";\r\n this.combatDetails.combatStats.damageType = \"/damage_types/physical\";\r\n this.combatDetails.combatStats.attackInterval = 3000000000;\r\n this.combatDetails.combatStats.primaryTraining = \"/skills/melee\";\r\n }\r\n\r\n if (this.equipment[\"/equipment_types/charm\"]) {\r\n this.combatDetails.combatStats.focusTraining = this.equipment[\"/equipment_types/charm\"].getFocusTraining();\r\n } else {\r\n this.combatDetails.combatStats.focusTraining = \"\";\r\n }\r\n\r\n [\r\n \"stabAccuracy\",\r\n \"slashAccuracy\",\r\n \"smashAccuracy\",\r\n \"rangedAccuracy\",\r\n \"magicAccuracy\",\r\n \"stabDamage\",\r\n \"slashDamage\",\r\n \"smashDamage\",\r\n \"rangedDamage\",\r\n \"magicDamage\",\r\n \"defensiveDamage\",\r\n \"taskDamage\",\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"stabEvasion\",\r\n \"slashEvasion\",\r\n \"smashEvasion\",\r\n \"rangedEvasion\",\r\n \"magicEvasion\",\r\n \"armor\",\r\n \"waterResistance\",\r\n \"natureResistance\",\r\n \"fireResistance\",\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"combatDropRate\",\r\n \"combatRareFind\",\r\n \"combatDropQuantity\",\r\n \"combatExperience\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"abilityHaste\",\r\n \"tenacity\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"threat\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"foodHaste\",\r\n \"drinkConcentration\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\",\r\n \"retaliation\"\r\n ].forEach((stat) => {\r\n this.combatDetails.combatStats[stat] = Object.values(this.equipment)\r\n .filter((equipment) => equipment != null)\r\n .map((equipment) => equipment.getCombatStat(stat))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n });\r\n\r\n if (this.equipment[\"/equipment_types/pouch\"]) {\r\n this.combatDetails.combatStats.foodSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"foodSlots\");\r\n this.combatDetails.combatStats.drinkSlots =\r\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"drinkSlots\");\r\n } else {\r\n this.combatDetails.combatStats.foodSlots = 1;\r\n this.combatDetails.combatStats.drinkSlots = 1;\r\n }\r\n\r\n super.updateCombatDetails();\r\n }\r\n}\r\n\r\nexport default Player;\r\n","import combatTriggerDependencyDetailMap from \"./data/combatTriggerDependencyDetailMap.json\";\r\n\r\nclass Trigger {\r\n constructor(dependencyHrid, conditionHrid, comparatorHrid, value = 0) {\r\n this.dependencyHrid = dependencyHrid;\r\n this.conditionHrid = conditionHrid;\r\n this.comparatorHrid = comparatorHrid;\r\n this.value = value;\r\n }\r\n\r\n static createFromDTO(dto) {\r\n let trigger = new Trigger(dto.dependencyHrid, dto.conditionHrid, dto.comparatorHrid, dto.value);\r\n\r\n return trigger;\r\n }\r\n\r\n isActive(source, target, friendlies, enemies, currentTime) {\r\n if (combatTriggerDependencyDetailMap[this.dependencyHrid].isSingleTarget) {\r\n return this.isActiveSingleTarget(source, target, currentTime);\r\n } else {\r\n return this.isActiveMultiTarget(friendlies, enemies, currentTime);\r\n }\r\n }\r\n\r\n isActiveSingleTarget(source, target, currentTime) {\r\n let dependencyValue;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/self\":\r\n dependencyValue = this.getDependencyValue(source, currentTime);\r\n break;\r\n case \"/combat_trigger_dependencies/targeted_enemy\":\r\n if (!target) {\r\n return false;\r\n }\r\n dependencyValue = this.getDependencyValue(target, currentTime);\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n isActiveMultiTarget(friendlies, enemies, currentTime) {\r\n let dependency;\r\n switch (this.dependencyHrid) {\r\n case \"/combat_trigger_dependencies/all_allies\":\r\n dependency = friendlies;\r\n break;\r\n case \"/combat_trigger_dependencies/all_enemies\":\r\n if (!enemies) {\r\n return false;\r\n }\r\n dependency = enemies;\r\n break;\r\n default:\r\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\r\n }\r\n\r\n let dependencyValue;\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/number_of_active_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints > 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/number_of_dead_units\":\r\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints <= 0).length;\r\n break;\r\n case \"/combat_trigger_conditions/lowest_hp_percentage\":\r\n dependencyValue = dependency.reduce((prev, curr) => {\r\n let currentHpPercentage = curr.combatDetails.currentHitpoints / curr.combatDetails.maxHitpoints;\r\n return currentHpPercentage < prev ? currentHpPercentage : prev;\r\n }, 2) * 100;\r\n break;\r\n default:\r\n dependencyValue = dependency\r\n .map((unit) => this.getDependencyValue(unit, currentTime))\r\n .reduce((prev, cur) => prev + cur, 0);\r\n break;\r\n }\r\n\r\n return this.compareValue(dependencyValue);\r\n }\r\n\r\n getDependencyValue(source, currentTime) {\r\n switch (this.conditionHrid) {\r\n case \"/combat_trigger_conditions/berserk\":\r\n case \"/combat_trigger_conditions/frenzy\":\r\n case \"/combat_trigger_conditions/precision\":\r\n case \"/combat_trigger_conditions/vampirism\":\r\n case \"/combat_trigger_conditions/attack_coffee\":\r\n case \"/combat_trigger_conditions/defense_coffee\":\r\n case \"/combat_trigger_conditions/lucky_coffee\":\r\n case \"/combat_trigger_conditions/magic_coffee\":\r\n case \"/combat_trigger_conditions/melee_coffee\":\r\n case \"/combat_trigger_conditions/ranged_coffee\":\r\n case \"/combat_trigger_conditions/swiftness_coffee\":\r\n case \"/combat_trigger_conditions/wisdom_coffee\":\r\n case \"/combat_trigger_conditions/ice_spear\":\r\n case \"/combat_trigger_conditions/puncture\":\r\n case \"/combat_trigger_conditions/frost_surge\":\r\n case \"/combat_trigger_conditions/elusiveness\":\r\n case \"/combat_trigger_conditions/channeling_coffee\":\r\n case \"/combat_trigger_conditions/fierce_aura\":\r\n case \"/combat_trigger_conditions/invincible_armor\":\r\n case \"/combat_trigger_conditions/invincible_fire_resistance\":\r\n case \"/combat_trigger_conditions/invincible_nature_resistance\":\r\n case \"/combat_trigger_conditions/invincible_water_resistance\":\r\n case \"/combat_trigger_conditions/provoke\":\r\n case \"/combat_trigger_conditions/taunt\":\r\n case \"/combat_trigger_conditions/crippling_slash\":\r\n case \"/combat_trigger_conditions/mana_spring\":\r\n case \"/combat_trigger_conditions/retribution\":\r\n case \"/combat_trigger_conditions/fracturing_impact\":\r\n case \"/combat_trigger_conditions/maim\":\r\n case \"/combat_trigger_conditions/curse\":\r\n case \"/combat_trigger_conditions/weaken\":\r\n let buffHrid = \"/buff_uniques\";\r\n buffHrid += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n return source.combatBuffs[buffHrid];\r\n case \"/combat_trigger_conditions/critical_aura\":\r\n case \"/combat_trigger_conditions/critical_coffee\":\r\n case \"/combat_trigger_conditions/intelligence_coffee\":\r\n case \"/combat_trigger_conditions/stamina_coffee\":\r\n case \"/combat_trigger_conditions/elemental_affinity\":\r\n case \"/combat_trigger_conditions/fury\":\r\n case \"/combat_trigger_conditions/guardian_aura\":\r\n case \"/combat_trigger_conditions/insanity\":\r\n case \"/combat_trigger_conditions/spike_shell\":\r\n case \"/combat_trigger_conditions/toxic_pollen\":\r\n case \"/combat_trigger_conditions/invincible\":\r\n case \"/combat_trigger_conditions/mystic_aura\":\r\n case \"/combat_trigger_conditions/pestilent_shot\":\r\n case \"/combat_trigger_conditions/smoke_burst\":\r\n case \"/combat_trigger_conditions/speed_aura\":\r\n case \"/combat_trigger_conditions/toughness\":\r\n case \"/combat_trigger_conditions/enrage\":\r\n let buffPrefix = \"/buff_uniques\";\r\n buffPrefix += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\r\n let buffs = Object.keys(source.combatBuffs).filter(buff => buff.startsWith(buffPrefix));\r\n return source.combatBuffs[buffs?.[0]];\r\n case \"/combat_trigger_conditions/current_hp\":\r\n return source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/current_mp\":\r\n return source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/missing_hp\":\r\n return source.combatDetails.maxHitpoints - source.combatDetails.currentHitpoints;\r\n case \"/combat_trigger_conditions/missing_mp\":\r\n return source.combatDetails.maxManapoints - source.combatDetails.currentManapoints;\r\n case \"/combat_trigger_conditions/stun_status\":\r\n // Replicate the game's behaviour of \"stun status active\" triggers activating\r\n // immediately after the stun has worn off\r\n return source.isStunned || source.stunExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/blind_status\":\r\n return source.isBlinded || source.blindExpireTime == currentTime;\r\n case \"/combat_trigger_conditions/silence_status\":\r\n return source.isSilenced || source.silenceExpireTime == currentTime;\r\n default:\r\n throw new Error(\"Unknown conditionHrid in trigger: \" + this.conditionHrid);\r\n }\r\n }\r\n\r\n compareValue(dependencyValue) {\r\n switch (this.comparatorHrid) {\r\n case \"/combat_trigger_comparators/greater_than_equal\":\r\n return dependencyValue >= this.value;\r\n case \"/combat_trigger_comparators/less_than_equal\":\r\n return dependencyValue <= this.value;\r\n case \"/combat_trigger_comparators/is_active\":\r\n return !!dependencyValue;\r\n case \"/combat_trigger_comparators/is_inactive\":\r\n return !dependencyValue;\r\n default:\r\n throw new Error(\"Unknown comparatorHrid in trigger: \" + this.comparatorHrid);\r\n }\r\n }\r\n}\r\n\r\nexport default Trigger;\r\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","// This function allow to reference async chunks\n__webpack_require__.u = (chunkId) => {\n\t// return url for filenames based on template\n\treturn \"\" + chunkId + \".bundle.js\";\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","var scriptUrl;\nif (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + \"\";\nvar document = __webpack_require__.g.document;\nif (!scriptUrl && document) {\n\tif (document.currentScript)\n\t\tscriptUrl = document.currentScript.src;\n\tif (!scriptUrl) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tif(scripts.length) {\n\t\t\tvar i = scripts.length - 1;\n\t\t\twhile (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;\n\t\t}\n\t}\n}\n// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration\n// or pass an empty string (\"\") and set the __webpack_public_path__ variable from your code to use your own logic.\nif (!scriptUrl) throw new Error(\"Automatic publicPath is not supported in this browser\");\nscriptUrl = scriptUrl.replace(/#.*$/, \"\").replace(/\\?.*$/, \"\").replace(/\\/[^\\/]+$/, \"/\");\n__webpack_require__.p = scriptUrl;","__webpack_require__.b = document.baseURI || self.location.href;\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t\"main\": 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n// no on chunks loaded\n\n// no jsonp function","import Equipment from \"./combatsimulator/equipment.js\";\r\nimport Player from \"./combatsimulator/player.js\";\r\nimport abilityDetailMap from \"./combatsimulator/data/abilityDetailMap.json\";\r\nimport itemDetailMap from \"./combatsimulator/data/itemDetailMap.json\";\r\nimport houseRoomDetailMap from \"./combatsimulator/data/houseRoomDetailMap.json\";\r\nimport Ability from \"./combatsimulator/ability.js\";\r\nimport Consumable from \"./combatsimulator/consumable.js\";\r\nimport HouseRoom from \"./combatsimulator/houseRoom\"\r\nimport combatTriggerDependencyDetailMap from \"./combatsimulator/data/combatTriggerDependencyDetailMap.json\";\r\nimport combatTriggerConditionDetailMap from \"./combatsimulator/data/combatTriggerConditionDetailMap.json\";\r\nimport combatTriggerComparatorDetailMap from \"./combatsimulator/data/combatTriggerComparatorDetailMap.json\";\r\nimport abilitySlotsLevelRequirementList from \"./combatsimulator/data/abilitySlotsLevelRequirementList.json\";\r\nimport actionDetailMap from \"./combatsimulator/data/actionDetailMap.json\";\r\nimport combatMonsterDetailMap from \"./combatsimulator/data/combatMonsterDetailMap.json\";\r\nimport damageTypeDetailMap from \"./combatsimulator/data/damageTypeDetailMap.json\";\r\nimport combatStyleDetailMap from \"./combatsimulator/data/combatStyleDetailMap.json\";\r\nimport openableLootDropMap from \"./combatsimulator/data/openableLootDropMap.json\";\r\nimport achievementTierMap from \"./combatsimulator/data/achievementTierDetailMap.json\"\r\nimport achievementDetailMap from \"./combatsimulator/data/achievementDetailMap.json\"\r\n\r\nimport patchNote from \"../patchNote.json\";\r\n\r\nconst ONE_SECOND = 1e9;\r\nconst ONE_HOUR = 60 * 60 * ONE_SECOND;\r\n\r\nlet buttonStartSimulation = document.getElementById(\"buttonStartSimulation\");\r\nlet buttonStopSimulation = document.getElementById(\"buttonStopSimulation\");\r\nlet progressbar = document.getElementById(\"simulationProgressBar\");\r\nlet simStartTime = 0;\r\n\r\nlet worker = new Worker(new URL(\"worker.js\", import.meta.url));\r\nlet multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\nlet workerPool = [];\r\n\r\n\r\nlet player = new Player();\r\nlet selectedPlayers = [];\r\nlet food = [null, null, null];\r\nlet drinks = [null, null, null];\r\nlet abilities = [null, null, null, null];\r\nlet triggerMap = {};\r\nlet modalTriggers = [];\r\nlet currentSimResults = {};\r\n\r\nlet currentPlayerTabId = '1';\r\nlet playerDataMap = {\r\n \"1\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"2\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"3\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"4\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\r\n \"5\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\"\r\n};\r\nwindow.revenue = 0;\r\nwindow.noRngRevenue = 0;\r\nwindow.expenses = 0;\r\nwindow.profit = 0;\r\nwindow.noRngProfit = 0;\r\n\r\n// #region Worker\r\n\r\nfunction onWorkerMessage(event) {\r\n switch (event.data.type) {\r\n case \"simulation_result\":\r\n progressbar.style.width = \"100%\";\r\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n //console.log(\"SIM RESULTS: \", event.data.simResult);\r\n showSimulationResult(event.data.simResult);\r\n updateContent();\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n document.getElementById('buttonShowAllSimData').style.display = 'none';\r\n break;\r\n case \"simulation_progress\":\r\n let progress = Math.floor(100 * event.data.progress);\r\n progressbar.style.width = progress + \"%\";\r\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n break;\r\n case \"simulation_error\":\r\n showErrorModal(event.data.error.toString());\r\n break;\r\n }\r\n};\r\n\r\nfunction onMultiWorkerMessage(event) {\r\n switch (event.data.type) {\r\n case \"simulation_result_allZones\":\r\n progressbar.style.width = \"100%\";\r\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n showAllSimulationResults(event.data.simResults);\r\n updateContent();\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n document.getElementById('buttonShowAllSimData').style.display = 'block';\r\n break;\r\n case \"simulation_progress\":\r\n let progress = Math.floor(100 * event.data.progress);\r\n progressbar.style.width = progress + \"%\";\r\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\r\n break;\r\n case \"simulation_error\":\r\n showErrorModal(event.data.error.toString());\r\n break;\r\n }\r\n};\r\n\r\n// #endregion\r\n\r\n// #region Equipment\r\n\r\nfunction initEquipmentSection() {\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n initEquipmentSelect(type);\r\n initEnhancementLevelInput(type);\r\n });\r\n}\r\n\r\nfunction initEquipmentSelect(equipmentType) {\r\n let selectId = \"selectEquipment_\";\r\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\r\n selectId += \"weapon\";\r\n } else {\r\n selectId += equipmentType;\r\n }\r\n let selectElement = document.getElementById(selectId);\r\n\r\n let gameEquipment = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/equipment\")\r\n .filter((item) => item.equipmentDetail.type == \"/equipment_types/\" + equipmentType)\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const equipment of Object.values(gameEquipment)) {\r\n let opt = new Option(equipment.name, equipment.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + equipment.hrid);\r\n selectElement.add(opt);\r\n }\r\n\r\n selectElement.addEventListener(\"change\", (event) => {\r\n equipmentSelectHandler(event, equipmentType);\r\n });\r\n}\r\n\r\nfunction initHouseRoomsModal() {\r\n let houseRoomsList = document.getElementById(\"houseRoomsList\");\r\n let newChildren = [];\r\n let houseRooms = Object.values(houseRoomDetailMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n player.houseRooms = {};\r\n\r\n for (const room of Object.values(houseRooms)) {\r\n player.houseRooms[room.hrid] = 0;\r\n\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let nameCol = createElement(\"div\", \"col-md-4 offset-md-3 align-self-center\", room.name);\r\n nameCol.setAttribute(\"data-i18n\", \"houseRoomNames.\" + room.hrid);\r\n row.appendChild(nameCol);\r\n\r\n let levelCol = createElement(\"div\", \"col-md-2\");\r\n let levelInput = createHouseInput(room.hrid);\r\n\r\n levelInput.addEventListener(\"input\", function (e) {\r\n let inputValue = e.target.value;\r\n const hrid = e.target.dataset.houseHrid;\r\n player.houseRooms[hrid] = parseInt(inputValue);\r\n });\r\n\r\n levelCol.appendChild(levelInput);\r\n row.appendChild(levelCol);\r\n\r\n newChildren.push(row);\r\n }\r\n\r\n houseRoomsList.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction createHouseInput(hrid) {\r\n let levelInput = document.createElement(\"input\");\r\n levelInput.className = \"form-control\";\r\n levelInput.type = \"number\";\r\n levelInput.placeholder = 0;\r\n levelInput.min = 0;\r\n levelInput.max = 8;\r\n levelInput.step = 1;\r\n levelInput.dataset.houseHrid = hrid;\r\n\r\n return levelInput;\r\n}\r\n\r\nfunction refreshAchievementStatics() {\r\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n for(const tier of Object.values(tierMap)) {\r\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\r\n const done = Array.from(checks).filter(cb => cb.checked).length;\r\n const total = checks.length;\r\n\r\n const stat = document.getElementById(`AchTier${tier.sortIndex}Statics`);\r\n stat.innerText = `(${done}/${total})`;\r\n if (done == total) {\r\n // set to green\r\n stat.classList.remove(\"text-secondary\");\r\n stat.classList.add(\"text-success\");\r\n } else {\r\n // set to secondary\r\n stat.classList.remove(\"text-success\");\r\n stat.classList.add(\"text-secondary\");\r\n }\r\n }\r\n}\r\n\r\nfunction initAchievementsModal(){\r\n let achievementsList = document.getElementById(\"achievementsList\");\r\n let newChildren = [];\r\n player.achievements = {};\r\n\r\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\r\n for(const tier of Object.values(tierMap)) {\r\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid).sort((a, b) => a.sortIndex - b.sortIndex);\r\n let detailMapCount = detailMap.length;\r\n if (detailMapCount <= 0) continue;\r\n\r\n let card = createElement(\"div\", \"card\");\r\n let cardHeader = createElement(\"div\", \"card-header d-flex align-items-center\");\r\n\r\n let cardTitle = createElement(\"a\", \"btn\", tier.name);\r\n cardTitle.setAttribute(\"data-bs-toggle\",\"collapse\");\r\n cardTitle.setAttribute(\"href\", `#AchTier${tier.sortIndex}`);\r\n cardTitle.setAttribute(\"data-i18n\", \"achievementTierNames.\"+tier.hrid);\r\n cardHeader.appendChild(cardTitle);\r\n\r\n let bufDesc = createElement(\"div\", \"small text-secondary\");\r\n let buffName = createElement(\"i\", \"\");\r\n buffName.setAttribute(\"data-i18n\", \"buffTypeNames.\"+tier[\"buff\"].typeHrid);\r\n bufDesc.appendChild(buffName);\r\n let buffValue = createElement(\"i\", \"\");\r\n buffValue.innerText = \":+\" + parseFloat(tier[\"buff\"].ratioBoost==0?tier[\"buff\"].flatBoost:tier[\"buff\"].ratioBoost)*100 + \"%\";\r\n bufDesc.appendChild(buffValue);\r\n cardHeader.appendChild(bufDesc);\r\n\r\n let cardStatics = createElement(\"div\", \"ms-auto btn\", `(0/${detailMapCount})`);\r\n cardStatics.id = `AchTier${tier.sortIndex}Statics`;\r\n cardStatics.dataset.checked = \"true\";\r\n cardStatics.addEventListener(\"click\", function (e) {\r\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\r\n for (const check of checks) {\r\n check.checked = cardStatics.dataset.checked == \"true\";\r\n const hrid = check.dataset.achievementHrid;\r\n player.achievements[hrid] = check.checked;\r\n }\r\n cardStatics.dataset.checked = cardStatics.dataset.checked == \"true\" ? \"false\" : \"true\";\r\n refreshAchievementStatics();\r\n });\r\n cardHeader.appendChild(cardStatics);\r\n\r\n card.appendChild(cardHeader);\r\n\r\n let cardMain = createElement(\"div\", \"collapse\");\r\n cardMain.id = `AchTier${tier.sortIndex}`;\r\n let cardBody = createElement(\"div\", \"card-body\");\r\n\r\n for (const detail of Object.values(detailMap)) {\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let formCheck = createElement(\"div\", \"form-check\");\r\n let input = createElement(\"input\", \"form-check-input\");\r\n input.setAttribute(\"type\", \"checkbox\");\r\n input.setAttribute(\"data-tier\", tier.sortIndex);\r\n input.id = `AchDetail${detail.sortIndex}`;\r\n input.dataset.achievementHrid = detail.hrid;\r\n input.addEventListener(\"change\", function (e) {\r\n const hrid = e.target.dataset.achievementHrid;\r\n player.achievements[hrid] = e.target.checked;\r\n\r\n refreshAchievementStatics();\r\n });\r\n formCheck.appendChild(input);\r\n\r\n let name = createElement(\"label\", \"form-check-label\", detail.name);\r\n name.setAttribute(\"data-i18n\", \"achievementNames.\" + detail.hrid);\r\n name.setAttribute(\"for\", `AchDetail${detail.sortIndex}`);\r\n formCheck.appendChild(name);\r\n row.appendChild(formCheck);\r\n cardBody.appendChild(row);\r\n }\r\n cardMain.appendChild(cardBody);\r\n card.appendChild(cardMain);\r\n\r\n newChildren.push(card);\r\n }\r\n\r\n achievementsList.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction initEnhancementLevelInput(equipmentType) {\r\n let inputId = \"inputEquipmentEnhancementLevel_\";\r\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\r\n inputId += \"weapon\";\r\n } else {\r\n inputId += equipmentType;\r\n }\r\n\r\n let inputElement = document.getElementById(inputId);\r\n inputElement.value = 0;\r\n inputElement.addEventListener(\"change\", enhancementLevelInputHandler);\r\n}\r\n\r\nfunction equipmentSelectHandler(event, type) {\r\n let equipmentType = \"/equipment_types/\" + type;\r\n\r\n if (!event.target.value) {\r\n updateEquipmentState();\r\n updateUI();\r\n return;\r\n }\r\n\r\n let gameItem = itemDetailMap[event.target.value];\r\n\r\n // Weapon select has two handlers because of mainhand and twohand weapons. Ignore the handler with the wrong type\r\n if (gameItem.equipmentDetail.type != equipmentType) {\r\n return;\r\n }\r\n\r\n if (type == \"two_hand\") {\r\n document.getElementById(\"selectEquipment_off_hand\").value = \"\";\r\n document.getElementById(\"inputEquipmentEnhancementLevel_off_hand\").value = 0;\r\n }\r\n if (type == \"off_hand\" && player.equipment[\"/equipment_types/two_hand\"]) {\r\n document.getElementById(\"selectEquipment_weapon\").value = \"\";\r\n document.getElementById(\"inputEquipmentEnhancementLevel_weapon\").value = 0;\r\n }\r\n\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\nfunction enhancementLevelInputHandler() {\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\nfunction updateEquipmentState() {\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentType = \"/equipment_types/\" + type;\r\n let selectType = type;\r\n if (type == \"main_hand\" || type == \"two_hand\") {\r\n selectType = \"weapon\";\r\n }\r\n\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + selectType);\r\n let equipmentHrid = equipmentSelect.value;\r\n\r\n if (!equipmentHrid) {\r\n player.equipment[equipmentType] = null;\r\n return;\r\n }\r\n\r\n let gameItem = itemDetailMap[equipmentHrid];\r\n\r\n // Clear old weapon if a weapon of a different type is equipped\r\n if (gameItem.equipmentDetail.type != equipmentType) {\r\n player.equipment[equipmentType] = null;\r\n return;\r\n }\r\n\r\n let enhancementLevel = Number(document.getElementById(\"inputEquipmentEnhancementLevel_\" + selectType).value);\r\n player.equipment[equipmentType] = new Equipment(gameItem.hrid, enhancementLevel);\r\n });\r\n}\r\n\r\ndocument.getElementById(\"selectEquipment_set\").onchange = changeEquipmentSetListener;\r\n\r\nfunction changeEquipmentSetListener() {\r\n let value = this.value\r\n let optgroupType = this.options[this.selectedIndex].parentNode.label;\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\"].forEach((type) => {\r\n let selectType = type;\r\n\r\n let currentEquipment = document.getElementById(\"selectEquipment_\" + selectType);\r\n if (type === \"feet\") {\r\n type = \"_boots\";\r\n }\r\n if (type === \"hands\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_bracers\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_gloves\";\r\n } else {\r\n type = \"_gauntlets\";\r\n }\r\n }\r\n if (type === \"head\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_hood\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_hat\";\r\n } else {\r\n type = \"_helmet\";\r\n }\r\n }\r\n if (type === \"legs\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_chaps\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_robe_bottoms\";\r\n } else {\r\n type = \"_plate_legs\";\r\n }\r\n }\r\n if (type === \"body\") {\r\n if (optgroupType === \"RANGED\") {\r\n type = \"_tunic\";\r\n } else if (optgroupType === \"MAGIC\") {\r\n type = \"_robe_top\";\r\n } else {\r\n type = \"_plate_body\";\r\n }\r\n }\r\n currentEquipment.value = \"/items/\" + value.toLowerCase() + type;\r\n });\r\n updateEquipmentState();\r\n updateUI();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Combat Stats\r\n\r\nfunction updateCombatStatsUI() {\r\n player.updateCombatDetails();\r\n\r\n let combatStyleElement = document.getElementById(\"combatStat_combatStyleHrid\");\r\n let combatStyle = player.combatDetails.combatStats.combatStyleHrid;\r\n combatStyleElement.setAttribute(\"data-i18n\", \"combatStyleNames.\" + combatStyle);\r\n combatStyleElement.innerHTML = combatStyleDetailMap[combatStyle].name;\r\n\r\n let damageTypeElement = document.getElementById(\"combatStat_damageType\");\r\n let damageType = damageTypeDetailMap[player.combatDetails.combatStats.damageType];\r\n damageTypeElement.setAttribute(\"data-i18n\", \"damageTypeNames.\" + damageType.hrid);\r\n damageTypeElement.innerHTML = damageType.name;\r\n\r\n let attackIntervalElement = document.getElementById(\"combatStat_attackInterval\");\r\n attackIntervalElement.innerHTML = (player.combatDetails.combatStats.attackInterval / 1e9).toLocaleString() + \"s\";\r\n\r\n let primaryTrainingElement = document.getElementById(\"combatStat_primaryTraining\");\r\n let primaryTraining = player.combatDetails.combatStats.primaryTraining;\r\n primaryTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + primaryTraining);\r\n primaryTrainingElement.innerHTML = primaryTraining;\r\n\r\n let focusTrainingElement = document.getElementById(\"combatStat_focusTraining\");\r\n let focusTraining = player.combatDetails.combatStats.focusTraining;\r\n if (focusTraining) {\r\n focusTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + focusTraining);\r\n } else {\r\n focusTrainingElement.setAttribute(\"data-i18n\", \"characterSelectPage.slots.empty\");\r\n }\r\n focusTrainingElement.innerHTML = focusTraining;\r\n\r\n [\r\n \"maxHitpoints\",\r\n \"maxManapoints\",\r\n \"stabAccuracyRating\",\r\n \"stabMaxDamage\",\r\n \"slashAccuracyRating\",\r\n \"slashMaxDamage\",\r\n \"smashAccuracyRating\",\r\n \"smashMaxDamage\",\r\n \"rangedAccuracyRating\",\r\n \"rangedMaxDamage\",\r\n \"magicAccuracyRating\",\r\n \"magicMaxDamage\",\r\n \"defensiveMaxDamage\",\r\n \"stabEvasionRating\",\r\n \"slashEvasionRating\",\r\n \"smashEvasionRating\",\r\n \"rangedEvasionRating\",\r\n \"magicEvasionRating\",\r\n \"totalArmor\",\r\n \"totalWaterResistance\",\r\n \"totalNatureResistance\",\r\n \"totalFireResistance\",\r\n \"totalThreat\"\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n element.innerHTML = Math.floor(player.combatDetails[stat]);\r\n });\r\n\r\n [\r\n \"abilityHaste\",\r\n \"tenacity\"\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n element.innerHTML = Math.floor(player.combatDetails.combatStats[stat]);\r\n });\r\n\r\n [\r\n \"physicalAmplify\",\r\n \"waterAmplify\",\r\n \"natureAmplify\",\r\n \"fireAmplify\",\r\n \"healingAmplify\",\r\n \"lifeSteal\",\r\n \"hpRegenPer10\",\r\n \"mpRegenPer10\",\r\n \"physicalThorns\",\r\n \"elementalThorns\",\r\n \"criticalRate\",\r\n \"criticalDamage\",\r\n \"combatExperience\",\r\n \"taskDamage\",\r\n \"armorPenetration\",\r\n \"waterPenetration\",\r\n \"naturePenetration\",\r\n \"firePenetration\",\r\n \"manaLeech\",\r\n \"castSpeed\",\r\n \"parry\",\r\n \"mayhem\",\r\n \"pierce\",\r\n \"curse\",\r\n \"fury\",\r\n \"weaken\",\r\n \"ripple\",\r\n \"bloom\",\r\n \"blaze\",\r\n \"attackSpeed\",\r\n \"autoAttackDamage\",\r\n \"abilityDamage\",\r\n \"drinkConcentration\",\r\n \"foodHaste\",\r\n \"staminaExperience\",\r\n \"intelligenceExperience\",\r\n \"attackExperience\",\r\n \"defenseExperience\",\r\n \"meleeExperience\",\r\n \"rangedExperience\",\r\n \"magicExperience\"\r\n\r\n ].forEach((stat) => {\r\n let element = document.getElementById(\"combatStat_\" + stat);\r\n let value = (100 * player.combatDetails.combatStats[stat]).toLocaleString([], {\r\n minimumFractionDigits: 0,\r\n maximumFractionDigits: 4,\r\n });\r\n element.innerHTML = value + \"%\";\r\n });\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Level\r\n\r\nfunction initLevelSection() {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n levelInput.value = 1;\r\n levelInput.addEventListener(\"change\", levelInputHandler);\r\n });\r\n}\r\n\r\nfunction levelInputHandler() {\r\n updateLevels();\r\n updateUI();\r\n}\r\n\r\nfunction updateLevels() {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n player[skill + \"Level\"] = Number(levelInput.value);\r\n });\r\n updateCombatLevel();\r\n}\r\n\r\nfunction calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel) {\r\n return Math.floor(\r\n 0.1 * (staminaLevel + intelligenceLevel + attackLevel + defenseLevel + Math.max(meleeLevel, rangedLevel, magicLevel))\r\n + 0.5 * Math.max(attackLevel, defenseLevel, meleeLevel, rangedLevel, magicLevel)\r\n );\r\n}\r\n\r\n\r\nfunction updateCombatLevel() {\r\n let staminaLevel = player[\"staminaLevel\"];\r\n let intelligenceLevel = player[\"intelligenceLevel\"];\r\n let defenseLevel = player[\"defenseLevel\"];\r\n let attackLevel = player[\"attackLevel\"];\r\n let meleeLevel = player[\"meleeLevel\"];\r\n let rangedLevel = player[\"rangedLevel\"];\r\n let magicLevel = player[\"magicLevel\"];\r\n\r\n let levelInput = document.getElementById(\"inputLevel_combat\");\r\n levelInput.value = calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel);;\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Food\r\n\r\nfunction initFoodSection() {\r\n for (let i = 0; i < 3; i++) {\r\n let element = document.getElementById(\"selectFood_\" + i);\r\n\r\n let gameFoods = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/food\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const food of Object.values(gameFoods)) {\r\n let opt = new Option(food.name, food.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + food.hrid);\r\n element.add(opt);\r\n }\r\n\r\n element.addEventListener(\"change\", foodSelectHandler);\r\n }\r\n}\r\n\r\nfunction foodSelectHandler() {\r\n updateFoodState();\r\n updateUI();\r\n}\r\n\r\nfunction updateFoodState() {\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n food[i] = foodSelect.value;\r\n if (food[i] && !triggerMap[food[i]]) {\r\n let gameItem = itemDetailMap[food[i]];\r\n triggerMap[food[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateFoodUI() {\r\n for (let i = 0; i < 3; i++) {\r\n let selectElement = document.getElementById(\"selectFood_\" + i);\r\n let triggerButton = document.getElementById(\"buttonFoodTrigger_\" + i);\r\n\r\n selectElement.disabled = i >= player.combatDetails.combatStats.foodSlots;\r\n triggerButton.disabled = i >= player.combatDetails.combatStats.foodSlots || !food[i];\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Drinks\r\n\r\nfunction initDrinksSection() {\r\n for (let i = 0; i < 3; i++) {\r\n let element = document.getElementById(\"selectDrink_\" + i);\r\n\r\n let gameDrinks = Object.values(itemDetailMap)\r\n .filter((item) => item.categoryHrid == \"/item_categories/drink\")\r\n .filter((item) => item.consumableDetail.usableInActionTypeMap[\"/action_types/combat\"])\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const drink of Object.values(gameDrinks)) {\r\n let opt = new Option(drink.name, drink.hrid);\r\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + drink.hrid);\r\n element.add(opt);\r\n }\r\n\r\n element.addEventListener(\"change\", drinkSelectHandler);\r\n }\r\n}\r\n\r\nfunction drinkSelectHandler() {\r\n updateDrinksState();\r\n updateDrinksUI();\r\n}\r\n\r\nfunction updateDrinksState() {\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n drinks[i] = drinkSelect.value;\r\n if (drinks[i] && !triggerMap[drinks[i]]) {\r\n let gameItem = itemDetailMap[drinks[i]];\r\n triggerMap[drinks[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateDrinksUI() {\r\n for (let i = 0; i < 3; i++) {\r\n let selectElement = document.getElementById(\"selectDrink_\" + i);\r\n let triggerButton = document.getElementById(\"buttonDrinkTrigger_\" + i);\r\n\r\n selectElement.disabled = i >= player.combatDetails.combatStats.drinkSlots;\r\n triggerButton.disabled = i >= player.combatDetails.combatStats.drinkSlots || !drinks[i];\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Abilities\r\n\r\nfunction initAbilitiesSection() {\r\n for (let i = 0; i < 5; i++) {\r\n let selectElement = document.getElementById(\"selectAbility_\" + i);\r\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\r\n\r\n inputElement.value = 1;\r\n\r\n let gameAbilities;\r\n if (i == 0) {\r\n gameAbilities = Object.values(abilityDetailMap).filter(x => x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\r\n } else {\r\n gameAbilities = Object.values(abilityDetailMap).filter(x => !x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\r\n }\r\n\r\n\r\n for (const ability of Object.values(gameAbilities)) {\r\n let opt = new Option(ability.name, ability.hrid);\r\n opt.setAttribute(\"data-i18n\", \"abilityNames.\" + ability.hrid);\r\n selectElement.add(opt);\r\n }\r\n\r\n selectElement.addEventListener(\"change\", abilitySelectHandler);\r\n }\r\n}\r\n\r\nfunction abilitySelectHandler() {\r\n updateAbilityState();\r\n updateAbilityUI();\r\n}\r\n\r\nfunction updateAbilityState() {\r\n for (let i = 0; i < 5; i++) {\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\r\n abilities[i] = abilitySelect.value;\r\n if (abilities[i] && !triggerMap[abilities[i]]) {\r\n let gameAbility = abilityDetailMap[abilities[i]];\r\n triggerMap[abilities[i]] = structuredClone(gameAbility.defaultCombatTriggers);\r\n }\r\n }\r\n}\r\n\r\nfunction updateAbilityUI() {\r\n for (let i = 0; i < 5; i++) {\r\n let selectElement = document.getElementById(\"selectAbility_\" + i);\r\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let triggerButton = document.getElementById(\"buttonAbilityTrigger_\" + i);\r\n\r\n selectElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\r\n inputElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\r\n triggerButton.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1] || !abilities[i];\r\n let moveUpButton = document.getElementById(\"selectAbilityMoveUp_\" + i);\r\n moveUpButton.onclick = () => swapAbilityOrder(i, -1);\r\n }\r\n}\r\n\r\nfunction swapAbilityOrder(abilityIndex, step) {\r\n const swapIndex = abilityIndex + step;\r\n if (swapIndex < 0 || swapIndex > 4) {\r\n return;\r\n }\r\n\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilityIndex);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilityIndex);\r\n\r\n const tempAbility = abilities[abilityIndex];\r\n abilities[abilityIndex] = abilities[swapIndex];\r\n abilities[swapIndex] = tempAbility;\r\n\r\n const tempLevel = abilityLevelInput.value;\r\n abilityLevelInput.value = document.getElementById(\"inputAbilityLevel_\" + swapIndex).value;\r\n document.getElementById(\"inputAbilityLevel_\" + swapIndex).value = tempLevel;\r\n\r\n abilitySelect.value = document.getElementById(\"selectAbility_\" + (swapIndex)).value;\r\n document.getElementById(\"selectAbility_\" + swapIndex).value = abilities[swapIndex];\r\n\r\n updateAbilityState();\r\n updateAbilityUI();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Trigger\r\n\r\nfunction initTriggerModal() {\r\n let modal = document.getElementById(\"triggerModal\");\r\n modal.addEventListener(\"show.bs.modal\", (event) => triggerModalShownHandler(event));\r\n\r\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\r\n triggerSaveButton.addEventListener(\"click\", (event) => triggerModalSaveHandler(event));\r\n\r\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\r\n triggerAddButton.addEventListener(\"click\", (event) => triggerAddButtonHandler(event));\r\n\r\n let triggerDefaultButton = document.getElementById(\"buttonDefaultTrigger\");\r\n triggerDefaultButton.addEventListener(\"click\", (event) => triggerDefaultButtonHandler(event));\r\n\r\n for (let i = 0; i < 4; i++) {\r\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\r\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\r\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\r\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\r\n let triggerRemoveButton = document.getElementById(\"buttonRemoveTrigger_\" + i);\r\n\r\n triggerDependencySelect.addEventListener(\"change\", (event) => triggerDependencySelectHandler(event, i));\r\n triggerConditionSelect.addEventListener(\"change\", (event) => triggerConditionSelectHandler(event, i));\r\n triggerComparatorSelect.addEventListener(\"change\", (event) => triggerComparatorSelectHander(event, i));\r\n triggerValueInput.addEventListener(\"change\", (event) => triggerValueInputHandler(event, i));\r\n triggerRemoveButton.addEventListener(\"click\", (event) => triggerRemoveButtonHandler(event, i));\r\n }\r\n}\r\n\r\nfunction triggerModalShownHandler(event) {\r\n let triggerButton = event.relatedTarget;\r\n\r\n let triggerType = triggerButton.getAttribute(\"data-bs-triggertype\");\r\n let triggerIndex = Number(triggerButton.getAttribute(\"data-bs-triggerindex\"));\r\n\r\n let triggerTarget;\r\n switch (triggerType) {\r\n case \"food\":\r\n triggerTarget = food[triggerIndex];\r\n break;\r\n case \"drink\":\r\n triggerTarget = drinks[triggerIndex];\r\n break;\r\n case \"ability\":\r\n triggerTarget = abilities[triggerIndex];\r\n break;\r\n }\r\n\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n triggerTargetnput.value = triggerTarget;\r\n modalTriggers = triggerMap[triggerTarget];\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerModalSaveHandler(event) {\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n let triggerTarget = triggerTargetnput.value;\r\n\r\n triggerMap[triggerTarget] = modalTriggers;\r\n}\r\n\r\nfunction triggerDependencySelectHandler(event, index) {\r\n modalTriggers[index].dependencyHrid = event.target.value;\r\n modalTriggers[index].conditionHrid = \"\";\r\n modalTriggers[index].comparatorHrid = \"\";\r\n modalTriggers[index].value = 0;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerConditionSelectHandler(event, index) {\r\n modalTriggers[index].conditionHrid = event.target.value;\r\n modalTriggers[index].comparatorHrid = \"\";\r\n modalTriggers[index].value = 0;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerComparatorSelectHander(event, index) {\r\n modalTriggers[index].comparatorHrid = event.target.value;\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerValueInputHandler(event, index) {\r\n modalTriggers[index].value = Number(event.target.value);\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerRemoveButtonHandler(event, index) {\r\n modalTriggers.splice(index, 1);\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerAddButtonHandler(event) {\r\n if (modalTriggers.length == 4) {\r\n return;\r\n }\r\n\r\n modalTriggers.push({\r\n dependencyHrid: \"\",\r\n conditionHrid: \"\",\r\n comparatorHrid: \"\",\r\n value: 0,\r\n });\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction triggerDefaultButtonHandler(event) {\r\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\r\n let triggerTarget = triggerTargetnput.value;\r\n\r\n if (triggerTarget.startsWith(\"/items/\")) {\r\n modalTriggers = structuredClone(itemDetailMap[triggerTarget].consumableDetail.defaultCombatTriggers);\r\n } else {\r\n modalTriggers = structuredClone(abilityDetailMap[triggerTarget].defaultCombatTriggers);\r\n }\r\n\r\n updateTriggerModal();\r\n}\r\n\r\nfunction updateTriggerModal() {\r\n let triggerStartTextElement = document.getElementById(\"triggerStartText\");\r\n if (modalTriggers.length == 0) {\r\n triggerStartTextElement.innerHTML = \"Activate as soon as it's off cooldown\";\r\n } else {\r\n triggerStartTextElement.innerHTML = \"Activate when:\";\r\n }\r\n\r\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\r\n triggerAddButton.disabled = modalTriggers.length == 4;\r\n\r\n let triggersValid = true;\r\n\r\n for (let i = 0; i < 4; i++) {\r\n let triggerElement = document.getElementById(\"modalTrigger_\" + i);\r\n\r\n if (!modalTriggers[i]) {\r\n hideElement(triggerElement);\r\n continue;\r\n }\r\n\r\n showElement(triggerElement);\r\n\r\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\r\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\r\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\r\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\r\n\r\n showElement(triggerDependencySelect);\r\n fillTriggerDependencySelect(triggerDependencySelect);\r\n\r\n if (modalTriggers[i].dependencyHrid == \"\") {\r\n hideElement(triggerConditionSelect);\r\n hideElement(triggerComparatorSelect);\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerDependencySelect.value = modalTriggers[i].dependencyHrid;\r\n showElement(triggerConditionSelect);\r\n fillTriggerConditionSelect(triggerConditionSelect, modalTriggers[i].dependencyHrid);\r\n\r\n if (modalTriggers[i].conditionHrid == \"\") {\r\n hideElement(triggerComparatorSelect);\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerConditionSelect.value = modalTriggers[i].conditionHrid;\r\n showElement(triggerComparatorSelect);\r\n fillTriggerComparatorSelect(triggerComparatorSelect, modalTriggers[i].conditionHrid);\r\n\r\n if (modalTriggers[i].comparatorHrid == \"\") {\r\n hideElement(triggerValueInput);\r\n triggersValid = false;\r\n continue;\r\n }\r\n\r\n triggerComparatorSelect.value = modalTriggers[i].comparatorHrid;\r\n\r\n if (combatTriggerComparatorDetailMap[modalTriggers[i].comparatorHrid].allowValue) {\r\n showElement(triggerValueInput);\r\n triggerValueInput.value = modalTriggers[i].value;\r\n } else {\r\n hideElement(triggerValueInput);\r\n }\r\n }\r\n\r\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\r\n triggerSaveButton.disabled = !triggersValid;\r\n\r\n updateContent();\r\n}\r\n\r\nfunction fillTriggerDependencySelect(element) {\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const dependency of Object.values(combatTriggerDependencyDetailMap).sort(\r\n (a, b) => a.sortIndex - b.sortIndex\r\n )) {\r\n let opt = new Option(dependency.name, dependency.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerDependencyNames.\" + dependency.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction fillTriggerConditionSelect(element, dependencyHrid) {\r\n let dependency = combatTriggerDependencyDetailMap[dependencyHrid];\r\n\r\n let conditions;\r\n if (dependency.isSingleTarget) {\r\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isSingleTarget);\r\n } else {\r\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isMultiTarget);\r\n }\r\n\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const condition of Object.values(conditions).sort((a, b) => a.sortIndex - b.sortIndex)) {\r\n let opt = new Option(condition.name, condition.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerConditionNames.\" + condition.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction fillTriggerComparatorSelect(element, conditionHrid) {\r\n let condition = combatTriggerConditionDetailMap[conditionHrid];\r\n\r\n let comparators = condition.allowedComparatorHrids.map((hrid) => combatTriggerComparatorDetailMap[hrid]);\r\n\r\n element.length = 0;\r\n element.add(new Option(\"\", \"\"));\r\n\r\n for (const comparator of Object.values(comparators).sort((a, b) => a.sortIndex - b.sortIndex)) {\r\n let opt = new Option(comparator.name, comparator.hrid);\r\n opt.setAttribute(\"data-i18n\", \"combatTriggerComparatorNames.\" + comparator.hrid);\r\n element.add(opt);\r\n }\r\n}\r\n\r\nfunction hideElement(element) {\r\n element.classList.remove(\"d-flex\");\r\n element.classList.add(\"d-none\");\r\n}\r\n\r\nfunction showElement(element) {\r\n element.classList.remove(\"d-none\");\r\n element.classList.add(\"d-flex\");\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Zones\r\n\r\nfunction initZones() {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n\r\n // TOOD dungeon wave spawns\r\n let gameZones = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const zone of Object.values(gameZones)) {\r\n let opt = new Option(zone.name, zone.hrid);\r\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + zone.hrid);\r\n zoneSelect.add(opt);\r\n }\r\n\r\n\r\n let zoneCheckBox = document.getElementById(\"zoneCheckBox\");\r\n let checkAllZonesToggle = document.getElementById('checkAllZones');\r\n\r\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\r\n simAllZonesToggle.addEventListener(\"change\", (event) => {\r\n if (simAllZonesToggle.checked) {\r\n zoneCheckBox.classList.remove(\"d-none\");\r\n zoneCheckBox.querySelectorAll(\".zone-checkbox\").forEach(checkbox => checkbox.checked = true);\r\n checkAllZonesToggle.checked = true;\r\n } else {\r\n zoneCheckBox.classList.add(\"d-none\");\r\n }\r\n });\r\n\r\n let zoneHrids = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .flat();\r\n\r\n for (const zoneHrid of zoneHrids) {\r\n const newZone = document.createElement('div');\r\n newZone.classList.add('form-check');\r\n newZone.innerHTML = `\r\n \r\n \r\n `;\r\n zoneCheckBox.append(newZone);\r\n }\r\n\r\n let checkZoneToggles = document.querySelectorAll('.zone-checkbox');\r\n checkAllZonesToggle.addEventListener('change', () => {\r\n checkZoneToggles.forEach(cb => cb.checked = checkAllZonesToggle.checked);\r\n });\r\n\r\n checkZoneToggles.forEach(cb =>\r\n cb.addEventListener('change', () => {\r\n checkAllZonesToggle.checked = [...checkZoneToggles].every(x => x.checked);\r\n })\r\n );\r\n\r\n\r\n let soloCheckBox = document.getElementById(\"soloCheckBox\");\r\n let checkAllSolosToggle = document.getElementById('checkAllSolos');\r\n\r\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\r\n simAllSoloToggle.addEventListener(\"change\", (event) => {\r\n if (simAllSoloToggle.checked) {\r\n soloCheckBox.classList.remove(\"d-none\");\r\n soloCheckBox.querySelectorAll(\".solo-checkbox\").forEach(checkbox => checkbox.checked = true);\r\n checkAllSolosToggle.checked = true;\r\n } else {\r\n soloCheckBox.classList.add(\"d-none\");\r\n }\r\n });\r\n\r\n let soloHrids = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount == 1)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .flat();\r\n\r\n for (const zoneHrid of soloHrids) {\r\n const newZone = document.createElement('div');\r\n newZone.classList.add('form-check');\r\n newZone.innerHTML = `\r\n \r\n \r\n `;\r\n soloCheckBox.append(newZone);\r\n }\r\n\r\n let checkSoloToggles = document.querySelectorAll('.solo-checkbox');\r\n checkAllSolosToggle.addEventListener('change', () => {\r\n checkSoloToggles.forEach(cb => cb.checked = checkAllSolosToggle.checked);\r\n });\r\n\r\n checkSoloToggles.forEach(cb =>\r\n cb.addEventListener('change', () => {\r\n checkAllSolosToggle.checked = [...checkSoloToggles].every(x => x.checked);\r\n })\r\n );\r\n}\r\n\r\nfunction initDungeons() {\r\n let dungeonSelect = document.getElementById(\"selectDungeon\");\r\n\r\n let gameDungeons = Object.values(actionDetailMap)\r\n .filter((action) => action.type == \"/action_types/combat\" && action.category == \"/action_categories/combat/dungeons\")\r\n .sort((a, b) => a.sortIndex - b.sortIndex);\r\n\r\n for (const dungeon of Object.values(gameDungeons)) {\r\n let opt = new Option(dungeon.name, dungeon.hrid);\r\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + dungeon.hrid);\r\n dungeonSelect.add(opt);\r\n }\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Simulation Result\r\n\r\nfunction createDamageDoneAccordion(enemyIndex) {\r\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageDoneAccordionEnemy${enemyIndex}`);\r\n\r\n const colDiv = createElement('div', 'col');\r\n const accordionMainDiv = createElement('div', 'accordion');\r\n const accordionItemDiv = createElement('div', 'accordion-item');\r\n\r\n const headerH2 = createElement('h2', 'accordion-header');\r\n const button = createElement('button', 'accordion-button collapsed',\r\n `Damage Done (Enemy ${enemyIndex})`,\r\n `buttonSimulationResultDamageDoneAccordionEnemy${enemyIndex}`\r\n );\r\n button.setAttribute('type', 'button');\r\n button.setAttribute('data-bs-toggle', 'collapse');\r\n button.setAttribute('data-bs-target', `#collapseDamageDone${enemyIndex}`);\r\n button.style.padding = '0.5em';\r\n\r\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageDone${enemyIndex}`);\r\n const accordionBodyDiv = createElement('div', 'accordion-body');\r\n\r\n const headerRow = createElement('div', 'row');\r\n headerRow.innerHTML = `\r\n
Source
\r\n
Hitchance
\r\n
DPS
\r\n
%
\r\n `;\r\n\r\n const resultDiv = createElement('div', '', '', `simulationResultDamageDoneEnemy${enemyIndex}`);\r\n\r\n accordionBodyDiv.appendChild(headerRow);\r\n accordionBodyDiv.appendChild(resultDiv);\r\n collapseDiv.appendChild(accordionBodyDiv);\r\n headerH2.appendChild(button);\r\n accordionItemDiv.appendChild(headerH2);\r\n accordionItemDiv.appendChild(collapseDiv);\r\n accordionMainDiv.appendChild(accordionItemDiv);\r\n colDiv.appendChild(accordionMainDiv);\r\n accordionDiv.appendChild(colDiv);\r\n\r\n return accordionDiv;\r\n}\r\nfunction createDamageTakenAccordion(enemyIndex) {\r\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageTakenAccordionEnemy${enemyIndex}`);\r\n\r\n const colDiv = createElement('div', 'col');\r\n const accordionMainDiv = createElement('div', 'accordion');\r\n const accordionItemDiv = createElement('div', 'accordion-item');\r\n\r\n const headerH2 = createElement('h2', 'accordion-header');\r\n const button = createElement('button', 'accordion-button collapsed',\r\n `Damage Taken (Enemy ${enemyIndex})`,\r\n `buttonSimulationResultDamageTakenAccordionEnemy${enemyIndex}`\r\n );\r\n button.setAttribute('type', 'button');\r\n button.setAttribute('data-bs-toggle', 'collapse');\r\n button.setAttribute('data-bs-target', `#collapseDamageTaken${enemyIndex}`);\r\n button.style.padding = '0.5em';\r\n\r\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageTaken${enemyIndex}`);\r\n const accordionBodyDiv = createElement('div', 'accordion-body');\r\n\r\n const headerRow = createElement('div', 'row');\r\n headerRow.innerHTML = `\r\n
Source
\r\n
Hitchance
\r\n
DPS
\r\n
%
\r\n `;\r\n\r\n const resultDiv = createElement('div', '', '', `simulationResultDamageTakenEnemy${enemyIndex}`);\r\n\r\n accordionBodyDiv.appendChild(headerRow);\r\n accordionBodyDiv.appendChild(resultDiv);\r\n collapseDiv.appendChild(accordionBodyDiv);\r\n headerH2.appendChild(button);\r\n accordionItemDiv.appendChild(headerH2);\r\n accordionItemDiv.appendChild(collapseDiv);\r\n accordionMainDiv.appendChild(accordionItemDiv);\r\n colDiv.appendChild(accordionMainDiv);\r\n accordionDiv.appendChild(colDiv);\r\n\r\n return accordionDiv;\r\n}\r\n\r\n\r\nfunction initDamageDoneTaken() {\r\n for (let i = 64; i > 0; i--) {\r\n document.getElementById(\"simulationResultTotalDamageDone\").insertAdjacentElement('afterend', createDamageDoneAccordion(i));\r\n document.getElementById(\"simulationResultTotalDamageTaken\").insertAdjacentElement('afterend', createDamageTakenAccordion(i));\r\n }\r\n}\r\n\r\nfunction showSimulationResult(simResult) {\r\n currentSimResults = simResult;\r\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\r\n expensesModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\r\n revenueModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let noRngRevenueModalTable = document.querySelector(\"#noRngRevenueTable > tbody\");\r\n noRngRevenueModalTable.innerHTML = 'ItemPriceAmountTotal';\r\n let playerToDisplay = \"player1\";\r\n if (selectedPlayers.includes(parseInt(currentPlayerTabId))) {\r\n playerToDisplay = \"player\" + currentPlayerTabId;\r\n }\r\n if (!simResult.dropRateMultiplier[playerToDisplay]) {\r\n return;\r\n }\r\n\r\n showKills(simResult, playerToDisplay);\r\n showDeaths(simResult, playerToDisplay);\r\n showExperienceGained(simResult, playerToDisplay);\r\n showConsumablesUsed(simResult, playerToDisplay);\r\n showHpSpent(simResult, playerToDisplay);\r\n showManaUsed(simResult, playerToDisplay);\r\n showHitpointsGained(simResult, playerToDisplay);\r\n showManapointsGained(simResult, playerToDisplay);\r\n showDamageDone(simResult, playerToDisplay);\r\n showDamageTaken(simResult, playerToDisplay);\r\n renderWipeEvents(simResult);\r\n window.profit = window.revenue - window.expenses;\r\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\r\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\r\n window.noRngProfit = window.noRngRevenue - window.expenses;\r\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\r\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\r\n}\r\n\r\nfunction showAllSimulationResults(simResults) {\r\n let displaySimResults = manipulateSimResultsDataForDisplay(simResults);\r\n updateAllSimsModal(displaySimResults);\r\n}\r\n\r\nfunction manipulateSimResultsDataForDisplay(simResults) {\r\n let displaySimResults = [];\r\n for (let i = 0; i < simResults.length; i++) {\r\n for (let j = 0; j < selectedPlayers.length; j++) {\r\n let playerToDisplay = \"player\" + selectedPlayers[j].toString();\r\n let simResult = simResults[i];\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let zoneName = simResult.zoneName;\r\n let difficultyTier = simResult.difficultyTier;\r\n let encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\r\n let playerDeaths = simResult.deaths[playerToDisplay] ?? 0;\r\n let deathsPerHour = (playerDeaths / hoursSimulated).toFixed(2);\r\n\r\n let totalExperience = 0;\r\n if (simResult.experienceGained[playerToDisplay]) {\r\n totalExperience = Object.values(simResult.experienceGained[playerToDisplay]).reduce((prev, cur) => prev + cur, 0);\r\n }\r\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\r\n\r\n let experiencePerHour = {};\r\n const skills = [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"];\r\n skills.forEach((skill) => {\r\n const skillLower = skill.toLowerCase();\r\n let experience = simResult.experienceGained[playerToDisplay]?.[skillLower] ?? 0;\r\n let experiencePerHourValue = 0;\r\n if (experience != 0) {\r\n experiencePerHourValue = (experience / hoursSimulated).toFixed(0);\r\n }\r\n experiencePerHour[skill] = experiencePerHourValue;\r\n });\r\n getDropProfit(simResult, playerToDisplay);\r\n let noRngRevenue = simResult[\"noRngRevenue\"];\r\n let noRngProfit = simResult[\"noRngProfit\"];\r\n let expenses = simResult[\"expenses\"];\r\n\r\n let displaySimRow = {\r\n \"ZoneName\": zoneName, \"DifficultyTier\": difficultyTier, \"Player\": playerToDisplay, \"Encounters\": encountersPerHour, \"Deaths\": deathsPerHour,\r\n \"TotalExperience\": totalExperiencePerHour, \"Stamina\": experiencePerHour[\"Stamina\"],\r\n \"Intelligence\": experiencePerHour[\"Intelligence\"], \"Attack\": experiencePerHour[\"Attack\"],\r\n \"Magic\": experiencePerHour[\"Magic\"], \"Ranged\": experiencePerHour[\"Ranged\"],\r\n \"Melee\": experiencePerHour[\"Melee\"], \"Defense\": experiencePerHour[\"Defense\"],\r\n \"noRngRevenue\": noRngRevenue,\r\n \"expenses\": expenses,\r\n \"noRngProfit\": noRngProfit\r\n };\r\n displaySimResults.push(displaySimRow);\r\n }\r\n }\r\n return displaySimResults;\r\n}\r\n\r\nfunction fidDropAmount(dropAmount) {\r\n if (Number.isInteger(dropAmount)) return dropAmount;\r\n\r\n const intPart = Math.floor(dropAmount);\r\n const fracPart = dropAmount - intPart;\r\n return Math.random() < fracPart ? intPart + 1 : intPart;\r\n}\r\n\r\nfunction calcDropMaps(simResult, playerToDisplay) {\r\n let dropRateMultiplier = simResult.dropRateMultiplier[playerToDisplay];\r\n let rareFindMultiplier = simResult.rareFindMultiplier[playerToDisplay];\r\n let combatDropQuantity = simResult.combatDropQuantity[playerToDisplay];\r\n let debuffOnLevelGap = simResult.debuffOnLevelGap[playerToDisplay];\r\n\r\n let numberOfPlayers = simResult.numberOfPlayers;\r\n let monsters = Object.keys(simResult.deaths)\r\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\r\n .sort();\r\n\r\n const totalDropMap = new Map();\r\n const noRngTotalDropMap = new Map();\r\n for (const monster of monsters) {\r\n const dropMap = new Map();\r\n const rareDropMap = new Map();\r\n if (combatMonsterDetailMap[monster].dropTable) {\r\n for (const drop of combatMonsterDetailMap[monster].dropTable) {\r\n if (drop.minDifficultyTier > simResult.difficultyTier) {\r\n continue;\r\n }\r\n\r\n let multiplier = 1.0 + 0.1 * simResult.difficultyTier;\r\n let dropRate = Math.min(1.0, multiplier * (drop.dropRate + (drop.dropRatePerDifficultyTier ?? 0) * simResult.difficultyTier));\r\n if (dropRate <= 0) continue;\r\n\r\n dropMap.set(drop.itemHrid, { \"dropRate\": Math.min(1.0, dropRate * dropRateMultiplier), \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\r\n }\r\n if (combatMonsterDetailMap[monster].rareDropTable)\r\n for (const drop of combatMonsterDetailMap[monster].rareDropTable) {\r\n if (drop.minDifficultyTier > simResult.difficultyTier) {\r\n continue;\r\n }\r\n rareDropMap.set(drop.itemHrid, { \"dropRate\": drop.dropRate * rareFindMultiplier, \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\r\n }\r\n\r\n for (let dropObject of dropMap.values()) {\r\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\r\n\r\n }\r\n for (let dropObject of rareDropMap.values()) {\r\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\r\n }\r\n\r\n for (let i = 0; i < simResult.deaths[monster]; i++) {\r\n for (let dropObject of dropMap.values()) {\r\n let chance = Math.random();\r\n if (chance <= dropObject.dropRate / numberOfPlayers) {\r\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\r\n dropObject.number = dropObject.number + fidDropAmount(amount);\r\n }\r\n }\r\n for (let dropObject of rareDropMap.values()) {\r\n let chance = Math.random();\r\n if (chance <= dropObject.dropRate / numberOfPlayers) {\r\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\r\n dropObject.number = dropObject.number + fidDropAmount(amount);\r\n }\r\n }\r\n }\r\n for (let [name, dropObject] of dropMap.entries()) {\r\n if (totalDropMap.has(name)) {\r\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\r\n } else {\r\n totalDropMap.set(name, dropObject.number);\r\n }\r\n if (noRngTotalDropMap.has(name)) {\r\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\r\n } else {\r\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\r\n }\r\n }\r\n for (let [name, dropObject] of rareDropMap.entries()) {\r\n if (totalDropMap.has(name)) {\r\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\r\n } else {\r\n totalDropMap.set(name, dropObject.number);\r\n }\r\n if (noRngTotalDropMap.has(name)) {\r\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\r\n } else {\r\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return { totalDropMap, noRngTotalDropMap };\r\n}\r\n\r\nfunction getDropProfit(simResult, playerToDisplay) {\r\n let { totalDropMap, noRngTotalDropMap } = calcDropMaps(simResult, playerToDisplay);\r\n\r\n let noRngTotal = 0;\r\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\r\n let price = -1;\r\n let revenueSetting = document.getElementById('selectPrices_drops').value;\r\n if (window.prices) {\r\n let item = window.prices[name];\r\n if (item) {\r\n if (revenueSetting == 'bid') {\r\n if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n } else if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n }\r\n } else if (revenueSetting == 'ask') {\r\n if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n } else if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n }\r\n }\r\n if (price == -1) {\r\n price = item['vendor'];\r\n }\r\n }\r\n }\r\n noRngTotal += price * dropAmount;\r\n }\r\n\r\n let consumablesUsed = simResult.consumablesUsed?.[playerToDisplay];\r\n\r\n if (consumablesUsed) {\r\n consumablesUsed = Object.entries(consumablesUsed).sort((a, b) => b[1] - a[1]);\r\n } else {\r\n consumablesUsed = [];\r\n }\r\n\r\n let expenses = 0;\r\n for (const [consumable, amount] of consumablesUsed) {\r\n let price = -1;\r\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\r\n if (window.prices) {\r\n let item = window.prices[consumable];\r\n if (item) {\r\n if (expensesSetting == 'bid') {\r\n if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n } else if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n }\r\n } else if (expensesSetting == 'ask') {\r\n if (item['ask'] !== -1) {\r\n price = item['ask'];\r\n } else if (item['bid'] !== -1) {\r\n price = item['bid'];\r\n }\r\n }\r\n if (price == -1) {\r\n price = item['vendor'];\r\n }\r\n }\r\n }\r\n expenses += price * amount;\r\n }\r\n\r\n simResult[\"noRngRevenue\"] = (noRngTotal).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n simResult[\"expenses\"] = (expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n simResult[\"noRngProfit\"] = (noRngTotal - expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\r\n}\r\n\r\nfunction updateAllSimsModal(data) {\r\n const tableBody = document.getElementById('allZonesData').getElementsByTagName('tbody')[0];\r\n tableBody.innerHTML = '';\r\n data.forEach(item => {\r\n const row = document.createElement('tr');\r\n\r\n Object.keys(item).forEach(key => {\r\n const cell = document.createElement('td');\r\n cell.textContent = item[key];\r\n if (key === 'ZoneName') {\r\n cell.setAttribute(\"data-i18n\", \"actionNames.\" + item[key]);\r\n }\r\n row.appendChild(cell);\r\n });\r\n\r\n tableBody.appendChild(row);\r\n });\r\n\r\n const table = document.getElementById('allZonesData');\r\n const rows = table.getElementsByTagName('tr');\r\n const numCols = rows[0].cells.length;\r\n\r\n // 遍历每一列\r\n for (let col = 5; col < numCols; col++) {\r\n let max = -Infinity;\r\n let maxCell = null;\r\n\r\n // 找到最大值及其单元格\r\n for (let row = 1; row < rows.length; row++) {\r\n const cell = rows[row].cells[col];\r\n const value = parseFloat(cell.textContent.replace(/,/g, ''));\r\n if (value > max) {\r\n max = value;\r\n maxCell = cell;\r\n }\r\n }\r\n\r\n // 将最大值单元格的背景色设置为绿色\r\n if (maxCell && max != 0) {\r\n maxCell.style.backgroundColor = 'green';\r\n maxCell.style.color = 'white'; // 设置文字颜色为白色以提高可读性\r\n }\r\n }\r\n}\r\n\r\nlet currentSortColumn = null;\r\nlet currentSortDirection = 'desc';\r\n\r\nfunction sortTable(tableId, columnIndex, direction) {\r\n const table = document.getElementById(tableId);\r\n const tbody = table.querySelector('tbody');\r\n const rows = Array.from(tbody.querySelectorAll('tr'));\r\n\r\n const sortedRows = rows.sort((rowA, rowB) => {\r\n const cellA = rowA.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\r\n const cellB = rowB.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\r\n\r\n const valueA = parseFloat(cellA.replace(/,/g, ''));\r\n const valueB = parseFloat(cellB.replace(/,/g, ''));\r\n\r\n return direction === 'asc' ? valueA - valueB : valueB - valueA;\r\n });\r\n\r\n sortedRows.forEach(row => tbody.appendChild(row));\r\n updateSortIndicators(tableId, columnIndex, direction);\r\n}\r\n\r\nfunction updateSortIndicators(tableId, columnIndex, direction) {\r\n const headers = document.querySelectorAll(`#${tableId} th`);\r\n headers.forEach((header, index) => {\r\n header.classList.remove('sort-asc', 'sort-desc');\r\n if (index === columnIndex) {\r\n header.classList.add(direction === 'asc' ? 'sort-asc' : 'sort-desc');\r\n }\r\n });\r\n}\r\n\r\ndocument.querySelectorAll('#allZonesData th').forEach((header, index) => {\r\n if (index === 0) return;\r\n if (index === 1) return;\r\n if (index === 2) return;\r\n\r\n header.addEventListener('click', () => {\r\n if (currentSortColumn === index) {\r\n currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';\r\n } else {\r\n currentSortColumn = index;\r\n currentSortDirection = 'desc';\r\n }\r\n sortTable('allZonesData', currentSortColumn, currentSortDirection);\r\n });\r\n});\r\n\r\ndocument.getElementById('buttonExportResults').addEventListener('click', function () {\r\n var table = document.getElementById('allZonesData');\r\n var csv = [];\r\n var rows = table.querySelectorAll('tr');\r\n\r\n for (var i = 0; i < rows.length; i++) {\r\n var row = rows[i];\r\n var cols = row.querySelectorAll('th, td');\r\n var csvRow = [];\r\n\r\n cols.forEach(function (col) {\r\n csvRow.push('\"' + col.innerText.replace(/\"/g, '\"\"') + '\"');\r\n });\r\n\r\n csv.push(csvRow.join(','));\r\n }\r\n\r\n var csvFile = new Blob([csv.join('\\n')], { type: 'text/csv' });\r\n var downloadLink = document.createElement('a');\r\n downloadLink.download = 'simData.csv';\r\n downloadLink.href = URL.createObjectURL(csvFile);\r\n downloadLink.style.display = 'none';\r\n document.body.appendChild(downloadLink);\r\n downloadLink.click();\r\n document.body.removeChild(downloadLink);\r\n});\r\n\r\nfunction showKills(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultKills\");\r\n let dropsResultDiv = document.getElementById(\"simulationResultDrops\");\r\n let noRngDropsResultDiv = document.getElementById(\"noRngDrops\");\r\n let newChildren = [];\r\n let newDropChildren = [];\r\n let newNoRngDropChildren = [];\r\n\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let encountersPerHour = 0;\r\n let encountersRow = null;\r\n if (simResult.isDungeon) {\r\n let wavesCompletedRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Wave Reached\", simResult.maxWaveReached]);\r\n wavesCompletedRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxWaveReached\");\r\n newChildren.push(wavesCompletedRow);\r\n let completedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Completed Dungeons\", simResult.dungeonsCompleted]);\r\n completedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsCompleted\");\r\n newChildren.push(completedDungeonsRow);\r\n if (simResult.dungeonsFailed > 0) {\r\n let failedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Failed Dungeons\", simResult.dungeonsFailed]);\r\n failedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsFailed\");\r\n newChildren.push(failedDungeonsRow);\r\n }\r\n encountersPerHour = (simResult.dungeonsCompleted / hoursSimulated).toFixed(1);\r\n let averageTime = (hoursSimulated * 60 / simResult.dungeonsCompleted).toFixed(1);\r\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Average Time\", averageTime]);\r\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.averageTime\");\r\n if (simResult.minDungenonTime > 0) {\r\n let minimumTime = (simResult.minDungenonTime / ONE_SECOND / 60).toFixed(1);\r\n let minimumTimeRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Minimum Time\", minimumTime]);\r\n minimumTimeRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.minimumTime\");\r\n newChildren.push(minimumTimeRow);\r\n }\r\n } else {\r\n encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\r\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Encounters\", encountersPerHour]);\r\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.encounters\");\r\n }\r\n\r\n if (simResult.maxEnrageStack > 0) {\r\n let enrageRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Enrage Stack\", simResult.maxEnrageStack]);\r\n enrageRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxEnrageStack\");\r\n newChildren.push(enrageRow);\r\n }\r\n\r\n if (simResult.debuffOnLevelGap[playerToDisplay] != 0) {\r\n let debuffOnLevelGapRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Debuff on Level Gap\", Math.round(simResult.debuffOnLevelGap[playerToDisplay] * 100) + \"%\"]);\r\n debuffOnLevelGapRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.debuffOnLevelGap\");\r\n newChildren.push(debuffOnLevelGapRow);\r\n }\r\n\r\n newChildren.push(encountersRow);\r\n\r\n Object.keys(simResult.deaths)\r\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\r\n .sort()\r\n .forEach(monster => {\r\n let killsPerHour = (simResult.deaths[monster] / hoursSimulated).toFixed(1);\r\n let monsterRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [combatMonsterDetailMap[monster].name, killsPerHour]\r\n );\r\n monsterRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + monster);\r\n newChildren.push(monsterRow);\r\n });\r\n\r\n let { totalDropMap, noRngTotalDropMap } = !simResult.isDungeon ? calcDropMaps(simResult, playerToDisplay) : {totalDropMap:new Map(), noRngTotalDropMap:new Map()};\r\n\r\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\r\n let total = 0;\r\n for (let [name, dropAmount] of totalDropMap.entries()) {\r\n let dropRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [name, dropAmount.toLocaleString()]\r\n );\r\n dropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\r\n newDropChildren.push(dropRow);\r\n\r\n let tableRow = ' tbody\");\r\n let noRngTotal = 0;\r\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\r\n let noRngDropRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [name, dropAmount.toLocaleString()]\r\n );\r\n noRngDropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\r\n newNoRngDropChildren.push(noRngDropRow);\r\n\r\n let tableRow = ' prev + cur, 0);\r\n }\r\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\r\n let totalRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Total\", totalExperiencePerHour]);\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"].forEach((skill) => {\r\n let experience = simResult.experienceGained[playerToDisplay]?.[skill.toLowerCase()] ?? 0;\r\n if (experience == 0) {\r\n return;\r\n }\r\n let experiencePerHour = (experience / hoursSimulated).toFixed(0);\r\n let experienceRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [skill, experiencePerHour]);\r\n experienceRow.firstElementChild.setAttribute(\"data-i18n\", \"leaderboardCategoryNames.\" + skill.toLowerCase());\r\n newChildren.push(experienceRow);\r\n });\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showHpSpent(simResult, playerToDisplay) {\r\n let hpSpentHeadingDiv = document.getElementById(\"simulationHpSpentHeading\");\r\n hpSpentHeadingDiv.classList.add(\"d-none\");\r\n let hpSpentDiv = document.getElementById(\"simulationHpSpent\");\r\n hpSpentDiv.classList.add(\"d-none\");\r\n\r\n if (simResult.hitpointsSpent[playerToDisplay]) {\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n let hpSpentSources = [];\r\n for (const source of Object.keys(simResult.hitpointsSpent[playerToDisplay])) {\r\n let hpSpentPerHour = (simResult.hitpointsSpent[playerToDisplay][source] / hoursSimulated).toFixed(2);\r\n let hpSpentRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [abilityDetailMap[source].name, hpSpentPerHour]);\r\n hpSpentRow.firstElementChild.setAttribute(\"data-i18n\", \"abilityNames.\" + source);\r\n hpSpentSources.push(hpSpentRow);\r\n }\r\n hpSpentDiv.replaceChildren(...hpSpentSources);\r\n hpSpentHeadingDiv.classList.remove(\"d-none\");\r\n hpSpentDiv.classList.remove(\"d-none\");\r\n }\r\n}\r\n\r\nfunction showConsumablesUsed(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultConsumablesUsed\");\r\n let newChildren = [];\r\n\r\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\r\n\r\n if (!simResult.consumablesUsed[playerToDisplay]) {\r\n resultDiv.replaceChildren(...newChildren);\r\n window.expenses = 0;\r\n return;\r\n }\r\n\r\n let consumablesUsed = Object.entries(simResult.consumablesUsed[playerToDisplay]).sort((a, b) => b[1] - a[1]);\r\n\r\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\r\n let total = 0;\r\n for (const [consumable, amount] of consumablesUsed) {\r\n let consumablesPerHour = (amount / hoursSimulated).toFixed(0);\r\n let consumableRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [itemDetailMap[consumable].name, consumablesPerHour]\r\n );\r\n consumableRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + consumable);\r\n newChildren.push(consumableRow);\r\n\r\n let tableRow = ' b[1] - a[1]);\r\n\r\n let totalHitpointsGained = hitpointsGained.reduce((prev, cur) => prev + cur[1], 0);\r\n let totalHitpointsPerSecond = (totalHitpointsGained / secondsSimulated).toFixed(2);\r\n let totalRow = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [\"Total\", totalHitpointsPerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [source, amount] of hitpointsGained) {\r\n if (amount == 0) {\r\n continue;\r\n }\r\n\r\n let sourceText;\r\n let sourceFullHrid;\r\n switch (source) {\r\n case \"regen\":\r\n sourceText = \"Regen\";\r\n sourceFullHrid = \"combatStats.hpRegenPer10\";\r\n break;\r\n case \"lifesteal\":\r\n sourceText = \"Life Steal\";\r\n sourceFullHrid = \"combatStats.lifeSteal\";\r\n break;\r\n case \"bloom\":\r\n sourceText = \"Bloom\";\r\n sourceFullHrid = \"combatStats.bloom\";\r\n break;\r\n default:\r\n if (itemDetailMap[source]) {\r\n sourceText = itemDetailMap[source].name;\r\n sourceFullHrid = \"itemNames.\" + source;\r\n } else if (abilityDetailMap[source]) {\r\n sourceText = abilityDetailMap[source].name;\r\n sourceFullHrid = \"abilityNames.\" + source;\r\n }\r\n break;\r\n }\r\n let hitpointsPerSecond = (amount / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * amount) / totalHitpointsGained).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [sourceText, hitpointsPerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showManapointsGained(simResult, playerToDisplay) {\r\n let resultDiv = document.getElementById(\"simulationResultManaRestored\");\r\n let newChildren = [];\r\n\r\n let secondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n if (!simResult.manapointsGained[playerToDisplay]) {\r\n resultDiv.replaceChildren(...newChildren);\r\n return;\r\n }\r\n\r\n let manapointsGained = Object.entries(simResult.manapointsGained[playerToDisplay]).sort((a, b) => b[1] - a[1]);\r\n\r\n let totalManapointsGained = manapointsGained.reduce((prev, cur) => prev + cur[1], 0);\r\n let totalManapointsPerSecond = (totalManapointsGained / secondsSimulated).toFixed(2);\r\n let totalRow = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [\"Total\", totalManapointsPerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [source, amount] of manapointsGained) {\r\n if (amount == 0) {\r\n continue;\r\n }\r\n\r\n let sourceText;\r\n let sourceFullHrid;\r\n switch (source) {\r\n case \"regen\":\r\n sourceText = \"Regen\";\r\n sourceFullHrid = \"combatStats.mpRegenPer10\";\r\n break;\r\n case \"manaLeech\":\r\n sourceText = \"Mana Leech\";\r\n sourceFullHrid = \"combatStats.manaLeech\";\r\n break;\r\n case \"ripple\":\r\n sourceText = \"Ripple\";\r\n sourceFullHrid = \"combatStats.ripple\";\r\n break;\r\n default:\r\n sourceText = itemDetailMap[source].name;\r\n sourceFullHrid = \"itemNames.\" + source;\r\n break;\r\n }\r\n let manapointsPerSecond = (amount / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * amount) / totalManapointsGained).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\r\n [sourceText, manapointsPerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n let ranOutOfManaText = simResult.playerRanOutOfMana[playerToDisplay] ? \"Yes\" : \"No\";\r\n let ranOutOfManaRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Ran out of mana\", ranOutOfManaText]);\r\n ranOutOfManaRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfMana\");\r\n ranOutOfManaRow.lastElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.\" + ranOutOfManaText);\r\n newChildren.push(ranOutOfManaRow);\r\n\r\n if (simResult.playerRanOutOfMana[playerToDisplay]) {\r\n let ranOutOfManaStat = simResult.playerRanOutOfManaTime[playerToDisplay]; // {isOutOfMana: false, startTimeForOutOfMana:0, totalTimeForOutOfMana:0};\r\n let totalTimeForOut = ranOutOfManaStat.totalTimeForOutOfMana + (ranOutOfManaStat.isOutOfMana ? (simResult.simulatedTime - ranOutOfManaStat.startTimeForOutOfMana) : 0);\r\n\r\n let ranOutOfManaStatRow = createRow(\r\n [\"col-md-6\", \"col-md-6 text-end\"],\r\n [\r\n \"Run Out Ratio\",\r\n (totalTimeForOut / simResult.simulatedTime * 100).toFixed(2) + \"%\"\r\n ]\r\n );\r\n ranOutOfManaStatRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfManaRatio\");\r\n newChildren.push(ranOutOfManaStatRow);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction showDamageDone(simResult, playerToDisplay) {\r\n let totalDamageDone = {};\r\n let enemyIndex = 1;\r\n\r\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n for (let i = 1; i < 64; i++) {\r\n let accordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + i);\r\n hideElement(accordion);\r\n }\r\n\r\n let bossTimeHeadingDiv = document.getElementById(\"simulationBossTimeHeading\");\r\n bossTimeHeadingDiv.classList.add(\"d-none\");\r\n let bossTimeDiv = document.getElementById(\"simulationBossTime\");\r\n bossTimeDiv.classList.add(\"d-none\");\r\n\r\n if (!simResult.attacks[playerToDisplay]) {\r\n return;\r\n }\r\n\r\n for (const [target, abilities] of Object.entries(simResult.attacks[playerToDisplay])) {\r\n let targetDamageDone = {};\r\n\r\n const i = simResult.timeSpentAlive.findIndex(e => e.name === target);\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\r\n\r\n for (const [ability, abilityCasts] of Object.entries(abilities)) {\r\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\r\n let misses = abilityCasts[\"miss\"] ?? 0;\r\n let damage = Object.entries(abilityCasts)\r\n .filter((entry) => entry[0] != \"miss\")\r\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\r\n\r\n targetDamageDone[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n if (totalDamageDone[ability]) {\r\n totalDamageDone[ability].casts += casts;\r\n totalDamageDone[ability].misses += misses;\r\n totalDamageDone[ability].damage += damage;\r\n } else {\r\n totalDamageDone[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n }\r\n }\r\n\r\n let resultDiv = document.getElementById(\"simulationResultDamageDoneEnemy\" + enemyIndex);\r\n createDamageTable(resultDiv, targetDamageDone, aliveSecondsSimulated);\r\n\r\n let resultAccordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + enemyIndex);\r\n showElement(resultAccordion);\r\n\r\n let resultAccordionButton = document.getElementById(\r\n \"buttonSimulationResultDamageDoneAccordionEnemy\" + enemyIndex\r\n );\r\n let targetName = combatMonsterDetailMap[target].name;\r\n resultAccordionButton.innerHTML = \"Damage Done (\" + \"\" + targetName + \"\" + \")\";\r\n\r\n if (simResult.bossSpawns.includes(target)) {\r\n let hoursSpentOnBoss = (aliveSecondsSimulated / 60 / 60).toFixed(2);\r\n let percentSpentOnBoss = (aliveSecondsSimulated / totalSecondsSimulated * 100).toFixed(2);\r\n\r\n let bossRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [targetName, hoursSpentOnBoss + \"h(\" + percentSpentOnBoss + \"%)\"]);\r\n bossRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + target);\r\n bossTimeDiv.replaceChildren(bossRow);\r\n\r\n bossTimeHeadingDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.classList.remove(\"d-none\");\r\n }\r\n\r\n enemyIndex++;\r\n }\r\n\r\n if (simResult.isDungeon) {\r\n let newChildren = [];\r\n for (const waveName of simResult.bossSpawns) {\r\n // waveName is something like \"#15,/monsters/jackalope,/monsters/butterjerry\"\r\n let waveNumber = waveName.split(\",\")[0];\r\n const idx = simResult.timeSpentAlive.findIndex(e => e.name === waveNumber);\r\n if (idx == -1 || simResult.timeSpentAlive[idx].count == 0) {\r\n continue;\r\n }\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[idx].timeSpentAlive / ONE_SECOND / simResult.timeSpentAlive[idx].count;\r\n let bossRow = createRow([\"col-md-6\", \"col-md-2\", \"col-md-4 text-end\"], [waveNumber, simResult.timeSpentAlive[idx].count, aliveSecondsSimulated.toFixed(1) + \"s\"]);\r\n newChildren.push(bossRow);\r\n }\r\n if (newChildren.length > 0) {\r\n bossTimeHeadingDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.classList.remove(\"d-none\");\r\n bossTimeDiv.replaceChildren(...newChildren);\r\n }\r\n }\r\n\r\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageDone\");\r\n createDamageTable(totalResultDiv, totalDamageDone, totalSecondsSimulated);\r\n}\r\n\r\nfunction showDamageTaken(simResult, playerToDisplay) {\r\n let totalDamageTaken = {};\r\n let enemyIndex = 1;\r\n\r\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\r\n\r\n for (let i = 1; i < 64; i++) {\r\n let accordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + i);\r\n hideElement(accordion);\r\n }\r\n\r\n for (const [source, targets] of Object.entries(simResult.attacks)) {\r\n const validSources = [\"player1\", \"player2\", \"player3\", \"player4\", \"player5\"];\r\n if (validSources.includes(source)) {\r\n continue;\r\n }\r\n const i = simResult.timeSpentAlive.findIndex(e => e.name === source);\r\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\r\n let sourceDamageTaken = {};\r\n if (targets[playerToDisplay] && Object.keys(targets[playerToDisplay]).length > 0) {\r\n for (const [ability, abilityCasts] of Object.entries(targets[playerToDisplay])) {\r\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\r\n let misses = abilityCasts[\"miss\"] ?? 0;\r\n let damage = Object.entries(abilityCasts)\r\n .filter((entry) => entry[0] != \"miss\")\r\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\r\n\r\n sourceDamageTaken[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n if (totalDamageTaken[ability]) {\r\n totalDamageTaken[ability].casts += casts;\r\n totalDamageTaken[ability].misses += misses;\r\n totalDamageTaken[ability].damage += damage;\r\n } else {\r\n totalDamageTaken[ability] = {\r\n casts,\r\n misses,\r\n damage,\r\n };\r\n }\r\n }\r\n }\r\n\r\n let resultDiv = document.getElementById(\"simulationResultDamageTakenEnemy\" + enemyIndex);\r\n createDamageTable(resultDiv, sourceDamageTaken, aliveSecondsSimulated);\r\n\r\n let resultAccordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + enemyIndex);\r\n showElement(resultAccordion);\r\n\r\n let resultAccordionButton = document.getElementById(\r\n \"buttonSimulationResultDamageTakenAccordionEnemy\" + enemyIndex\r\n );\r\n let sourceName = combatMonsterDetailMap[source].name;\r\n resultAccordionButton.innerHTML = \"Damage Taken (\" + \"\" + sourceName + \"\" + \")\";\r\n\r\n enemyIndex++;\r\n }\r\n\r\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageTaken\");\r\n createDamageTable(totalResultDiv, totalDamageTaken, totalSecondsSimulated);\r\n}\r\n\r\nfunction createDamageTable(resultDiv, damageDone, secondsSimulated) {\r\n let newChildren = [];\r\n\r\n let sortedDamageDone = Object.entries(damageDone).sort((a, b) => b[1].damage - a[1].damage);\r\n\r\n let totalCasts = sortedDamageDone.reduce((prev, cur) => prev + cur[1].casts, 0);\r\n let totalMisses = sortedDamageDone.reduce((prev, cur) => prev + cur[1].misses, 0);\r\n let totalDamage = sortedDamageDone.reduce((prev, cur) => prev + cur[1].damage, 0);\r\n let totalHitChance = ((100 * (totalCasts - totalMisses)) / totalCasts).toFixed(1);\r\n let totalDamagePerSecond = (totalDamage / secondsSimulated).toFixed(2);\r\n\r\n let totalRow = createRow(\r\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\r\n [\"Total\", totalHitChance + \"%\", totalDamagePerSecond, \"100%\"]\r\n );\r\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\r\n newChildren.push(totalRow);\r\n\r\n for (const [ability, damageInfo] of sortedDamageDone) {\r\n let abilityText;\r\n let abilityFullHrid;\r\n switch (ability) {\r\n case \"autoAttack\":\r\n abilityText = \"Auto Attack\";\r\n abilityFullHrid = \"combatUnit.autoAttack\";\r\n break;\r\n case \"parry\":\r\n abilityText = \"Parry Attack\";\r\n abilityFullHrid = \"common:simulationResults.parryAttack\";\r\n break;\r\n case \"damageOverTime\":\r\n abilityText = \"Damage Over Time\";\r\n abilityFullHrid = \"common:simulationResults.damageOverTime\";\r\n break;\r\n case \"physicalThorns\":\r\n abilityText = \"Physical Thorns\";\r\n abilityFullHrid = \"combatStats.physicalThorns\";\r\n break;\r\n case \"elementalThorns\":\r\n abilityText = \"Elemental Thorns\";\r\n abilityFullHrid = \"combatStats.elementalThorns\";\r\n break;\r\n case \"retaliation\":\r\n abilityText = \"Retaliation\";\r\n abilityFullHrid = \"combatStats.retaliation\";\r\n break;\r\n case 'blaze':\r\n abilityText = \"Blaze\";\r\n abilityFullHrid = \"combatStats.blaze\";\r\n break;\r\n default:\r\n abilityText = abilityDetailMap[ability].name;\r\n abilityFullHrid = \"abilityNames.\" + ability;\r\n break;\r\n }\r\n\r\n let hitChance = ((100 * (damageInfo.casts - damageInfo.misses)) / damageInfo.casts).toFixed(1);\r\n let damagePerSecond = (damageInfo.damage / secondsSimulated).toFixed(2);\r\n let percentage = ((100 * damageInfo.damage) / totalDamage).toFixed(0);\r\n\r\n let row = createRow(\r\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\r\n [abilityText, hitChance + \"%\", damagePerSecond, percentage + \"%\"]\r\n );\r\n row.firstElementChild.setAttribute(\"data-i18n\", abilityFullHrid);\r\n newChildren.push(row);\r\n }\r\n\r\n resultDiv.replaceChildren(...newChildren);\r\n}\r\n\r\nfunction createRow(columnClassNames, columnValues) {\r\n let row = createElement(\"div\", \"row\");\r\n\r\n for (let i = 0; i < columnClassNames.length; i++) {\r\n let column = createElement(\"div\", columnClassNames[i], columnValues[i]);\r\n row.appendChild(column);\r\n }\r\n\r\n return row;\r\n}\r\n\r\nfunction createElement(tagName, className, innerHTML = \"\", id = \"\") {\r\n let element = document.createElement(tagName);\r\n element.className = className;\r\n element.innerHTML = innerHTML;\r\n if (id) element.id = id;\r\n return element;\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Simulation Controls\r\n\r\ndocument.addEventListener('DOMContentLoaded', function () {\r\n const simDungeonToggle = document.getElementById('simDungeonToggle');\r\n const playerContainer = document.getElementById('playerCheckBox');\r\n\r\n function addPlayers() {\r\n const player4 = document.createElement('div');\r\n player4.classList.add('form-check');\r\n player4.innerHTML = `\r\n \r\n \r\n `;\r\n\r\n const player5 = document.createElement('div');\r\n player5.classList.add('form-check');\r\n player5.innerHTML = `\r\n \r\n \r\n `;\r\n\r\n playerContainer.appendChild(player4);\r\n playerContainer.appendChild(player5);\r\n }\r\n\r\n function removePlayers() {\r\n const player4 = document.getElementById('player4');\r\n const player5 = document.getElementById('player5');\r\n if (player4) player4.parentElement.remove();\r\n if (player5) player5.parentElement.remove();\r\n }\r\n\r\n function updatePlayerNames() {\r\n const tabLinks = document.querySelectorAll('#playerTab .nav-link');\r\n tabLinks.forEach((tabLink, index) => {\r\n const label = document.querySelector(`label[for=\"player${index + 1}\"]`);\r\n if (label) {\r\n label.textContent = tabLink.textContent.trim();\r\n }\r\n });\r\n }\r\n\r\n function updatePlayersCheckbox(isCheck) {\r\n const boxes = playerContainer.querySelectorAll('.player-checkbox');\r\n boxes.forEach((checkBox) => { checkBox.checked = isCheck });\r\n }\r\n\r\n function updateDifficultySelect(isCheck) {\r\n const difficultySelect = document.getElementById('selectDifficulty');\r\n // disable last four option\r\n if (isCheck && Number(difficultySelect.value) >= 3) {\r\n difficultySelect.value = 0;\r\n }\r\n for (let i = 3; i < difficultySelect.options.length; i++) {\r\n difficultySelect.options[i].disabled = isCheck;\r\n }\r\n }\r\n\r\n simDungeonToggle.addEventListener('change', function () {\r\n if (simDungeonToggle.checked) {\r\n addPlayers();\r\n updatePlayersCheckbox(true);\r\n updateDifficultySelect(true);\r\n } else {\r\n removePlayers();\r\n updatePlayersCheckbox(false);\r\n updateDifficultySelect(false);\r\n }\r\n updatePlayerNames();\r\n });\r\n\r\n document.getElementById('buttonSimulationSetup').addEventListener('click', function () {\r\n updatePlayerNames();\r\n });\r\n});\r\n\r\nfunction onTabChange(event) {\r\n const nextPlayerTabId = event.target.getAttribute('href').substring(7);\r\n savePreviousPlayer(currentPlayerTabId);\r\n updateNextPlayer(nextPlayerTabId);\r\n currentPlayerTabId = nextPlayerTabId;\r\n updateState();\r\n updateUI();\r\n if (Object.keys(currentSimResults).length !== 0) {\r\n showSimulationResult(currentSimResults);\r\n }\r\n\r\n updateContent();\r\n}\r\n\r\ndocument.querySelectorAll('#playerTab .nav-link').forEach(tab => {\r\n tab.addEventListener('shown.bs.tab', onTabChange);\r\n});\r\n\r\nfunction initSimulationControls() {\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n simulationTimeInput.value = 24;\r\n\r\n buttonStartSimulation.addEventListener(\"click\", (event) => {\r\n let invalidElements = document.querySelectorAll(\":invalid\");\r\n if (invalidElements.length > 0) {\r\n invalidElements.forEach((element) => element.reportValidity());\r\n return;\r\n }\r\n savePreviousPlayer(currentPlayerTabId);\r\n\r\n const simDungeonToggle = document.getElementById(\"simDungeonToggle\");\r\n const checkboxes = document.querySelectorAll('.player-checkbox');\r\n selectedPlayers = [];\r\n checkboxes.forEach(checkbox => {\r\n if (checkbox.checked) {\r\n const playerNumber = parseInt(checkbox.id.replace('player', ''));\r\n selectedPlayers.push(playerNumber);\r\n }\r\n });\r\n\r\n if (selectedPlayers.length === 0) {\r\n alert(\"You need to select at least one player to sim.\");\r\n return;\r\n }\r\n // buttonStartSimulation.disabled = true;\r\n buttonStopSimulation.style.display = 'block';\r\n startSimulation(selectedPlayers);\r\n });\r\n\r\n buttonStopSimulation.style.display = 'none';\r\n buttonStopSimulation.addEventListener(\"click\", (event) => {\r\n progressbar.style.width = \"0%\";\r\n progressbar.innerHTML = \"0%\";\r\n if (worker) {\r\n worker.terminate();\r\n }\r\n worker = new Worker(new URL(\"worker.js\", import.meta.url));\r\n\r\n if (multiWorker) {\r\n multiWorker.terminate();\r\n }\r\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n\r\n for (let worker of workerPool) {\r\n worker.worker.terminate();\r\n }\r\n\r\n buttonStartSimulation.disabled = false;\r\n buttonStopSimulation.style.display = 'none';\r\n });\r\n}\r\n\r\nfunction startSimulation(selectedPlayers) {\r\n let playersToSim = [];\r\n for (let j = 1; j < 6; j++) {\r\n if (selectedPlayers.includes(j)) {\r\n updateNextPlayer(j);\r\n updateState();\r\n updateUI();\r\n player.hrid = \"player\" + j.toString();\r\n for (let i = 0; i < 3; i++) {\r\n if (food[i] && i < player.combatDetails.combatStats.foodSlots) {\r\n let consumable = new Consumable(food[i], triggerMap[food[i]]);\r\n player.food[i] = consumable;\r\n } else {\r\n player.food[i] = null;\r\n }\r\n\r\n if (drinks[i] && i < player.combatDetails.combatStats.drinkSlots) {\r\n let consumable = new Consumable(drinks[i], triggerMap[drinks[i]]);\r\n player.drinks[i] = consumable;\r\n } else {\r\n player.drinks[i] = null;\r\n }\r\n }\r\n\r\n for (let i = 0; i < 5; i++) {\r\n if (abilities[i] && player.intelligenceLevel >= abilitySlotsLevelRequirementList[i + 1]) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let ability = new Ability(abilities[i], Number(abilityLevelInput.value), triggerMap[abilities[i]]);\r\n player.abilities[i] = ability;\r\n } else {\r\n player.abilities[i] = null;\r\n }\r\n }\r\n\r\n playersToSim.push(structuredClone(player));\r\n }\r\n }\r\n updateNextPlayer(currentPlayerTabId);\r\n updateState();\r\n updateUI();\r\n\r\n let maxPlayerCombatLevel = 1;\r\n for (let player of playersToSim) {\r\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\r\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\r\n }\r\n\r\n for (let player of playersToSim) {\r\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\r\n const maxDebuffOnLevelGap = 0.9;\r\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\r\n\r\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\r\n\r\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\r\n }\r\n else {\r\n player.debuffOnLevelGap = 0;\r\n }\r\n }\r\n\r\n let extra = {};\r\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\r\n extra.comExp = 0;\r\n if (document.getElementById(\"comExpToggle\").checked) {\r\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\r\n }\r\n extra.comDrop = 0;\r\n if (document.getElementById(\"comDropToggle\").checked) {\r\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\r\n }\r\n\r\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\r\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\r\n let simDungeonToggle = document.getElementById(\"simDungeonToggle\");\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let dungeonSelect = document.getElementById(\"selectDungeon\");\r\n let difficultySelect = document.getElementById(\"selectDifficulty\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let simulationTimeLimit = Number(simulationTimeInput.value) * ONE_HOUR;\r\n buttonStopSimulation.style.display = 'block';\r\n if (!simAllZonesToggle.checked && !simAllSoloToggle.checked) {\r\n let zoneHrid = zoneSelect.value;\r\n let difficultyTier = Number(difficultySelect.value);\r\n if (simDungeonToggle.checked) {\r\n zoneHrid = dungeonSelect.value;\r\n }\r\n let workerMessage = {\r\n type: \"start_simulation\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n simStartTime = Date.now();\r\n if (!worker) {\r\n worker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n }\r\n worker.onmessage = onWorkerMessage;\r\n worker.postMessage(workerMessage);\r\n } else {\r\n let targetHrids = {};\r\n\r\n if (simAllZonesToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1 &&\r\n document.getElementById(a.hrid)?.checked\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n if (simAllSoloToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount === 1 &&\r\n document.getElementById(a.hrid)?.checked\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n let simHrids = Object.values(targetHrids)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .map(action => {\r\n let result = [];\r\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\r\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\r\n }\r\n return result;\r\n })\r\n .flat();\r\n\r\n let workerMessage = {\r\n type: \"start_simulation_all_zones\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zones: simHrids,\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra: extra\r\n };\r\n simStartTime = Date.now();\r\n if (!multiWorker) {\r\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\r\n }\r\n multiWorker.onmessage = onMultiWorkerMessage;\r\n multiWorker.postMessage(workerMessage);\r\n }\r\n}\r\n\r\nfunction parsePlayerJson(playerJson, hrid) {\r\n let playerData = {\r\n hrid: hrid,\r\n food: [],\r\n drinks: [],\r\n abilities: [],\r\n ...playerJson.player,\r\n houseRooms: playerJson.houseRooms,\r\n };\r\n playerData.equipment = {};\r\n const triggerMap = playerJson.triggerMap;\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"main_hand\", \"two_hand\", \"charm\"].forEach((type) => {\r\n let currentEquipment = playerJson.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment){\r\n playerData.equipment[`/equipment_types/${type}`] = new Equipment(currentEquipment.itemHrid, currentEquipment.enhancementLevel);\r\n }\r\n });\r\n\r\n for (const foodHrid of playerJson.food[\"/action_types/combat\"]) {\r\n if (foodHrid.itemHrid === \"\") continue;\r\n const food = new Consumable(foodHrid.itemHrid, triggerMap[foodHrid.itemHrid]);\r\n playerData.food.push(food);\r\n }\r\n for (const drinkHrid of playerJson.drinks[\"/action_types/combat\"]) {\r\n if (drinkHrid.itemHrid === \"\") continue;\r\n const drink = new Consumable(drinkHrid.itemHrid, triggerMap[drinkHrid.itemHrid]);\r\n playerData.drinks.push(drink);\r\n }\r\n for (const ability of playerJson.abilities) {\r\n if (ability.abilityHrid === \"\") continue;\r\n const abilityLevel = Number(ability.level);\r\n const abilityHrid = ability.abilityHrid;\r\n if (abilityLevel > 0) {\r\n const abilityObj = new Ability(abilityHrid, abilityLevel, triggerMap[abilityHrid]);\r\n playerData.abilities.push(abilityObj);\r\n }\r\n }\r\n const player = Player.createFromDTO(playerData)\r\n player.updateCombatDetails();\r\n player.houseRooms = playerJson.houseRooms;\r\n player.achievements = playerJson.achievements ?? {};\r\n return player;\r\n}\r\n// read JSON file to simulate\r\ndocument.getElementById(\"buttonUploadJSONSimulate\").addEventListener(\"click\", (event) => {\r\n let extra = {};\r\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\r\n extra.comExp = 0;\r\n if (document.getElementById(\"comExpToggle\").checked) {\r\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\r\n }\r\n extra.comDrop = 0;\r\n if (document.getElementById(\"comDropToggle\").checked) {\r\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\r\n }\r\n\r\n let fileInput = document.getElementById(\"inputUploadJSONSimulation\");\r\n let file = fileInput.files[0];\r\n if (!file) {\r\n alert(\"Please select a file to upload.\");\r\n return;\r\n }\r\n\r\n let reader = new FileReader();\r\n reader.onload = function (event) {\r\n let fileContent = event.target.result;\r\n const jsonDataList = JSON.parse(fileContent);\r\n try {\r\n const simDataList = [];\r\n for (const key in jsonDataList) {\r\n if (jsonDataList[key].cases) {\r\n const cases = getProductCases(jsonDataList[key], jsonDataList[key].cases);\r\n simDataList.push(...cases);\r\n } else {\r\n simDataList.push(jsonDataList[key]);\r\n }\r\n }\r\n for (const key in simDataList) {\r\n const jsonData = simDataList[key];\r\n if (!jsonData || !jsonData.zone || !jsonData.players) {\r\n alert(\"Invalid JSON file format. Please ensure it contains a 'simulationResult' property.\");\r\n return;\r\n }\r\n const playersToSim = Object.values(jsonData.players).map(\r\n (player, index) => parsePlayerJson(player, `player${index + 1}`)\r\n );\r\n\r\n let maxPlayerCombatLevel = 1;\r\n for (let player of playersToSim) {\r\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\r\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\r\n }\r\n\r\n for (let player of playersToSim) {\r\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\r\n const maxDebuffOnLevelGap = 0.9;\r\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\r\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\r\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\r\n }\r\n else {\r\n player.debuffOnLevelGap = 0;\r\n }\r\n }\r\n\r\n const simulationTimeLimit = (jsonData.simulationTimeLimit || 24) * ONE_HOUR;\r\n const simName = jsonData.name || `Json ${key}`;\r\n const zoneHrid = jsonData.zone;\r\n if (zoneHrid === \"all\") {\r\n let targetHrids = {};\r\n\r\n if (simAllZonesToggle.checked) {\r\n Object.values(actionDetailMap)\r\n .filter(a =>\r\n a.type === \"/action_types/combat\" &&\r\n a.category !== \"/action_categories/combat/dungeons\" &&\r\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1\r\n )\r\n .forEach(a => { targetHrids[a.hrid] = a; });\r\n }\r\n\r\n let simHrids = Object.values(targetHrids)\r\n .sort((a, b) => a.sortIndex - b.sortIndex)\r\n .map(action => {\r\n let result = [];\r\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\r\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\r\n }\r\n return result;\r\n })\r\n .flat();\r\n\r\n let workerMessage = {\r\n simulationName: simName,\r\n type: \"start_simulation_all_zones\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zones: simHrids,\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \r\n worker.onmessage = mainWorkerOnMessage;\r\n worker.postMessage(workerMessage);\r\n customAlert(\"Simulation task Created\", \"info\")\r\n workerPool.push({\r\n workerId: workerMessage.workerId,\r\n worker: worker,\r\n });\r\n } else {\r\n let difficultyTier = jsonData.difficultyTier || 0;\r\n let workerMessage = {\r\n simulationName: simName,\r\n type: \"start_simulation\",\r\n workerId: Math.floor(Math.random() * 1e9).toString(),\r\n players: playersToSim,\r\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\r\n simulationTimeLimit: simulationTimeLimit,\r\n extra : extra\r\n };\r\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \r\n worker.onmessage = mainWorkerOnMessage;\r\n worker.postMessage(workerMessage);\r\n customAlert(\"Simulation task Created\", \"info\")\r\n workerPool.push({\r\n workerId: workerMessage.workerId,\r\n worker: worker,\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n // alert(\"Error parsing JSON file: \" + error.message);\r\n customAlert(\"Error parsing JSON file: \" + error.message, \"danger\");\r\n }\r\n }\r\n reader.readAsText(file);\r\n});\r\n\r\n\r\n// #endregion\r\n\r\n// #region WipeEvents\r\n\r\nfunction renderWipeEvents(simResult) {\r\n const selector = document.getElementById('wipeEventSelector');\r\n const logsContainer = document.getElementById('wipeLogsContainer');\r\n const waveBadge = document.getElementById('wipeWaveBadge');\r\n const timeInfo = document.getElementById('wipeTimeInfo');\r\n\r\n selector.innerHTML = '';\r\n logsContainer.innerHTML = '';\r\n\r\n if (!simResult.wipeEvents || simResult.wipeEvents.length === 0) {\r\n selector.innerHTML = ``;\r\n logsContainer.innerHTML = `
No Wipe Events Detected
`;\r\n waveBadge.textContent = '';\r\n timeInfo.textContent = '';\r\n return;\r\n }\r\n\r\n simResult.wipeEvents.forEach((event, index) => {\r\n const wave = event.wave || '?';\r\n // const time = (event.simulationTime / 1e9).toFixed(2);\r\n // const timestamp = new Date(event.timestamp).toLocaleTimeString();\r\n\r\n const option = document.createElement('option');\r\n option.value = index;\r\n option.textContent = `#${index + 1} - 波次: ${wave}`;\r\n selector.appendChild(option);\r\n });\r\n\r\n selector.value = 0;\r\n renderSelectedWipeEvent(0, simResult);\r\n\r\n selector.addEventListener('change', () => {\r\n renderSelectedWipeEvent(selector.value, simResult);\r\n });\r\n}\r\n\r\n// 渲染选中的团灭事件\r\nfunction renderSelectedWipeEvent(index, simResult) {\r\n const logsContainer = document.getElementById('wipeLogsContainer');\r\n const waveBadge = document.getElementById('wipeWaveBadge');\r\n const timeInfo = document.getElementById('wipeTimeInfo');\r\n\r\n logsContainer.innerHTML = '';\r\n\r\n if (index < 0 || index >= simResult.wipeEvents.length) {\r\n logsContainer.innerHTML = `
No Wipe Events
`;\r\n waveBadge.textContent = '';\r\n timeInfo.textContent = '';\r\n return;\r\n }\r\n\r\n const wipeEvent = simResult.wipeEvents[index];\r\n const wave = wipeEvent.wave || '?';\r\n const time = (wipeEvent.simulationTime / 1e9).toFixed(2);\r\n const timestamp = new Date(wipeEvent.timestamp).toLocaleString();\r\n\r\n waveBadge.textContent = `波次: ${wave}`;\r\n timeInfo.textContent = `模拟时间: ${time}s | 记录时间: ${timestamp}`;\r\n\r\n const logsByTime = groupLogsByTime(wipeEvent.logs);\r\n\r\n const baseTime = logsByTime.length > 0 ? logsByTime[0].time : 0;\r\n\r\n logsByTime.forEach(group => {\r\n const timeGroupElement = document.createElement('div');\r\n timeGroupElement.className = 'log-time-group';\r\n\r\n const relativeTime = (group.time - baseTime) / 1e9;\r\n\r\n // 时间标题\r\n const timeHeader = document.createElement('div');\r\n timeHeader.className = 'log-time-header';\r\n timeHeader.textContent = `[${relativeTime.toFixed(2)}s] [Wave#${group.wave}]`;\r\n timeGroupElement.appendChild(timeHeader);\r\n\r\n // 事件列表\r\n const eventsList = document.createElement('div');\r\n eventsList.className = 'log-events';\r\n\r\n const damagedPlayers = new Set();\r\n\r\n group.logs.forEach(log => {\r\n const eventElement = document.createElement('div');\r\n eventElement.className = 'log-event';\r\n\r\n damagedPlayers.add(log.target);\r\n\r\n const sourceSpan = document.createElement('span');\r\n sourceSpan.className = 'log-source';\r\n if (log.ability === \"damageOverTime\") {\r\n sourceSpan.textContent = log.target;\r\n } else if(log.source == 'UNKNOWN_SOURCE') {\r\n sourceSpan.textContent = 'UNKNOWN';\r\n } else {\r\n sourceSpan.setAttribute('data-i18n', `monsterNames.${log.source}`);\r\n sourceSpan.textContent = log.source;\r\n }\r\n\r\n const castSpan = document.createElement('span');\r\n castSpan.className = 'log-cast';\r\n castSpan.setAttribute('data-i18n', `common:cast`);\r\n castSpan.textContent = ' cast ';\r\n\r\n const abilitySpan = document.createElement('span');\r\n abilitySpan.className = 'log-ability';\r\n if (log.ability === \"autoAttack\") {\r\n abilitySpan.setAttribute('data-i18n', 'combatUnit.autoAttack');\r\n abilitySpan.textContent = 'Auto Attack';\r\n } else if (log.ability === \"physicalThorns\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.physicalThorns`);\r\n abilitySpan.textContent = 'Physical Thorns';\r\n } else if (log.ability === \"elementalThorns\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.elementalThorns`);\r\n abilitySpan.textContent = 'Elemental Thorns';\r\n } else if (log.ability === \"retaliation\") {\r\n abilitySpan.setAttribute('data-i18n', `combatStats.retaliation`);\r\n abilitySpan.textContent = 'Retaliation';\r\n } else if (log.ability === \"damageOverTime\") {\r\n abilitySpan.setAttribute('data-i18n', `common:simulationResults.damageOverTime`);\r\n abilitySpan.textContent = 'Damage Over Time';\r\n } else {\r\n abilitySpan.setAttribute('data-i18n', `abilityNames.${log.ability}`);\r\n abilitySpan.textContent = log.ability;\r\n }\r\n\r\n const toSpan = document.createElement('span');\r\n toSpan.className = 'log-to';\r\n toSpan.setAttribute('data-i18n', `common:to`);\r\n toSpan.textContent = ' to ';\r\n\r\n const targetSpan = document.createElement('span');\r\n targetSpan.className = 'log-target';\r\n targetSpan.textContent = log.target;\r\n\r\n const dealDamageSpan = document.createElement('span');\r\n dealDamageSpan.className = 'log-deal-damage';\r\n dealDamageSpan.setAttribute('data-i18n', `common:dealDamage`);\r\n dealDamageSpan.textContent = ' deal damage ';\r\n\r\n const damageDoneSpan = document.createElement('span');\r\n damageDoneSpan.className = 'log-damage-done';\r\n damageDoneSpan.textContent = log.damage;\r\n if (log.isCrit) {\r\n damageDoneSpan.style.fontWeight = 'bold';\r\n damageDoneSpan.textContent += '!!!';\r\n }\r\n\r\n eventElement.appendChild(sourceSpan);\r\n eventElement.appendChild(castSpan);\r\n eventElement.appendChild(abilitySpan);\r\n eventElement.appendChild(toSpan);\r\n eventElement.appendChild(targetSpan);\r\n eventElement.appendChild(dealDamageSpan);\r\n eventElement.appendChild(damageDoneSpan);\r\n eventElement.appendChild(document.createTextNode(` , HP ${log.beforeHp} → ${log.afterHp}`));\r\n\r\n eventsList.appendChild(eventElement);\r\n });\r\n\r\n timeGroupElement.appendChild(eventsList);\r\n\r\n const lastLog = group.logs[group.logs.length - 1];\r\n const playersHpElement = document.createElement('div');\r\n\r\n const playerHpTitle = document.createElement('span');\r\n playerHpTitle.className = 'log-players-hp';\r\n playerHpTitle.setAttribute('data-i18n', `common:playersHp`);\r\n playerHpTitle.textContent = 'Players HP: ';\r\n playersHpElement.appendChild(playerHpTitle);\r\n\r\n lastLog.playersHp.forEach((player, idx) => {\r\n const playerElement = document.createElement('span');\r\n playerElement.className = 'log-player-hp';\r\n playerElement.textContent = `${player.hrid}: ${player.current}/${player.max}`;\r\n\r\n if (player.current <= 0) {\r\n playerElement.style.color = darkModeToggle.checked ? '#FF6347' : '#CC0000';\r\n } else if (damagedPlayers.has(player.hrid)) {\r\n playerElement.style.color = darkModeToggle.checked ? '#00BFFF' : '#007BFF';\r\n }\r\n\r\n if (idx > 0) {\r\n playersHpElement.appendChild(document.createTextNode(' | '));\r\n }\r\n playersHpElement.appendChild(playerElement);\r\n });\r\n const spacer = document.createElement('div');\r\n spacer.style.height = '15px';\r\n logsContainer.appendChild(spacer);\r\n timeGroupElement.appendChild(playersHpElement);\r\n logsContainer.appendChild(timeGroupElement);\r\n });\r\n\r\n // 更新汉化\r\n updateContent()\r\n}\r\n\r\n// 按时间分组日志\r\nfunction groupLogsByTime(logs) {\r\n const groups = [];\r\n let currentGroup = null;\r\n\r\n logs.forEach(log => {\r\n if (!currentGroup || currentGroup.time !== log.time) {\r\n currentGroup = {\r\n time: log.time,\r\n wave: log.wave,\r\n logs: [log]\r\n };\r\n groups.push(currentGroup);\r\n } else {\r\n currentGroup.logs.push(log);\r\n }\r\n });\r\n\r\n groups.forEach(group => {\r\n let hpMap = {};\r\n if (group.logs.length > 0) {\r\n group.logs[0].playersHp.forEach(p => {\r\n hpMap[p.hrid] = { current: p.current, max: p.max };\r\n });\r\n }\r\n group.logs.forEach(log => {\r\n if (hpMap[log.target]) {\r\n hpMap[log.target].current = log.afterHp;\r\n }\r\n });\r\n group.logs.forEach(log => {\r\n log.playersHp = Object.entries(hpMap).map(([hrid, val]) => ({\r\n hrid,\r\n current: val.current,\r\n max: val.max\r\n }));\r\n });\r\n });\r\n\r\n return groups;\r\n}\r\n\r\n// #endregion\r\n\r\n\r\n// #region Equipment Sets\r\n\r\nfunction initEquipmentSetsModal() {\r\n let equipmentSetsModal = document.getElementById(\"equipmentSetsModal\");\r\n equipmentSetsModal.addEventListener(\"show.bs.modal\", equipmentSetsModalShownHandler);\r\n\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n equipmentSetNameInput.addEventListener(\"input\", (event) => equipmentSetNameChangedHandler(event));\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.addEventListener(\"click\", createNewEquipmentSetHandler);\r\n}\r\n\r\nfunction equipmentSetsModalShownHandler() {\r\n resetNewEquipmentSetControls();\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction resetNewEquipmentSetControls() {\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n equipmentSetNameInput.value = \"\";\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.disabled = true;\r\n}\r\n\r\nfunction updateEquipmentSetList() {\r\n let newChildren = [];\r\n let equipmentSets = loadEquipmentSets();\r\n\r\n for (const equipmentSetName of Object.keys(equipmentSets)) {\r\n let row = createElement(\"div\", \"row mb-2\");\r\n\r\n let nameCol = createElement(\"div\", \"col align-self-center\", equipmentSetName);\r\n row.appendChild(nameCol);\r\n\r\n let loadButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let loadButton = createElement(\"button\", \"btn btn-primary\", \"Load\");\r\n loadButton.setAttribute(\"data-i18n\", \"common:controls.load\");\r\n loadButton.setAttribute(\"type\", \"button\");\r\n loadButton.addEventListener(\"click\", (_) => loadEquipmentSetHandler(equipmentSetName));\r\n loadButtonCol.appendChild(loadButton);\r\n row.appendChild(loadButtonCol);\r\n\r\n let saveButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let saveButton = createElement(\"button\", \"btn btn-primary\", \"Save\");\r\n saveButton.setAttribute(\"data-i18n\", \"common:controls.save\");\r\n saveButton.setAttribute(\"type\", \"button\");\r\n saveButton.addEventListener(\"click\", (_) => updateEquipmentSetHandler(equipmentSetName));\r\n saveButtonCol.appendChild(saveButton);\r\n row.appendChild(saveButtonCol);\r\n\r\n let deleteButtonCol = createElement(\"div\", \"col-md-auto\");\r\n let deleteButton = createElement(\"button\", \"btn btn-danger\", \"Delete\");\r\n deleteButton.setAttribute(\"data-i18n\", \"common:controls.delete\");\r\n deleteButton.setAttribute(\"type\", \"button\");\r\n deleteButton.addEventListener(\"click\", (_) => deleteEquipmentSetHandler(equipmentSetName));\r\n deleteButtonCol.appendChild(deleteButton);\r\n row.appendChild(deleteButtonCol);\r\n\r\n newChildren.push(row);\r\n }\r\n\r\n let equipmentSetList = document.getElementById(\"equipmentSetList\");\r\n equipmentSetList.replaceChildren(...newChildren);\r\n\r\n updateContent();\r\n}\r\n\r\nfunction equipmentSetNameChangedHandler(event) {\r\n let invalid = false;\r\n\r\n if (event.target.value.length == 0) {\r\n invalid = true;\r\n }\r\n\r\n let equipmentSets = loadEquipmentSets();\r\n if (equipmentSets[event.target.value]) {\r\n invalid = true;\r\n }\r\n\r\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\r\n createEquipmentSetButton.disabled = invalid;\r\n}\r\n\r\nfunction createNewEquipmentSetHandler() {\r\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\r\n let equipmentSetName = equipmentSetNameInput.value;\r\n\r\n let equipmentSet = getEquipmentSetFromUI();\r\n let equipmentSets = loadEquipmentSets();\r\n equipmentSets[equipmentSetName] = equipmentSet;\r\n saveEquipmentSets(equipmentSets);\r\n\r\n resetNewEquipmentSetControls();\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction loadEquipmentSetHandler(name) {\r\n let equipmentSets = loadEquipmentSets();\r\n loadEquipmentSetIntoUI(equipmentSets[name]);\r\n}\r\n\r\nfunction updateEquipmentSetHandler(name) {\r\n let equipmentSet = getEquipmentSetFromUI();\r\n let equipmentSets = loadEquipmentSets();\r\n equipmentSets[name] = equipmentSet;\r\n saveEquipmentSets(equipmentSets);\r\n}\r\n\r\nfunction deleteEquipmentSetHandler(name) {\r\n let equipmentSets = loadEquipmentSets();\r\n delete equipmentSets[name];\r\n saveEquipmentSets(equipmentSets);\r\n\r\n updateEquipmentSetList();\r\n}\r\n\r\nfunction loadEquipmentSets() {\r\n return JSON.parse(localStorage.getItem(\"equipmentSets\")) ?? {};\r\n}\r\n\r\nfunction saveEquipmentSets(equipmentSets) {\r\n localStorage.setItem(\"equipmentSets\", JSON.stringify(equipmentSets));\r\n}\r\n\r\nfunction getEquipmentSetFromUI() {\r\n let equipmentSet = {\r\n levels: {},\r\n equipment: {},\r\n food: {},\r\n drinks: {},\r\n abilities: {},\r\n triggerMap: {},\r\n houseRooms: {},\r\n achievements: {},\r\n };\r\n\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n equipmentSet.levels[skill] = Number(levelInput.value);\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n\r\n equipmentSet.equipment[type] = {\r\n equipment: equipmentSelect.value,\r\n enhancementLevel: Number(enhancementLevelInput.value),\r\n };\r\n });\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n equipmentSet.food[i] = foodSelect.value;\r\n }\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n equipmentSet.drinks[i] = drinkSelect.value;\r\n }\r\n\r\n for (let i = 0; i < 5; i++) {\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n equipmentSet.abilities[i] = {\r\n ability: abilitySelect.value,\r\n level: Number(abilityLevelInput.value),\r\n };\r\n }\r\n\r\n equipmentSet.triggerMap = triggerMap;\r\n\r\n equipmentSet.houseRooms = player.houseRooms;\r\n equipmentSet.achievements = player.achievements;\r\n\r\n return equipmentSet;\r\n}\r\n\r\nfunction fixTriggerMap(triggerMap) {\r\n let delKeys = []\r\n for (const key of Object.keys(triggerMap)) {\r\n let err = false;\r\n if (null == triggerMap[key]) {\r\n triggerMap[key] = [];\r\n }\r\n for (const trigger of triggerMap[key]) {\r\n if (!combatTriggerConditionDetailMap[trigger.conditionHrid]) {\r\n err = true;\r\n break;\r\n }\r\n }\r\n if (err) {\r\n delKeys.push(key);\r\n }\r\n }\r\n for (const key of delKeys) {\r\n delete triggerMap[key];\r\n }\r\n}\r\n\r\nfunction loadEquipmentSetIntoUI(equipmentSet) {\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !equipmentSet.levels[\"meleeLevel\"] && equipmentSet.levels[\"powerLevel\"]) {\r\n equipmentSet.levels[\"meleeLevel\"] = equipmentSet.levels[\"powerLevel\"];\r\n }\r\n levelInput.value = equipmentSet.levels[skill] ?? 1;\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n\r\n let currentEquipment = equipmentSet.equipment[type];\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.equipment;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n foodSelect.value = equipmentSet.food[i];\r\n }\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n drinkSelect.value = equipmentSet.drinks[i].replace(\"power\", \"melee\");\r\n }\r\n\r\n let hasSpecial = false;\r\n if (equipmentSet.abilities && Object.keys(equipmentSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n equipmentSet.abilities[i].ability == \"/abilities/aqua_aura\" ||\r\n equipmentSet.abilities[i].ability == \"/abilities/flame_aura\" ||\r\n equipmentSet.abilities[i].ability == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n equipmentSet.abilities[i].ability = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (equipmentSet.abilities[i].ability == \"/abilities/arcane_reflection\") {\r\n equipmentSet.abilities[i].ability = \"/abilities/retribution\";\r\n }\r\n\r\n abilitySelect.value = equipmentSet.abilities[i].ability;\r\n abilityLevelInput.value = equipmentSet.abilities[i].level;\r\n }\r\n\r\n triggerMap = equipmentSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n\r\n if (equipmentSet.houseRooms) {\r\n for (const room in equipmentSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (equipmentSet.houseRooms[room]) {\r\n field.value = equipmentSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = equipmentSet.houseRooms;\r\n } else {\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n\r\n if (equipmentSet.achievements) {\r\n for (const achievement in equipmentSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (equipmentSet.achievements[achievement]) {\r\n field.checked = true;\r\n } else {\r\n field.checked = false;\r\n }\r\n player.achievements[achievement] = field.checked;\r\n }\r\n } else {\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n refreshAchievementStatics();\r\n\r\n updateState();\r\n updateUI();\r\n\r\n updateContent();\r\n}\r\n\r\n// #endregion\r\n\r\n// #region Error Handling\r\n\r\nfunction initErrorHandling() {\r\n window.addEventListener(\"error\", (event) => {\r\n showErrorModal(event.message);\r\n });\r\n\r\n let copyErrorButton = document.getElementById(\"buttonCopyError\");\r\n copyErrorButton.addEventListener(\"click\", (event) => {\r\n let errorInput = document.getElementById(\"inputError\");\r\n navigator.clipboard.writeText(errorInput.value);\r\n });\r\n}\r\n\r\nfunction initImportExportModal() {\r\n let exportSetButton = document.getElementById(\"buttonExportSet\");\r\n exportSetButton.addEventListener(\"click\", (event) => {\r\n savePreviousPlayer(currentPlayerTabId);\r\n const activeTab = document.querySelector('#importTab .nav-link.active');\r\n if (activeTab.id === 'group-combat-tab') {\r\n doGroupExport();\r\n } else if (activeTab.id === 'solo-tab') {\r\n doSoloExport();\r\n }\r\n });\r\n\r\n let importSetButton = document.getElementById(\"buttonImportSet\");\r\n importSetButton.addEventListener(\"click\", (event) => {\r\n const activeTab = document.querySelector('#importTab .nav-link.active');\r\n if (activeTab.id === 'group-combat-tab') {\r\n doGroupImport();\r\n } else if (activeTab.id === 'solo-tab') {\r\n doSoloImport();\r\n }\r\n updateState();\r\n updateUI();\r\n resetImportInputs();\r\n });\r\n}\r\n\r\nfunction resetImportInputs() {\r\n document.getElementById('inputSetGroupCombatAll').value = '';\r\n document.getElementById('inputSetGroupCombatplayer1').value = '';\r\n document.getElementById('inputSetGroupCombatplayer2').value = '';\r\n document.getElementById('inputSetGroupCombatplayer3').value = '';\r\n document.getElementById('inputSetGroupCombatplayer4').value = '';\r\n document.getElementById('inputSetGroupCombatplayer5').value = '';\r\n document.getElementById('inputSetSolo').value = '';\r\n}\r\n\r\nfunction doGroupExport() {\r\n try {\r\n navigator.clipboard.writeText(JSON.stringify(playerDataMap)).then(() => alert(\"Current Group has been copied to clipboard.\"));\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction doSoloExport() {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let equipmentArray = [];\r\n for (const item in player.equipment) {\r\n if (player.equipment[item] != null) {\r\n equipmentArray.push({\r\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\r\n \"itemHrid\": player.equipment[item].hrid,\r\n \"enhancementLevel\": player.equipment[item].enhancementLevel\r\n });\r\n }\r\n }\r\n let playerArray = {\r\n \"attackLevel\": player.attackLevel,\r\n \"magicLevel\": player.magicLevel,\r\n \"meleeLevel\": player.meleeLevel,\r\n \"rangedLevel\": player.rangedLevel,\r\n \"defenseLevel\": player.defenseLevel,\r\n \"staminaLevel\": player.staminaLevel,\r\n \"intelligenceLevel\": player.intelligenceLevel,\r\n \"equipment\": equipmentArray\r\n };\r\n let abilitiesArray = [];\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let abilityName = document.getElementById(\"selectAbility_\" + i);\r\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\r\n }\r\n let drinksArray = [];\r\n for (let i = 0; i < drinks?.length; i++) {\r\n drinksArray.push({ \"itemHrid\": drinks[i] });\r\n }\r\n let foodArray = [];\r\n for (let i = 0; i < food?.length; i++) {\r\n foodArray.push({ \"itemHrid\": food[i] });\r\n }\r\n let state = {\r\n player: playerArray,\r\n food: { \"/action_types/combat\": foodArray },\r\n drinks: { \"/action_types/combat\": drinksArray },\r\n abilities: abilitiesArray,\r\n triggerMap: triggerMap,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n houseRooms: player.houseRooms,\r\n achievements: player.achievements\r\n };\r\n try {\r\n navigator.clipboard.writeText(JSON.stringify(state)).then(() => alert(\"Current set has been copied to clipboard.\"));\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction setPlayerData(playerId, inputElementId) {\r\n const inputElement = document.getElementById(inputElementId);\r\n const value = inputElement ? inputElement.value.trim() : \"\";\r\n\r\n // Only set the value in the map if it's not null, undefined, or empty\r\n if (value) {\r\n playerDataMap[playerId] = value;\r\n return true;\r\n }\r\n return false;\r\n}\r\n\r\nfunction doGroupImport() {\r\n let needUpdateCurrentTab = false;\r\n const value = document.getElementById(\"inputSetGroupCombatAll\")?.value || \"\";\r\n if (!value.trim()) {\r\n for (let i of ['1', '2', '3', '4', '5']) {\r\n if (setPlayerData(i, \"inputSetGroupCombatplayer\" + i) && currentPlayerTabId == i) {\r\n needUpdateCurrentTab = true;\r\n }\r\n }\r\n } else {\r\n playerDataMap = JSON.parse(value);\r\n needUpdateCurrentTab = true;\r\n }\r\n\r\n if (needUpdateCurrentTab) {\r\n updateNextPlayer(currentPlayerTabId);\r\n }\r\n}\r\n\r\nfunction doSoloImport() {\r\n let importSet = document.getElementById(\"inputSetSolo\").value;\r\n importSet = JSON.parse(importSet);\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\r\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\r\n }\r\n levelInput.value = importSet.player[skill + \"Level\"];\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.itemHrid;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\r\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\r\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\r\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\r\n if (mainhandWeapon !== undefined) {\r\n weaponSelect.value = mainhandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\r\n } else if (twohandWeapon !== undefined) {\r\n weaponSelect.value = twohandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\r\n } else {\r\n weaponSelect.value = \"\";\r\n weaponEnhancementLevelInput.value = 0;\r\n }\r\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\r\n importSet.food = importSet.food[\"/action_types/combat\"];\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n if (importSet.drinks[i] != null) {\r\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\r\n } else {\r\n drinkSelect.value = \"\";\r\n }\r\n if (importSet.food[i] != null) {\r\n foodSelect.value = importSet.food[i].itemHrid;\r\n } else {\r\n foodSelect.value = \"\";\r\n }\r\n }\r\n\r\n let hasSpecial = false;\r\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\r\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\r\n }\r\n\r\n if (importSet.abilities[i] != null) {\r\n abilitySelect.value = importSet.abilities[i].abilityHrid;\r\n abilityLevelInput.value = String(importSet.abilities[i].level);\r\n } else {\r\n abilitySelect.value = \"\";\r\n abilityLevelInput.value = \"1\";\r\n }\r\n }\r\n\r\n if (importSet.triggerMap) {\r\n triggerMap = importSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n }\r\n\r\n if (importSet.houseRooms) {\r\n for (const room in importSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (importSet.houseRooms[room]) {\r\n field.value = importSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = importSet.houseRooms;\r\n } else {\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n\r\n if (importSet.achievements) {\r\n for (const achievement in importSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (importSet.achievements[achievement]) {\r\n field.checked = true;\r\n } else {\r\n field.checked = false;\r\n }\r\n player.achievements[achievement] = field.checked;\r\n }\r\n } else {\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n refreshAchievementStatics();\r\n\r\n if (\"zone\" in importSet) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n zoneSelect.value = importSet[\"zone\"];\r\n }\r\n\r\n if (\"simulationTime\" in importSet) {\r\n let simulationDuration = document.getElementById(\"inputSimulationTime\");\r\n simulationDuration.value = importSet[\"simulationTime\"];\r\n }\r\n}\r\n\r\nfunction savePreviousPlayer(playerId) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n let equipmentArray = [];\r\n for (const item in player.equipment) {\r\n if (player.equipment[item] != null) {\r\n equipmentArray.push({\r\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\r\n \"itemHrid\": player.equipment[item].hrid,\r\n \"enhancementLevel\": player.equipment[item].enhancementLevel\r\n });\r\n }\r\n }\r\n let playerArray = {\r\n \"attackLevel\": player.attackLevel,\r\n \"magicLevel\": player.magicLevel,\r\n \"meleeLevel\": player.meleeLevel,\r\n \"rangedLevel\": player.rangedLevel,\r\n \"defenseLevel\": player.defenseLevel,\r\n \"staminaLevel\": player.staminaLevel,\r\n \"intelligenceLevel\": player.intelligenceLevel,\r\n \"equipment\": equipmentArray\r\n };\r\n let abilitiesArray = [];\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n let abilityName = document.getElementById(\"selectAbility_\" + i);\r\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\r\n }\r\n let drinksArray = [];\r\n for (let i = 0; i < drinks?.length; i++) {\r\n drinksArray.push({ \"itemHrid\": drinks[i] });\r\n }\r\n let foodArray = [];\r\n for (let i = 0; i < food?.length; i++) {\r\n foodArray.push({ \"itemHrid\": food[i] });\r\n }\r\n let state = {\r\n player: playerArray,\r\n food: { \"/action_types/combat\": foodArray },\r\n drinks: { \"/action_types/combat\": drinksArray },\r\n abilities: abilitiesArray,\r\n triggerMap: triggerMap,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n houseRooms: player.houseRooms,\r\n achievements: player.achievements\r\n };\r\n try {\r\n playerDataMap[playerId] = JSON.stringify(state);\r\n } catch (err) {\r\n alert('Error copying to clipboard: ' + err);\r\n }\r\n}\r\n\r\nfunction updateNextPlayer(currentPlayerNumber) {\r\n let playerImportData = playerDataMap[currentPlayerNumber];\r\n let importSet = JSON.parse(playerImportData);\r\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\r\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\r\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\r\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\r\n }\r\n levelInput.value = importSet.player[skill + \"Level\"];\r\n });\r\n\r\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\r\n\r\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\r\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\r\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\r\n if (currentEquipment !== undefined) {\r\n equipmentSelect.value = currentEquipment.itemHrid;\r\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\r\n } else {\r\n equipmentSelect.value = \"\";\r\n enhancementLevelInput.value = 0;\r\n }\r\n });\r\n\r\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\r\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\r\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\r\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\r\n if (mainhandWeapon !== undefined) {\r\n weaponSelect.value = mainhandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\r\n } else if (twohandWeapon !== undefined) {\r\n weaponSelect.value = twohandWeapon.itemHrid;\r\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\r\n } else {\r\n weaponSelect.value = \"\";\r\n weaponEnhancementLevelInput.value = 0;\r\n }\r\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\r\n importSet.food = importSet.food[\"/action_types/combat\"];\r\n for (let i = 0; i < 3; i++) {\r\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\r\n let foodSelect = document.getElementById(\"selectFood_\" + i);\r\n if (importSet.drinks[i] != null) {\r\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\r\n } else {\r\n drinkSelect.value = \"\";\r\n }\r\n if (importSet.food[i] != null) {\r\n foodSelect.value = importSet.food[i].itemHrid;\r\n } else {\r\n foodSelect.value = \"\";\r\n }\r\n }\r\n\r\n let hasSpecial = false;\r\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\r\n hasSpecial = true;\r\n }\r\n\r\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\r\n let abilitySlot = hasSpecial ? i : (i + 1);\r\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\r\n\r\n if (hasSpecial && i == 0 && (\r\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\r\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\r\n )\r\n ) {\r\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\r\n }\r\n\r\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\r\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\r\n }\r\n\r\n if (importSet.abilities[i] != null) {\r\n abilitySelect.value = importSet.abilities[i].abilityHrid;\r\n abilityLevelInput.value = String(importSet.abilities[i].level);\r\n } else {\r\n abilitySelect.value = \"\";\r\n abilityLevelInput.value = \"1\";\r\n }\r\n }\r\n\r\n if (importSet.triggerMap) {\r\n triggerMap = importSet.triggerMap;\r\n fixTriggerMap(triggerMap);\r\n }\r\n\r\n { // reset all houseRooms\r\n let houseRooms = Object.values(houseRoomDetailMap);\r\n for (const room of Object.values(houseRooms)) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\r\n field.value = '';\r\n player.houseRooms[room.hrid] = 0;\r\n }\r\n }\r\n if (importSet.houseRooms) {\r\n for (const room in importSet.houseRooms) {\r\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\r\n if (importSet.houseRooms[room]) {\r\n field.value = importSet.houseRooms[room];\r\n } else {\r\n field.value = '';\r\n }\r\n }\r\n player.houseRooms = importSet.houseRooms;\r\n }\r\n\r\n { // reset all achievements\r\n let achievements = Object.values(achievementDetailMap);\r\n for (const detail of Object.values(achievements)) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\r\n field.checked = false;\r\n player.achievements[detail.hrid] = false;\r\n }\r\n }\r\n if (importSet.achievements) {\r\n for (const achievement in importSet.achievements) {\r\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\r\n if (importSet.achievements[achievement]) {\r\n field.checked = true;\r\n player.achievements[achievement] = true;\r\n } else {\r\n field.checked = false;\r\n player.achievements[achievement] = false;\r\n }\r\n }\r\n }\r\n refreshAchievementStatics();\r\n}\r\n\r\nfunction showErrorModal(error) {\r\n let zoneSelect = document.getElementById(\"selectZone\");\r\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\r\n\r\n let state = {\r\n error: error,\r\n player: player,\r\n food: food,\r\n drinks: drinks,\r\n abilities: abilities,\r\n triggerMap: triggerMap,\r\n modalTriggers: modalTriggers,\r\n zone: zoneSelect.value,\r\n simulationTime: simulationTimeInput.value,\r\n };\r\n\r\n for (let i = 0; i < 5; i++) {\r\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\r\n state[\"abilityLevel\" + i] = abilityLevelInput.value;\r\n }\r\n\r\n let errorInput = document.getElementById(\"inputError\");\r\n errorInput.value = JSON.stringify(state);\r\n\r\n let errorModal = new bootstrap.Modal(document.getElementById(\"errorModal\"));\r\n errorModal.show();\r\n}\r\n\r\nwindow.prices;\r\n\r\nasync function fetchPrices() {\r\n let response = null;\r\n try {\r\n response = await fetch('https://www.milkywayidle.com/game_data/marketplace.json'\r\n , {\r\n mode: 'cors'\r\n }\r\n );\r\n if (!response.ok) {\r\n console.log('Error fetching prices');\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n\r\n if (response == null) {\r\n try {\r\n response = await fetch('https://www.milkywayidlecn.com/game_data/marketplace.json'\r\n , {\r\n mode: 'cors'\r\n }\r\n );\r\n if (!response.ok) {\r\n console.log('Error fetching prices');\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n }\r\n\r\n if (!response || !response.ok) {\r\n return;\r\n }\r\n\r\n try {\r\n\r\n let btn = document.querySelector('#buttonGetPrices');\r\n btn.style.backgroundColor = 'green';\r\n\r\n const pricesJson = await response.json();\r\n\r\n const priceTmp = pricesJson['marketData'];\r\n window.prices = {};\r\n for (const item in itemDetailMap) {\r\n const hrid = itemDetailMap[item].hrid;\r\n if (hrid in priceTmp) {\r\n window.prices[hrid] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (priceTmp[hrid]['0']) {\r\n window.prices[hrid].ask = priceTmp[hrid]['0'].a;\r\n window.prices[hrid].bid = priceTmp[hrid]['0'].b;\r\n }\r\n }\r\n } \r\n\r\n window.prices[\"/items/coin\"] = { \"ask\": 1, \"bid\": 1, \"vendor\": 1 };\r\n\r\n window.prices[\"/items/small_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n window.prices[\"/items/medium_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n window.prices[\"/items/large_treasure_chest\"] = {\r\n \"ask\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"bid\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n \"vendor\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\r\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\r\n }).reduce((a, b) => a + b, 0),\r\n };\r\n\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n}\r\n\r\ndocument.getElementById(\"buttonGetPrices\").onclick = async () => {\r\n await fetchPrices();\r\n};\r\n\r\ndocument.addEventListener(\"input\", (e) => {\r\n let element = e.target;\r\n if (element.tagName == \"TD\" && element.parentNode.parentNode.parentNode.classList.value.includes('profit-table')) {\r\n let tableId = element.parentNode.parentNode.parentNode.id;\r\n let row = element.parentNode.querySelectorAll('td');\r\n let item = row[0].getAttribute('data-i18n').split('.')[1];\r\n let newPrice = element.innerText;\r\n\r\n let revenueSetting = document.getElementById('selectPrices_drops').value;\r\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\r\n\r\n let expensesDifference = 0;\r\n let revenueDifference = 0;\r\n let noRngRevenueDifference = 0;\r\n\r\n if (tableId == 'expensesTable') {\r\n expensesDifference = updateTable('expensesTable', item, newPrice);\r\n if (revenueSetting == expensesSetting) {\r\n revenueDifference = updateTable('revenueTable', item, newPrice);\r\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\r\n }\r\n if (window.prices) {\r\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (expensesSetting == 'bid') {\r\n window.prices[item]['bid'] = newPrice;\r\n } else {\r\n window.prices[item]['ask'] = newPrice;\r\n }\r\n }\r\n } else {\r\n revenueDifference = updateTable('revenueTable', item, newPrice);\r\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\r\n if (revenueSetting == expensesSetting) {\r\n expensesDifference = updateTable('expensesTable', item, newPrice);\r\n }\r\n if (window.prices) {\r\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\r\n if (revenueSetting == 'bid') {\r\n window.prices[item]['bid'] = newPrice;\r\n } else {\r\n window.prices[item]['ask'] = newPrice;\r\n }\r\n }\r\n }\r\n\r\n window.expenses += expensesDifference;\r\n document.getElementById('expensesSpan').innerText = window.expenses.toLocaleString();\r\n window.revenue += revenueDifference;\r\n document.getElementById('revenueSpan').innerText = window.revenue.toLocaleString();\r\n window.noRngRevenue += noRngRevenueDifference;\r\n document.getElementById('noRngRevenueSpan').innerText = window.noRngRevenue.toLocaleString();\r\n\r\n window.profit = window.revenue - window.expenses;\r\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\r\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\r\n window.noRngProfit = window.noRngRevenue - window.expenses;\r\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\r\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\r\n }\r\n});\r\n\r\nfunction updateTable(tableId, item, price) {\r\n let row = document.querySelector('#' + tableId + ' .' + CSS.escape(item));\r\n if (row == null) {\r\n return 0;\r\n }\r\n\r\n row = row.querySelectorAll('td');\r\n let priceTd = row[1];\r\n let amountTd = row[2];\r\n let totalTd = row[3];\r\n let oldTotal = totalTd.innerText;\r\n let newTotal = price * amountTd.innerText;\r\n\r\n if (priceTd.innerText != price) {\r\n priceTd.innerText = price;\r\n }\r\n totalTd.innerText = newTotal;\r\n\r\n return newTotal - oldTotal;\r\n}\r\n\r\n// #endregion\r\n\r\nfunction initPatchNotes() {\r\n const patchNotesRows = document.getElementById(\"patchNotes\");\r\n for (const pn in patchNote) {\r\n const patchNoteContainer = document.createElement(\"div\");\r\n patchNotesRows.setAttribute('class', 'col-12 mb-4');\r\n\r\n const patchNoteElement = document.createElement(\"h6\");\r\n patchNoteElement.innerHTML = pn;\r\n const patchNoteList = document.createElement(\"ul\");\r\n for (const note of patchNote[pn]) {\r\n const noteElement = document.createElement(\"li\");\r\n noteElement.innerHTML = note;\r\n patchNoteList.appendChild(noteElement);\r\n }\r\n patchNoteContainer.appendChild(patchNoteElement);\r\n patchNoteContainer.appendChild(patchNoteList);\r\n\r\n patchNotesRows.appendChild(patchNoteContainer);\r\n }\r\n}\r\n\r\nfunction initExtraBuffSection() {\r\n // mooPass\r\n let mooPassToggle = document.getElementById(\"mooPassToggle\");\r\n let mooPass = localStorage.getItem('mooPass');\r\n if (mooPass) {\r\n mooPassToggle.checked = Boolean(mooPass);\r\n }\r\n mooPassToggle.onchange = () => {\r\n localStorage.setItem('mooPass', mooPassToggle.checked);\r\n }\r\n \r\n // comExp\r\n let comExpToggle = document.getElementById(\"comExpToggle\");\r\n let comExpInput = document.getElementById(\"comExpInput\");\r\n let comExp = localStorage.getItem('comExp');\r\n if (comExp) {\r\n let comExpNumber = Number(comExp);\r\n if (comExpNumber > 0) {\r\n comExpToggle.checked = true;\r\n comExpInput.value = comExpNumber;\r\n } else {\r\n comExpToggle.checked = false;\r\n comExpInput.disabled = true;\r\n }\r\n }\r\n const updateComExp = () => {\r\n if (comExpToggle.checked) {\r\n let comExp = Number(comExpInput.value);\r\n localStorage.setItem('comExp', comExp); \r\n comExpInput.disabled = false;\r\n } else {\r\n localStorage.setItem('comExp', 0);\r\n comExpInput.disabled = true;\r\n }\r\n }\r\n comExpToggle.onchange = updateComExp;\r\n comExpInput.onchange = updateComExp;\r\n\r\n // comDrop\r\n let comDropToggle = document.getElementById(\"comDropToggle\");\r\n let comDropInput = document.getElementById(\"comDropInput\");\r\n let comDrop = localStorage.getItem('comDrop');\r\n if (comDrop) {\r\n let comDropNumber = Number(comDrop);\r\n if (comDropNumber > 0) {\r\n comDropToggle.checked = true;\r\n comDropInput.value = comDropNumber;\r\n } else {\r\n comDropToggle.checked = false;\r\n comDropInput.disabled = true;\r\n }\r\n }\r\n const updateComDrop = () => {\r\n if (comDropToggle.checked) {\r\n let comDrop = Number(comDropInput.value);\r\n localStorage.setItem('comDrop', comDrop); \r\n comDropInput.disabled = false;\r\n } else {\r\n localStorage.setItem('comDrop', 0);\r\n comDropInput.disabled = true;\r\n }\r\n }\r\n comDropToggle.onchange = updateComDrop;\r\n comDropInput.onchange = updateComDrop;\r\n}\r\n\r\n\r\nfunction updateState() {\r\n updateEquipmentState();\r\n updateLevels();\r\n updateFoodState();\r\n updateDrinksState();\r\n updateAbilityState();\r\n}\r\n\r\nfunction updateUI() {\r\n updateCombatStatsUI();\r\n updateFoodUI();\r\n updateDrinksUI();\r\n updateAbilityUI();\r\n\r\n updateContent();\r\n}\r\n\r\nconst darkModeToggle = document.getElementById('darkModeToggle');\r\nconst body = document.body;\r\n\r\nif (localStorage.getItem('darkModeEnabled') === 'true') {\r\n body.classList.add('dark-mode');\r\n const tables = document.getElementsByClassName('profit-table');\r\n for (const table of tables) {\r\n table.classList.toggle('table-striped');\r\n }\r\n darkModeToggle.checked = true;\r\n}\r\n\r\ndarkModeToggle.addEventListener('change', () => {\r\n body.classList.toggle('dark-mode');\r\n const tables = document.getElementsByClassName('profit-table');\r\n for (const table of tables) {\r\n table.classList.toggle('table-striped');\r\n }\r\n localStorage.setItem('darkModeEnabled', darkModeToggle.checked);\r\n});\r\n\r\nfunction updateContent() {\r\n document.querySelectorAll('[data-i18n]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n');\r\n if (key) {\r\n element.textContent = i18next.t(key);\r\n }\r\n });\r\n\r\n document.querySelectorAll('[data-i18n-placeholder]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n-placeholder');\r\n if (key) {\r\n element.placeholder = i18next.t(key);\r\n }\r\n });\r\n\r\n document.querySelectorAll('option[data-i18n]').forEach(function (element) {\r\n const key = element.getAttribute('data-i18n');\r\n if (key) {\r\n element.textContent = i18next.t(key);\r\n }\r\n });\r\n}\r\n\r\ninitEquipmentSection();\r\ninitHouseRoomsModal();\r\ninitAchievementsModal();\r\ninitLevelSection();\r\ninitFoodSection();\r\ninitDrinksSection();\r\ninitAbilitiesSection();\r\ninitZones();\r\ninitDungeons();\r\ninitTriggerModal();\r\ninitSimulationControls();\r\ninitEquipmentSetsModal();\r\ninitErrorHandling();\r\ninitImportExportModal();\r\ninitDamageDoneTaken();\r\ninitPatchNotes();\r\ninitExtraBuffSection();\r\n\r\nupdateState();\r\nupdateUI();\r\n"],"names":[],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"bundle.js","mappings":";;;;;;;;;;;;;;;;;AAA0B;AACkC;AAC5B;;AAEhC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA,0BAA0B,wDAAgB;AAC1C;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,6CAAI;AACrD;AACA;AACA;AACA;;AAEA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA,qDAAqD,gDAAO;AAC5D;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;ACxNG;AACkD;AACR;;AAEpE;AACA;AACA;AACA;;AAEA,wCAAwC,gEAAwB;AAChE;AACA,0CAA0C,4DAAoB;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;;AAEA,iEAAe,WAAW;;;;;;;;;;;;;;AC1B1B;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,iEAAe,IAAI,EAAC;;;;;;;;;;;;;;;ACdpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;;AAET;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;;AAET;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;;AAEA;AACA,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA,aAAa;AACb;;AAEA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;;AAET;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wDAAwD;AACtF,aAAa;;AAEb;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA,wBAAwB,mBAAmB;AAC3C;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;;ACnhBA;AAC4B;AACtB;;AAEhC;AACA;AACA;;AAEA,6BAA6B,qDAAa;AAC1C;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;;AAEA;AACA;AACA,UAAU;AACV;AACA;AACA,kCAAkC,gDAAO;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA,qDAAqD,gDAAO;AAC5D;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;ACtF4B;AACmD;;AAEzG;AACA;AACA;AACA,uBAAuB,qDAAa;AACpC;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA,yBAAyB,iFAAoC;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA,iEAAe,SAAS,EAAC;;;;;;;;;;;;;;;;;AC/CC;AACsC;;AAEhE;AACA;AACA;AACA;;AAEA,4BAA4B,0DAAkB;AAC9C;AACA;AACA;;AAEA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;AACA,+BAA+B,6CAAI;AACnC;AACA;AACA;AACA;AACA;;AAEA,iEAAe,SAAS;;;;;;;;;;;;;;;;;;;;AC7BQ;AACM;AACA;AACF;AACA;AACI;;AAExC,qBAAqB,mDAAU;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA,4CAA4C,kDAAS;AACrD;;AAEA,qDAAqD,mDAAU;AAC/D,2DAA2D,mDAAU;AACrE,qEAAqE,gDAAO;AAC5E;AACA;AACA,2CAA2C,kDAAS;AACpD;AACA,SAAS;;AAET,kCAAkC,oDAAW;;AAE7C;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,UAAU;AACV;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;;AAET;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;;AAEA;AACA;AACA;;AAEA,iEAAe,MAAM,EAAC;;;;;;;;;;;;;;;;ACvLsE;;AAE5F;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA,YAAY,wEAAgC;AAC5C;AACA,UAAU;AACV;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UCjLvB;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;UAEA;UACA;;;;;WCzBA;WACA;WACA;WACA;WACA,yCAAyC,wCAAwC;WACjF;WACA;WACA;;;;;WCPA;WACA;WACA;WACA;WACA;;;;;WCJA;WACA;WACA;WACA;WACA,GAAG;WACH;WACA;WACA,CAAC;;;;;WCPD;;;;;WCAA;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D;;;;;WCNA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;;;;WClBA;;WAEA;WACA;WACA;WACA;WACA;WACA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrBuD;AACN;AAC2B;AACN;AACU;AAC7B;AACM;AACN;AACN;AACc;AACiD;AACF;AACE;AACA;AAClC;AACc;AACN;AACE;AACF;AACG;AACF;;AAEzC;;AAE1C;AACA;;AAEA;AACA;AACA;AACA;;AAEA,gCAAgC,2FAA4B;AAC5D,qCAAqC,qHAAiC;AACtE;;;AAGA,iBAAiB,kEAAM;AACvB;AACA,IAAI,SAAI;AACR,IAAI,WAAM;AACV,IAAI,cAAS;AACb;AACA;AACA;;AAEA;AACA;AACA,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC,WAAW,YAAY,qJAAqJ,WAAW,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,2BAA2B,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,EAAE,qCAAqC,mBAAmB,6EAA6E,meAAme,qBAAqB;AAC5pC;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;;AAEA,sCAAsC,qEAAa;AACnD;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA,mCAAmC,0EAAkB;AACrD;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;;AAET;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA,gCAAgC,iFAAkB;AAClD;AACA,4FAA4F,eAAe;AAC3G;AACA;;AAEA,uDAAuD,eAAe;AACtE,6BAA6B,KAAK,GAAG,MAAM;AAC3C;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA,gCAAgC,iFAAkB;AAClD;AACA,sCAAsC,6EAAoB;AAC1D;AACA;;AAEA;AACA;;AAEA;AACA;AACA,kDAAkD,eAAe;AACjE;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,oEAAoE,eAAe;AACnF,mCAAmC,eAAe;AAClD;AACA;AACA,gGAAgG,eAAe;AAC/G;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;;AAEA;;AAEA;AACA,gCAAgC,eAAe;AAC/C;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA,mCAAmC,iBAAiB;AACpD;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;;AAEA;AACA;AACA,iDAAiD,iBAAiB;AAClE;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA,mBAAmB,qEAAa;;AAEhC;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA,uBAAuB,qEAAa;;AAEpC;AACA;AACA;AACA;AACA;;AAEA;AACA,8CAA8C,qEAAS;AACvD,KAAK;AACL;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC,kEAAS;AAC7C;AACA;AACA,iBAAiB;AACjB,cAAc;AACd;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,8BAA8B,uEAAW;AACzC;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,gEAAI;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,gEAAI;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,gEAAI;AAC3B;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA,2BAA2B,qEAAa;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,gEAAI;AACvC;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC;AACA;AACA;AACA;AACA,sCAAsC,wEAAgB;AACtD;AACA;AACA;AACA;AACA,mDAAmD,gEAAI;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,oCAAoC,iCAAiC;AACrE;AACA;AACA;AACA;AACA,8CAA8C,wEAAgB;AAC9D;AACA;AACA;AACA;AACA,2DAA2D,gEAAI;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;;AAEA;AACA;AACA;AACA,yBAAyB,kEAAM;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C;AAC7C;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,mCAAmC,6EAAoB;;AAEvD;AACA,qBAAqB,4EAAmB;AACxC;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;;AAEA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA,oBAAoB,OAAO;AAC3B;;AAEA,sCAAsC,qEAAa;AACnD;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,SAAI;AACZ,YAAY,SAAI,mBAAmB,SAAI;AACvC,2BAA2B,qEAAa,CAAC,SAAI;AAC7C,uBAAuB,SAAI;AAC3B;AACA;AACA;;AAEA;AACA,oBAAoB,OAAO;AAC3B;AACA;;AAEA;AACA,qFAAqF,SAAI;AACzF;AACA;;AAEA;;AAEA;;AAEA;AACA,oBAAoB,OAAO;AAC3B;;AAEA,uCAAuC,qEAAa;AACpD;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,WAAM;AACd,YAAY,WAAM,mBAAmB,WAAM;AAC3C,2BAA2B,qEAAa,CAAC,WAAM;AAC/C,uBAAuB,WAAM;AAC7B;AACA;AACA;;AAEA;AACA,oBAAoB,OAAO;AAC3B;AACA;;AAEA;AACA,sFAAsF,WAAM;AAC5F;AACA;;AAEA;;AAEA;;AAEA;AACA,oBAAoB,OAAO;AAC3B;AACA;;AAEA;;AAEA;AACA;AACA,0CAA0C,wEAAgB;AAC1D,UAAU;AACV,0CAA0C,wEAAgB;AAC1D;;;AAGA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,oBAAoB,OAAO;AAC3B;AACA,QAAQ,cAAS;AACjB,YAAY,cAAS,mBAAmB,cAAS;AACjD,8BAA8B,wEAAgB,CAAC,cAAS;AACxD,uBAAuB,cAAS;AAChC;AACA;AACA;;AAEA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;;AAEA,4DAA4D,yFAAgC;AAC5F,2DAA2D,yFAAgC;AAC3F,4DAA4D,yFAAgC,YAAY,cAAS;AACjH;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA,wBAAwB,cAAS;AACjC,IAAI,cAAS,iBAAiB,cAAS;AACvC,IAAI,cAAS;;AAEb;AACA;AACA;;AAEA;AACA,kEAAkE,cAAS;;AAE3E;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,4BAA4B,SAAI;AAChC;AACA;AACA,4BAA4B,WAAM;AAClC;AACA;AACA,4BAA4B,cAAS;AACrC;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;;AAEA;AACA;AACA;;AAEA;AACA,wCAAwC,qEAAa;AACrD,MAAM;AACN,wCAAwC,wEAAgB;AACxD;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;;AAEA;AACA;;AAEA;;AAEA,oBAAoB,OAAO;AAC3B;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;;AAEA,YAAY,yFAAgC;AAC5C;AACA;AACA,UAAU;AACV;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA,2CAA2C,yFAAgC;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,qBAAqB,yFAAgC;;AAErD;AACA;AACA,mCAAmC,wFAA+B;AAClE,MAAM;AACN,mCAAmC,wFAA+B;AAClE;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,oBAAoB,wFAA+B;;AAEnD,qEAAqE,yFAAgC;;AAErG;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;;AAEA;AACA,kCAAkC,wEAAe;AACjD;AACA;;AAEA;AACA;AACA;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;;AAEL,kCAAkC,wEAAe;AACjD;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA,SAAS;AACT;;;AAGA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;;AAEL,kCAAkC,wEAAe;AACjD;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,gFAAgF,cAAc;AAC9F,mDAAmD,cAAc,2BAA2B,cAAc;AAC1G,kBAAkB;AAClB;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA,SAAS;AACT;AACA;;AAEA;AACA;;AAEA,qCAAqC,wEAAe;AACpD;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA,2GAA2G,WAAW;;AAEtH;AACA;AACA;;AAEA;AACA;AACA,iCAAiC,WAAW;AAC5C,yDAAyD,WAAW;AACpE;AACA;AACA;AACA,gEAAgE,WAAW;AAC3E;;AAEA,qGAAqG,WAAW;AAChH;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,qFAAqF,WAAW;;AAEhG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,4GAA4G,WAAW;;AAEvH;AACA;AACA;;AAEA;AACA;AACA,kCAAkC,WAAW;AAC7C,0DAA0D,WAAW;AACrE;AACA;AACA;AACA,iEAAiE,WAAW;AAC5E;;AAEA,sGAAsG,WAAW;AACjH;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,sFAAsF,WAAW;;AAEjG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;;AAGA;AACA,qBAAqB,OAAO;AAC5B;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;;AAEA;AACA,8BAA8B;;AAE9B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU,4DAA4D;AACtE,UAAU,4DAA4D;AACtE,UAAU,4DAA4D;AACtE,UAAU,4DAA4D;AACtE,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU,4DAA4D;AACtE,UAAU,4DAA4D;AACtE,UAAU,4DAA4D;AACtE,UAAU,4DAA4D;AACtE,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,gBAAgB;AACtC;AACA;AACA;AACA;AACA,wBAAwB;AACxB;AACA,SAAS;AACT;AACA;AACA;AACA,yBAAyB,eAAe;AACxC,wBAAwB;AACxB,aAAa;AACb;AACA;AACA,yBAAyB,eAAe;AACxC,wBAAwB;AACxB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA,SAAS;AACT;AACA;AACA;AACA,oBAAoB,0BAA0B;AAC9C;AACA,SAAS;AACT,MAAM;AACN;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;;AAEA;;AAEA;AACA;AACA,oBAAoB,uBAAuB;AAC3C,wBAAwB,4BAA4B;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,YAAY,+EAAsB;AAClC,+BAA+B,+EAAsB;AACrD;AACA;AACA;;AAEA;AACA;AACA;;AAEA,6CAA6C,iJAAiJ;AAC9L;AACA,gBAAgB,+EAAsB;AACtC,mCAAmC,+EAAsB;AACzD;AACA;AACA;AACA,qDAAqD,uIAAuI;AAC5L;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA,4BAA4B,+BAA+B;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;;AAEA,aAAa;AACb;;AAEA;AACA,UAAU,kCAAkC;;AAE5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA,MAAM;AACN;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,uEAAuE,oDAAoD;AAC3H,iEAAiE,oDAAoD;AACrH,iFAAiF,oDAAoD;AACrI;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;;AAET;AACA,KAAK;;AAEL;AACA;AACA;;AAEA;AACA,sBAAsB,eAAe;AACrC;AACA;;AAEA;AACA,0BAA0B,mBAAmB;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,2CAA2C;AAC3C;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA,KAAK;;AAEL;AACA;AACA;;AAEA;AACA,kDAAkD,SAAS;AAC3D;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,KAAK;AACL,CAAC;;AAED;AACA;AACA;AACA;;AAEA,oBAAoB,iBAAiB;AACrC;AACA;AACA;;AAEA;AACA;AACA,SAAS;;AAET;AACA;;AAEA,+CAA+C,kBAAkB;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,+EAAsB;AACvC;AACA;AACA;AACA,SAAS;;AAET,UAAU,kCAAkC,qEAAqE;;AAEjH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,2EAA2E,wEAAgB;AAC3F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa,qEAAa;AAC1B;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA,kBAAkB;AAClB;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA,0CAA0C,wEAAgB;AAC1D;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,qEAAa;AACjC,iCAAiC,qEAAa;AAC9C;AACA,kBAAkB,SAAS,wEAAgB;AAC3C,iCAAiC,wEAAgB;AACjD;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,qEAAa;AAC1C;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA,kFAAkF,IAAI;AACtF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA,oBAAoB,QAAQ;AAC5B;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA,oBAAoB,QAAQ;AAC5B;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,yBAAyB,+EAAsB;AAC/C;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,wEAAgB;AAC9C;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA,oBAAoB,6BAA6B;AACjD;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,qEAAqE,UAAU;AAC/E;AACA;AACA;AACA,SAAS;AACT;;AAEA;AACA;AACA,sCAAsC,4BAA4B;AAClE;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,qCAAqC;AAC7D;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA,KAAK;AACL,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA,CAAC;;AAED;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;;AAET;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC,2FAA4B;;AAEhE;AACA;AACA;AACA,yCAAyC,qHAAiC;;AAE1E;AACA;AACA;;AAEA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA,4BAA4B,OAAO;AACnC,oBAAoB,SAAI;AACxB,yCAAyC,sEAAU,CAAC,SAAI,gBAAgB,SAAI;AAC5E;AACA,kBAAkB;AAClB;AACA;;AAEA,oBAAoB,WAAM;AAC1B,yCAAyC,sEAAU,CAAC,WAAM,gBAAgB,WAAM;AAChF;AACA,kBAAkB;AAClB;AACA;AACA;;AAEA,4BAA4B,OAAO;AACnC,oBAAoB,cAAS,mCAAmC,yFAAgC;AAChG;AACA,sCAAsC,mEAAO,CAAC,cAAS,iDAAiD,cAAS;AACjH;AACA,kBAAkB;AAClB;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,oDAAoD;AACxE;AACA;AACA;AACA;AACA;AACA,wCAAwC,qHAAiC;AACzE;AACA;AACA;AACA,MAAM;AACN;;AAEA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;;AAEA;AACA,0BAA0B,wEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,0BAA0B;AAC1D;;AAEA;AACA;AACA;AACA;AACA,6CAA6C,wCAAwC;AACrF,kCAAkC,uDAAuD;AACzF;AACA;AACA,aAAa;AACb;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,qHAAiC;AAC9E;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,KAAK,SAAS,qEAAS;AAC5E;AACA,KAAK;;AAEL;AACA;AACA,yBAAyB,sEAAU;AACnC;AACA;AACA;AACA;AACA,0BAA0B,sEAAU;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,mEAAO;AAC1C;AACA;AACA;AACA,mBAAmB,kEAAM;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wEAAwE,UAAU;AAClF;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,yDAAyD,IAAI;AAC7D;AACA;AACA;;AAEA;AACA,sCAAsC,wEAAe;AACrD;AACA;AACA;AACA;AACA;AACA,4CAA4C,0BAA0B;AACtE;;AAEA;AACA;AACA;AACA;AACA,yDAAyD,wCAAwC;AACjG,8CAA8C,uDAAuD;AACrG;AACA;AACA,yBAAyB;AACzB;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,oDAAoD;AACpF;AACA;AACA;AACA,sDAAsD,2FAA4B;AAClF;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,CAAC;;;AAGD;;AAEA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA,iCAAiC,WAAW,QAAQ,KAAK;AACzD;AACA,KAAK;;AAEL;AACA;;AAEA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA,mCAAmC,KAAK;AACxC,oCAAoC,KAAK,YAAY,UAAU;;AAE/D;;AAEA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA,qCAAqC,wBAAwB,WAAW,WAAW;AACnF;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,cAAc;AACd,qEAAqE,WAAW;AAChF;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd;AACA;AACA,cAAc;AACd,sEAAsE,YAAY;AAClF;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sEAAsE,cAAc,IAAI,YAAY;;AAEpG;AACA,SAAS;;AAET;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,2CAA2C,YAAY,IAAI,eAAe,GAAG,WAAW;;AAExF;AACA;AACA,cAAc;AACd;AACA;;AAEA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA,kCAAkC;AAClC,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,KAAK;;AAEL;AACA;;AAEA;;;AAGA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,kBAAkB;AAClB,qBAAqB;AACrB,gBAAgB;AAChB,kBAAkB;AAClB,qBAAqB;AACrB,sBAAsB;AACtB,sBAAsB;AACtB,wBAAwB;AACxB;;AAEA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,KAAK;;AAEL,oBAAoB,OAAO;AAC3B;AACA;AACA;;AAEA,oBAAoB,OAAO;AAC3B;AACA;AACA;;AAEA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,wFAA+B;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;;AAEL,oBAAoB,OAAO;AAC3B;AACA;AACA;;AAEA,oBAAoB,OAAO;AAC3B;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;;AAEA;;AAEA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,8BAA8B;AAC9B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,oBAAoB,IAAI,WAAM,UAAU;AACxC,2BAA2B,YAAY,WAAM,KAAK;AAClD;AACA;AACA,oBAAoB,IAAI,SAAI,UAAU;AACtC,yBAAyB,YAAY,SAAI,KAAK;AAC9C;AACA;AACA;AACA,gBAAgB,mCAAmC;AACnD,kBAAkB,qCAAqC;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA,oBAAoB,0BAA0B;AAC9C;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA,MAAM;AACN,uCAAuC,0EAAkB;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;;AAEA,MAAM;AACN,yCAAyC,6EAAoB;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA,cAAc,SAAI;AAClB,gBAAgB,WAAM;AACtB,mBAAmB,cAAS;AAC5B;AACA;AACA;AACA;AACA;;AAEA,oBAAoB,OAAO;AAC3B;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA,2BAA2B,qEAAa;AACxC,yBAAyB,qEAAa;AACtC;AACA,wCAAwC,gCAAgC,qEAAa;AACrF;AACA;AACA;AACA;AACA;AACA;;AAEA,yCAAyC;;AAEzC;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;;AAEA;AACA,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,mBAAmB,4GAAmD;AACtE;AACA,aAAa;AACb,sBAAsB,4GAAmD;AACzE;AACA,aAAa;AACb;;AAEA;AACA,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,mBAAmB,2GAAkD;AACrE;AACA,aAAa;AACb,sBAAsB,2GAAkD;AACxE;AACA,aAAa;AACb;;AAEA,MAAM;AACN;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,kEAAkE,gCAAgC,qEAAa;AAC/G;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;;AAED;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA,qBAAqB,6CAAS;AAC9B;AACA;;AAEA;AACA;AACA;AACA,2BAA2B,6CAAS;AACpC;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA","sources":["webpack://mwicombatsimulator/./src/combatsimulator/ability.js","webpack://mwicombatsimulator/./src/combatsimulator/achievement.js","webpack://mwicombatsimulator/./src/combatsimulator/buff.js","webpack://mwicombatsimulator/./src/combatsimulator/combatUnit.js","webpack://mwicombatsimulator/./src/combatsimulator/consumable.js","webpack://mwicombatsimulator/./src/combatsimulator/equipment.js","webpack://mwicombatsimulator/./src/combatsimulator/houseRoom.js","webpack://mwicombatsimulator/./src/combatsimulator/player.js","webpack://mwicombatsimulator/./src/combatsimulator/trigger.js","webpack://mwicombatsimulator/webpack/bootstrap","webpack://mwicombatsimulator/webpack/runtime/define property getters","webpack://mwicombatsimulator/webpack/runtime/get javascript chunk filename","webpack://mwicombatsimulator/webpack/runtime/global","webpack://mwicombatsimulator/webpack/runtime/hasOwnProperty shorthand","webpack://mwicombatsimulator/webpack/runtime/make namespace object","webpack://mwicombatsimulator/webpack/runtime/publicPath","webpack://mwicombatsimulator/webpack/runtime/jsonp chunk loading","webpack://mwicombatsimulator/./src/main.js"],"sourcesContent":["import Buff from \"./buff\";\nimport abilityDetailMap from \"./data/abilityDetailMap.json\";\nimport Trigger from \"./trigger\";\n\nconst abilityFromCombatStat = {\n \"blaze\":\n {\n \"hrid\": \"/abilities/blaze\",\n \"name\": \"Blaze\",\n \"description\": \"\",\n \"isSpecialAbility\": false,\n \"manaCost\": 0,\n \"cooldownDuration\": 0,\n \"castDuration\": 0,\n \"abilityEffects\": [\n {\n \"targetType\": \"allEnemies\",\n \"effectType\": \"/ability_effect_types/damage\",\n \"combatStyleHrid\": \"/combat_styles/magic\",\n \"damageType\": \"/damage_types/fire\",\n \"baseDamageFlat\": 0,\n \"baseDamageFlatLevelBonus\": 0.0,\n \"baseDamageRatio\": 0.3,\n \"baseDamageRatioLevelBonus\": 0,\n \"bonusAccuracyRatio\": 0,\n \"bonusAccuracyRatioLevelBonus\": 0,\n \"damageOverTimeRatio\": 0,\n \"damageOverTimeDuration\": 0,\n \"armorDamageRatio\": 0,\n \"armorDamageRatioLevelBonus\": 0,\n \"hpDrainRatio\": 0,\n \"pierceChance\": 0,\n \"blindChance\": 0,\n \"blindDuration\": 0,\n \"silenceChance\": 0,\n \"silenceDuration\": 0,\n \"stunChance\": 0,\n \"stunDuration\": 0,\n \"spendHpRatio\": 0,\n \"buffs\": null\n }\n ],\n \"defaultCombatTriggers\": [\n {\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\n \"conditionHrid\": \"/combat_trigger_conditions/number_of_active_units\",\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\n \"value\": 1\n },\n {\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_enemies\",\n \"conditionHrid\": \"/combat_trigger_conditions/current_hp\",\n \"comparatorHrid\": \"/combat_trigger_comparators/greater_than_equal\",\n \"value\": 1\n }\n ],\n },\n \"bloom\":\n {\n \"hrid\": \"/abilities/bloom\",\n \"name\": \"Bloom\",\n \"description\": \"\",\n \"isSpecialAbility\": false,\n \"manaCost\": 0,\n \"cooldownDuration\": 0,\n \"castDuration\": 0,\n \"abilityEffects\": [\n {\n \"targetType\": \"lowestHpAlly\",\n \"effectType\": \"/ability_effect_types/heal\",\n \"combatStyleHrid\": \"/combat_styles/magic\",\n \"damageType\": \"\",\n \"baseDamageFlat\": 10,\n \"baseDamageFlatLevelBonus\": 0,\n \"baseDamageRatio\": 0.15,\n \"baseDamageRatioLevelBonus\": 0,\n \"bonusAccuracyRatio\": 0,\n \"bonusAccuracyRatioLevelBonus\": 0,\n \"damageOverTimeRatio\": 0,\n \"damageOverTimeDuration\": 0,\n \"armorDamageRatio\": 0,\n \"armorDamageRatioLevelBonus\": 0,\n \"hpDrainRatio\": 0,\n \"pierceChance\": 0,\n \"blindChance\": 0,\n \"blindDuration\": 0,\n \"silenceChance\": 0,\n \"silenceDuration\": 0,\n \"stunChance\": 0,\n \"stunDuration\": 0,\n \"spendHpRatio\": 0,\n \"buffs\": null\n }\n ],\n \"defaultCombatTriggers\": [\n {\n \"dependencyHrid\": \"/combat_trigger_dependencies/all_allies\",\n \"conditionHrid\": \"/combat_trigger_conditions/lowest_hp_percentage\",\n \"comparatorHrid\": \"/combat_trigger_comparators/less_than_equal\",\n \"value\": 100\n }\n ],\n }\n}\n\nclass Ability {\n constructor(hrid, level = 1, triggers = null) {\n this.hrid = hrid;\n this.level = level;\n\n let gameAbility = abilityDetailMap[hrid];\n if (!gameAbility) {\n gameAbility = abilityFromCombatStat[hrid];\n }\n if (!gameAbility) {\n throw new Error(\"No ability found for hrid: \" + this.hrid);\n }\n\n this.manaCost = gameAbility.manaCost;\n this.cooldownDuration = gameAbility.cooldownDuration;\n this.castDuration = gameAbility.castDuration;\n this.isSpecialAbility = gameAbility.isSpecialAbility;\n\n this.abilityEffects = [];\n\n for (const effect of gameAbility.abilityEffects) {\n let abilityEffect = {\n targetType: effect.targetType,\n effectType: effect.effectType,\n combatStyleHrid: effect.combatStyleHrid,\n damageType: effect.damageType,\n damageFlat: effect.baseDamageFlat + (this.level - 1) * effect.baseDamageFlatLevelBonus,\n damageRatio: effect.baseDamageRatio + (this.level - 1) * effect.baseDamageRatioLevelBonus,\n bonusAccuracyRatio: effect.bonusAccuracyRatio + (this.level - 1) * effect.bonusAccuracyRatioLevelBonus,\n damageOverTimeRatio: effect.damageOverTimeRatio,\n damageOverTimeDuration: effect.damageOverTimeDuration,\n armorDamageRatio: effect.armorDamageRatio + (this.level - 1) * effect.armorDamageRatioLevelBonus,\n hpDrainRatio: effect.hpDrainRatio,\n pierceChance: effect.pierceChance,\n blindChance: effect.blindChance,\n blindDuration: effect.blindDuration,\n silenceChance: effect.silenceChance,\n silenceDuration: effect.silenceDuration,\n stunChance: effect.stunChance,\n stunDuration: effect.stunDuration,\n spendHpRatio: effect.spendHpRatio,\n buffs: null,\n };\n if (effect.buffs) {\n abilityEffect.buffs = [];\n for (const buff of effect.buffs) {\n abilityEffect.buffs.push(new Buff(buff, this.level));\n }\n }\n this.abilityEffects.push(abilityEffect);\n }\n\n if (triggers) {\n this.triggers = triggers;\n } else {\n this.triggers = [];\n for (const defaultTrigger of gameAbility.defaultCombatTriggers) {\n let trigger = new Trigger(\n defaultTrigger.dependencyHrid,\n defaultTrigger.conditionHrid,\n defaultTrigger.comparatorHrid,\n defaultTrigger.value\n );\n this.triggers.push(trigger);\n }\n }\n\n this.lastUsed = Number.MIN_SAFE_INTEGER;\n }\n\n static createFromDTO(dto) {\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\n let ability = new Ability(dto.hrid, dto.level, triggers);\n\n return ability;\n }\n\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\n if (source.isStunned) {\n return false;\n }\n\n if (source.isSilenced) {\n return false;\n }\n\n let haste = source.combatDetails.combatStats.abilityHaste;\n let cooldownDuration = this.cooldownDuration;\n if (haste > 0) {\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\n }\n\n if (this.lastUsed + cooldownDuration > currentTime) {\n return false;\n }\n\n if (this.triggers.length == 0) {\n return true;\n }\n\n let shouldTrigger = true;\n for (const trigger of this.triggers) {\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\n shouldTrigger = false;\n }\n }\n\n return shouldTrigger;\n }\n}\n\nexport default Ability;\n","import Buff from \"./buff\";\nimport achievementTierDetailMap from \"./data/achievementTierDetailMap.json\";\nimport achievementDetailMap from \"./data/achievementDetailMap.json\";\n\nclass Achievement {\n constructor(achievements) {\n this.achievements = achievements;\n this.buffs = [];\n\n for(const tier of Object.values(achievementTierDetailMap)) {\n let isGetAll = true;\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid)\n for(const achievement of Object.values(detailMap)) {\n if(!this.achievements[achievement.hrid] || this.achievements[achievement.hrid] == false) {\n isGetAll = false;\n break;\n }\n }\n if(isGetAll) {\n let buff = new Buff(tier.buff);\n this.buffs.push(buff);\n }\n }\n }\n}\n\nexport default Achievement;","class Buff {\n startTime;\n\n constructor(buff, level = 1) {\n this.uniqueHrid = buff.uniqueHrid;\n this.typeHrid = buff.typeHrid;\n this.ratioBoost = buff.ratioBoost + (level - 1) * buff.ratioBoostLevelBonus;\n this.flatBoost = buff.flatBoost + (level - 1) * buff.flatBoostLevelBonus;\n this.duration = buff.duration;\n this.multiplierForSkillHrid = buff.multiplierForSkillHrid ?? \"\";\n this.multiplierPerSkillLevel = buff.multiplierPerSkillLevel ?? 0;\n }\n}\n\nexport default Buff;\n","class CombatUnit {\n isPlayer;\n isStunned = false;\n stunExpireTime = null;\n isBlinded = false;\n blindExpireTime = null;\n isSilenced = false;\n silenceExpireTime = null;\n\n isOutOfMana = false;\n\n // Base levels which don't change after initialization\n staminaLevel = 1;\n intelligenceLevel = 1;\n attackLevel = 1;\n meleeLevel = 1;\n defenseLevel = 1;\n rangedLevel = 1;\n magicLevel = 1;\n\n experience = 0;\n experienceRate = 0;\n enrageTime = 0;\n\n abilities = [null, null, null, null];\n food = [null, null, null];\n drinks = [null, null, null];\n houseRooms = [];\n achievements = null;\n dropTable = [];\n rareDropTable = [];\n abilityManaCosts = new Map();\n\n // Calculated combat stats including temporary buffs\n combatDetails = {\n staminaLevel: 1,\n intelligenceLevel: 1,\n attackLevel: 1,\n meleeLevel: 1,\n defenseLevel: 1,\n rangedLevel: 1,\n magicLevel: 1,\n maxHitpoints: 110,\n currentHitpoints: 110,\n maxManapoints: 110,\n currentManapoints: 110,\n stabAccuracyRating: 11,\n slashAccuracyRating: 11,\n smashAccuracyRating: 11,\n rangedAccuracyRating: 11,\n magicAccuracyRating: 11,\n stabMaxDamage: 11,\n slashMaxDamage: 11,\n smashMaxDamage: 11,\n rangedMaxDamage: 11,\n magicMaxDamage: 11,\n stabEvasionRating: 11,\n slashEvasionRating: 11,\n smashEvasionRating: 11,\n rangedEvasionRating: 11,\n magicEvasionRating: 11,\n defensiveMaxDamage: 0,\n totalArmor: 0.2,\n totalWaterResistance: 0.4,\n totalNatureResistance: 0.4,\n totalFireResistance: 0.4,\n abilityHaste: 0,\n tenacity: 0,\n totalThreat: 100,\n combatStats: {\n combatStyleHrid: \"/combat_styles/smash\",\n damageType: \"/damage_types/physical\",\n attackInterval: 3000000000,\n autoAttackDamage: 0,\n abilityDamage: 0,\n criticalRate: 0,\n criticalDamage: 0,\n stabAccuracy: 0,\n slashAccuracy: 0,\n smashAccuracy: 0,\n rangedAccuracy: 0,\n magicAccuracy: 0,\n stabDamage: 0,\n slashDamage: 0,\n smashDamage: 0,\n rangedDamage: 0,\n magicDamage: 0,\n defensiveDamage: 0,\n taskDamage: 0,\n physicalAmplify: 0,\n waterAmplify: 0,\n natureAmplify: 0,\n fireAmplify: 0,\n healingAmplify: 0,\n physicalThorns: 0,\n elementalThorns: 0,\n maxHitpoints: 0,\n maxManapoints: 0,\n stabEvasion: 0,\n slashEvasion: 0,\n smashEvasion: 0,\n rangedEvasion: 0,\n magicEvasion: 0,\n armor: 0,\n waterResistance: 0,\n natureResistance: 0,\n fireResistance: 0,\n lifeSteal: 0,\n hpRegenPer10: 0.01,\n mpRegenPer10: 0.01,\n combatDropRate: 0,\n combatDropQuantity: 0,\n combatRareFind: 0,\n combatExperience: 0,\n foodSlots: 1,\n drinkSlots: 1,\n armorPenetration: 0,\n waterPenetration: 0,\n naturePenetration: 0,\n firePenetration: 0,\n manaLeech: 0,\n castSpeed: 0,\n threat: 100,\n parry: 0,\n mayhem: 0,\n pierce: 0,\n curse: 0,\n ripple: 0,\n bloom: 0,\n blaze: 0,\n weaken: 0,\n fury: 0,\n foodHaste: 0,\n drinkConcentration: 0,\n damageTaken: 0,\n attackSpeed: 0,\n armorDamageRatio: 0,\n hpDrainRatio: 0,\n primaryTraining: \"\",\n focusTraining: \"\",\n staminaExperience: 0,\n intelligenceExperience: 0,\n attackExperience: 0,\n defenseExperience: 0,\n meleeExperience: 0,\n rangedExperience: 0,\n magicExperience: 0,\n retaliation: 0,\n },\n };\n combatBuffs = {};\n permanentBuffs = {};\n zoneBuffs = {};\n extraBuffs = {};\n\n constructor() { }\n\n updateCombatDetails() {\n if (this.isPlayer) {\n if (this.combatDetails.combatStats.hpRegenPer10 === 0) {\n this.combatDetails.combatStats.hpRegenPer10 = 0.01;\n } else {\n this.combatDetails.combatStats.hpRegenPer10 = 0.01 + this.combatDetails.combatStats.hpRegenPer10;\n }\n if (this.combatDetails.combatStats.mpRegenPer10 === 0) {\n this.combatDetails.combatStats.mpRegenPer10 = 0.01;\n } else {\n this.combatDetails.combatStats.mpRegenPer10 = 0.01 + this.combatDetails.combatStats.mpRegenPer10;\n }\n }\n\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((stat) => {\n this.combatDetails[stat + \"Level\"] = this[stat + \"Level\"];\n let boosts = this.getBuffBoosts(\"/buff_types/\" + stat + \"_level\");\n boosts.forEach((buff) => {\n this.combatDetails[stat + \"Level\"] += (this[stat + \"Level\"] * buff.ratioBoost);\n this.combatDetails[stat + \"Level\"] += buff.flatBoost;\n });\n });\n\n this.combatDetails.maxHitpoints = Math.floor\n (10 * (10 + this.combatDetails.staminaLevel) + this.combatDetails.combatStats.maxHitpoints);\n this.combatDetails.maxManapoints = Math.floor\n (10 * (10 + this.combatDetails.intelligenceLevel) + this.combatDetails.combatStats.maxManapoints);\n\n let accuracyRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_accuracy\").ratioBoost;\n let damageRatioBoostFromFury = this.getBuffBoost(\"/buff_types/fury_damage\").ratioBoost;\n // if (accuracyRatioBoostFromFury > 0) {\n // console.log(\"Fury Boost: \" + accuracyRatioBoostFromFury);\n // }\n\n let accuracyRatioBoost = this.getBuffBoost(\"/buff_types/accuracy\").ratioBoost;\n let damageRatioBoost = this.getBuffBoost(\"/buff_types/damage\").ratioBoost;\n\n [\"stab\", \"slash\", \"smash\"].forEach((style) => {\n this.combatDetails[style + \"AccuracyRating\"] =\n (10 + this.combatDetails.attackLevel) *\n (1 + this.combatDetails.combatStats[style + \"Accuracy\"]) *\n (1 + accuracyRatioBoost) *\n (1 + accuracyRatioBoostFromFury);\n this.combatDetails[style + \"MaxDamage\"] =\n (10 + this.combatDetails.meleeLevel) *\n (1 + this.combatDetails.combatStats[style + \"Damage\"]) *\n (1 + damageRatioBoost) *\n (1 + damageRatioBoostFromFury);\n let baseEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats[style + \"Evasion\"]);\n this.combatDetails[style + \"EvasionRating\"] = baseEvasion;\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\n for (const boost of evasionBoosts) {\n this.combatDetails[style + \"EvasionRating\"] += boost.flatBoost;\n this.combatDetails[style + \"EvasionRating\"] += baseEvasion * boost.ratioBoost;\n }\n });\n\n this.combatDetails.defensiveMaxDamage = \n (10 + this.combatDetails.defenseLevel) * \n (1 + this.combatDetails.combatStats.defensiveDamage) *\n (1 + damageRatioBoost) *\n (1 + damageRatioBoostFromFury);\n\n // when equiped bulwark\n if (this.equipment?.['/equipment_types/two_hand']?.hrid.includes(\"bulwark\")) {\n this.combatDetails.smashMaxDamage += this.combatDetails.defensiveMaxDamage;\n }\n\n this.combatDetails.rangedAccuracyRating =\n (10 + this.combatDetails.attackLevel) *\n (1 + this.combatDetails.combatStats.rangedAccuracy) *\n (1 + accuracyRatioBoost) *\n (1 + accuracyRatioBoostFromFury);\n this.combatDetails.rangedMaxDamage =\n (10 + this.combatDetails.rangedLevel) *\n (1 + this.combatDetails.combatStats.rangedDamage) *\n (1 + damageRatioBoost) *\n (1 + damageRatioBoostFromFury);\n\n let baseRangedEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.rangedEvasion);\n this.combatDetails.rangedEvasionRating = baseRangedEvasion;\n let evasionBoosts = this.getBuffBoosts(\"/buff_types/evasion\");\n for (const boost of evasionBoosts) {\n this.combatDetails.rangedEvasionRating += boost.flatBoost;\n this.combatDetails.rangedEvasionRating += baseRangedEvasion * boost.ratioBoost;\n }\n\n this.combatDetails.combatStats.damageTaken = this.getBuffBoost(\"/buff_types/damage_taken\").flatBoost;\n // if (this.combatDetails.combatStats.damageTaken > 0) {\n // console.log(\"Damage taken: \" + this.combatDetails.combatStats.damageTaken);\n // }\n\n this.combatDetails.magicAccuracyRating =\n (10 + this.combatDetails.attackLevel) *\n (1 + this.combatDetails.combatStats.magicAccuracy) *\n (1 + accuracyRatioBoost) *\n (1 + accuracyRatioBoostFromFury);\n this.combatDetails.magicMaxDamage =\n (10 + this.combatDetails.magicLevel) *\n (1 + this.combatDetails.combatStats.magicDamage) *\n (1 + damageRatioBoost) *\n (1 + damageRatioBoostFromFury);\n\n let baseMagicEvasion = (10 + this.combatDetails.defenseLevel) * (1 + this.combatDetails.combatStats.magicEvasion);\n this.combatDetails.magicEvasionRating = baseMagicEvasion;\n for (const boost of evasionBoosts) {\n this.combatDetails.magicEvasionRating += boost.flatBoost;\n this.combatDetails.magicEvasionRating += baseMagicEvasion * boost.ratioBoost;\n }\n\n this.combatDetails.combatStats.physicalAmplify += this.getBuffBoost(\"/buff_types/physical_amplify\").flatBoost;\n this.combatDetails.combatStats.waterAmplify += this.getBuffBoost(\"/buff_types/water_amplify\").flatBoost;\n this.combatDetails.combatStats.natureAmplify += this.getBuffBoost(\"/buff_types/nature_amplify\").flatBoost;\n this.combatDetails.combatStats.fireAmplify += this.getBuffBoost(\"/buff_types/fire_amplify\").flatBoost;\n\n this.combatDetails.combatStats.attackInterval /= (1 + (this.combatDetails.attackLevel / 2000));\n\n let baseAttackSpeed = this.combatDetails.combatStats.attackSpeed;\n this.combatDetails.combatStats.attackInterval /= (1 + baseAttackSpeed);\n let attackIntervalBoosts = this.getBuffBoosts(\"/buff_types/attack_speed\");\n let attackIntervalRatioBoost = attackIntervalBoosts\n .map((boost) => boost.ratioBoost)\n .reduce((prev, cur) => prev + cur, 0);\n this.combatDetails.combatStats.attackInterval /= (1 + attackIntervalRatioBoost);\n\n let baseArmor = 0.2 * this.combatDetails.defenseLevel + this.combatDetails.combatStats.armor;\n this.combatDetails.totalArmor = baseArmor;\n let armorBoosts = this.getBuffBoosts(\"/buff_types/armor\");\n for (const boost of armorBoosts) {\n this.combatDetails.totalArmor += boost.flatBoost;\n this.combatDetails.totalArmor += baseArmor * boost.ratioBoost;\n }\n\n let baseWaterResistance =\n 0.2 * this.combatDetails.defenseLevel +\n this.combatDetails.combatStats.waterResistance;\n this.combatDetails.totalWaterResistance = baseWaterResistance;\n let waterResistanceBoosts = this.getBuffBoosts(\"/buff_types/water_resistance\");\n for (const boost of waterResistanceBoosts) {\n this.combatDetails.totalWaterResistance += boost.flatBoost;\n this.combatDetails.totalWaterResistance += baseWaterResistance * boost.ratioBoost;\n }\n\n let baseNatureResistance =\n 0.2 * this.combatDetails.defenseLevel +\n this.combatDetails.combatStats.natureResistance;\n this.combatDetails.totalNatureResistance = baseNatureResistance;\n let natureResistanceBoosts = this.getBuffBoosts(\"/buff_types/nature_resistance\");\n for (const boost of natureResistanceBoosts) {\n this.combatDetails.totalNatureResistance += boost.flatBoost;\n this.combatDetails.totalNatureResistance += baseNatureResistance * boost.ratioBoost;\n }\n\n let baseFireResistance =\n 0.2 * this.combatDetails.defenseLevel +\n this.combatDetails.combatStats.fireResistance;\n this.combatDetails.totalFireResistance = baseFireResistance;\n let fireResistanceBoosts = this.getBuffBoosts(\"/buff_types/fire_resistance\");\n for (const boost of fireResistanceBoosts) {\n this.combatDetails.totalFireResistance += boost.flatBoost;\n this.combatDetails.totalFireResistance += baseFireResistance * boost.ratioBoost;\n }\n\n let hpRegenBoosts = this.getBuffBoost(\"/buff_types/hp_regen\");\n this.combatDetails.combatStats.hpRegenPer10 += this.combatDetails.combatStats.hpRegenPer10 * hpRegenBoosts.ratioBoost;\n this.combatDetails.combatStats.hpRegenPer10 += hpRegenBoosts.flatBoost;\n\n let mpRegenBoosts = this.getBuffBoost(\"/buff_types/mp_regen\");\n this.combatDetails.combatStats.mpRegenPer10 += this.combatDetails.combatStats.mpRegenPer10 * mpRegenBoosts.ratioBoost;\n this.combatDetails.combatStats.mpRegenPer10 += mpRegenBoosts.flatBoost;\n\n this.combatDetails.combatStats.lifeSteal += this.getBuffBoost(\"/buff_types/life_steal\").flatBoost;\n this.combatDetails.combatStats.physicalThorns += this.getBuffBoost(\n \"/buff_types/physical_thorns\"\n ).flatBoost;\n this.combatDetails.combatStats.elementalThorns += this.getBuffBoost(\n \"/buff_types/elemental_thorns\"\n ).flatBoost;\n this.combatDetails.combatStats.combatExperience += this.getBuffBoost(\"/buff_types/wisdom\").flatBoost;\n this.combatDetails.combatStats.criticalRate += this.getBuffBoost(\"/buff_types/critical_rate\").flatBoost;\n this.combatDetails.combatStats.criticalDamage += this.getBuffBoost(\"/buff_types/critical_damage\").flatBoost;\n\n this.combatDetails.combatStats.castSpeed += this.getBuffBoost(\"/buff_types/cast_speed\").flatBoost;\n this.combatDetails.combatStats.castSpeed += this.combatDetails[\"attackLevel\"] / 2000;\n\n let combatDropRateBoosts = this.getBuffBoost(\"/buff_types/combat_drop_rate\");\n this.combatDetails.combatStats.combatDropRate += (1 + this.combatDetails.combatStats.combatDropRate) * combatDropRateBoosts.ratioBoost;\n this.combatDetails.combatStats.combatDropRate += combatDropRateBoosts.flatBoost;\n let combatRareFindBoosts = this.getBuffBoost(\"/buff_types/rare_find\");\n this.combatDetails.combatStats.combatRareFind += (1 + this.combatDetails.combatStats.combatRareFind) * combatRareFindBoosts.ratioBoost;\n this.combatDetails.combatStats.combatRareFind += combatRareFindBoosts.flatBoost;\n let combatDropQuantityBoosts = this.getBuffBoost(\"/buff_types/combat_drop_quantity\");\n this.combatDetails.combatStats.combatDropQuantity += (1 + this.combatDetails.combatStats.combatDropQuantity) * combatDropQuantityBoosts.ratioBoost;\n this.combatDetails.combatStats.combatDropQuantity += combatDropQuantityBoosts.flatBoost;\n\n let baseThreat = 100 + this.combatDetails.combatStats.threat;\n this.combatDetails.totalThreat = baseThreat;\n let threatBoosts = this.getBuffBoost(\"/buff_types/threat\");\n if (threatBoosts.ratioBoost !== 0) {\n this.combatDetails.combatStats.threat += baseThreat * threatBoosts.ratioBoost;\n } else {\n this.combatDetails.combatStats.threat = baseThreat;\n }\n this.combatDetails.combatStats.threat += threatBoosts.flatBoost;\n\n this.combatDetails.combatStats.retaliation += this.getBuffBoost(\"/buff_types/retaliation\").flatBoost;\n }\n\n addBuff(buff, currentTime) {\n buff.startTime = currentTime;\n this.combatBuffs[buff.uniqueHrid] = buff;\n\n this.updateCombatDetails();\n }\n\n removeBuff(buff) {\n if (!this.combatBuffs[buff.uniqueHrid]) {\n return;\n }\n delete this.combatBuffs[buff.uniqueHrid];\n\n this.updateCombatDetails();\n }\n\n addPermanentBuff(buff) {\n if (this.permanentBuffs[buff.typeHrid]) {\n this.permanentBuffs[buff.typeHrid].flatBoost += buff.flatBoost;\n this.permanentBuffs[buff.typeHrid].ratioBoost += buff.ratioBoost;\n } else {\n this.permanentBuffs[buff.typeHrid] = buff;\n }\n }\n\n generatePermanentBuffs() {\n for (let i = 0; i < this.houseRooms.length; i++) {\n const houseRoom = this.houseRooms[i];\n houseRoom.buffs.forEach(buff => {\n this.addPermanentBuff(buff);\n });\n }\n\n if (this.achievements) {\n this.achievements.buffs.forEach(buff => {\n this.addPermanentBuff(buff);\n });\n }\n if (this.zoneBuffs) {\n this.zoneBuffs.forEach(buff => {\n this.addPermanentBuff(buff);\n });\n }\n if (this.extraBuffs) {\n this.extraBuffs.forEach(buff => {\n this.addPermanentBuff(buff);\n });\n }\n }\n\n removeExpiredBuffs(currentTime) {\n let expiredBuffs = Object.values(this.combatBuffs).filter(\n (buff) => buff.startTime + buff.duration <= currentTime\n );\n expiredBuffs.forEach((buff) => {\n delete this.combatBuffs[buff.uniqueHrid];\n });\n\n this.updateCombatDetails();\n }\n\n clearBuffs() {\n this.combatBuffs = structuredClone(this.permanentBuffs);\n this.updateCombatDetails();\n }\n\n clearCCs() {\n this.isStunned = false;\n this.stunExpireTime = null;\n this.isSilenced = false;\n this.silenceExpireTime = null;\n this.isBlinded = false;\n this.blindExpireTime = null;\n this.combatDetails.combatStats.damageTaken = 0;\n }\n\n getBuffBoosts(type) {\n let boosts = [];\n Object.values(this.combatBuffs)\n .filter((buff) => buff.typeHrid == type)\n .forEach((buff) => {\n boosts.push({ ratioBoost: buff.ratioBoost, flatBoost: buff.flatBoost });\n });\n\n return boosts;\n }\n\n getBuffBoost(type) {\n let boosts = this.getBuffBoosts(type);\n\n let boost = {\n ratioBoost: 0,\n flatBoost: 0,\n };\n\n for (let i = 0; i < boosts.length; i++) {\n boost.ratioBoost += boosts[i]?.ratioBoost ?? 0;\n boost.flatBoost += boosts[i]?.flatBoost ?? 0;\n }\n\n return boost;\n }\n\n reset(currentTime = 0) {\n this.clearCCs();\n this.clearBuffs();\n this.updateCombatDetails();\n this.resetCooldowns(currentTime);\n\n this.combatDetails.currentHitpoints = this.combatDetails.maxHitpoints;\n this.combatDetails.currentManapoints = this.combatDetails.maxManapoints;\n }\n\n resetCooldowns(currentTime = 0) {\n this.food.filter((food) => food != null).forEach((food) => (food.lastUsed = Number.MIN_SAFE_INTEGER));\n this.drinks.filter((drink) => drink != null).forEach((drink) => (drink.lastUsed = Number.MIN_SAFE_INTEGER));\n\n let haste = this.combatDetails.combatStats.abilityHaste;\n\n this.abilities\n .filter((ability) => ability != null)\n .forEach((ability) => {\n if (this.isPlayer) {\n ability.lastUsed = Number.MIN_SAFE_INTEGER;\n } else {\n let cooldownDuration = ability.cooldownDuration;\n if (haste > 0) {\n cooldownDuration = cooldownDuration * 100 / (100 + haste);\n }\n ability.lastUsed = currentTime - Math.floor(cooldownDuration * 0.5) + Math.floor(Math.random() * cooldownDuration * 0.5);\n }\n });\n }\n\n addHitpoints(hitpoints) {\n let hitpointsAdded = 0;\n\n if (this.combatDetails.currentHitpoints >= this.combatDetails.maxHitpoints) {\n return hitpointsAdded;\n }\n\n let newHitpoints = Math.min(this.combatDetails.currentHitpoints + hitpoints, this.combatDetails.maxHitpoints);\n hitpointsAdded = newHitpoints - this.combatDetails.currentHitpoints;\n this.combatDetails.currentHitpoints = newHitpoints;\n\n return hitpointsAdded;\n }\n\n addManapoints(manapoints) {\n let manapointsAdded = 0;\n\n if (this.combatDetails.currentManapoints >= this.combatDetails.maxManapoints) {\n return manapointsAdded;\n }\n\n let newManapoints = Math.min(\n this.combatDetails.currentManapoints + manapoints,\n this.combatDetails.maxManapoints\n );\n manapointsAdded = newManapoints - this.combatDetails.currentManapoints;\n this.combatDetails.currentManapoints = newManapoints;\n\n return manapointsAdded;\n }\n}\n\nexport default CombatUnit;\n","import Buff from \"./buff\";\nimport itemDetailMap from \"./data/itemDetailMap.json\";\nimport Trigger from \"./trigger\";\n\nclass Consumable {\n constructor(hrid, triggers = null) {\n this.hrid = hrid;\n\n let gameConsumable = itemDetailMap[this.hrid];\n if (!gameConsumable) {\n throw new Error(\"No consumable found for hrid: \" + this.hrid);\n }\n\n this.cooldownDuration = gameConsumable.consumableDetail.cooldownDuration;\n this.hitpointRestore = gameConsumable.consumableDetail.hitpointRestore;\n this.manapointRestore = gameConsumable.consumableDetail.manapointRestore;\n this.recoveryDuration = gameConsumable.consumableDetail.recoveryDuration;\n this.catagoryHrid = gameConsumable.categoryHrid;\n\n this.buffs = [];\n if (gameConsumable.consumableDetail.buffs) {\n for (const consumableBuff of gameConsumable.consumableDetail.buffs) {\n let buff = new Buff(consumableBuff);\n this.buffs.push(buff);\n }\n }\n\n if (triggers) {\n this.triggers = triggers;\n } else {\n this.triggers = [];\n for (const defaultTrigger of gameConsumable.consumableDetail.defaultCombatTriggers) {\n let trigger = new Trigger(\n defaultTrigger.dependencyHrid,\n defaultTrigger.conditionHrid,\n defaultTrigger.comparatorHrid,\n defaultTrigger.value\n );\n this.triggers.push(trigger);\n }\n }\n\n this.lastUsed = Number.MIN_SAFE_INTEGER;\n }\n\n static createFromDTO(dto) {\n let triggers = dto.triggers.map((trigger) => Trigger.createFromDTO(trigger));\n let consumable = new Consumable(dto.hrid, triggers);\n\n return consumable;\n }\n\n shouldTrigger(currentTime, source, target, friendlies, enemies) {\n if (source.isStunned) {\n return false;\n }\n let consumableHaste;\n if (this.catagoryHrid.includes(\"food\")) {\n consumableHaste = source.combatDetails.combatStats.foodHaste\n } else {\n consumableHaste = source.combatDetails.combatStats.drinkConcentration;\n }\n let cooldownDuration = this.cooldownDuration;\n if (consumableHaste > 0) {\n cooldownDuration = cooldownDuration / (1 + consumableHaste);\n }\n\n if (this.lastUsed + cooldownDuration > currentTime) {\n return false;\n }\n\n if (this.triggers.length == 0) {\n return true;\n }\n\n let shouldTrigger = true;\n for (const trigger of this.triggers) {\n if (!trigger.isActive(source, target, friendlies, enemies, currentTime)) {\n shouldTrigger = false;\n }\n }\n\n return shouldTrigger;\n }\n}\n\nexport default Consumable;\n","import itemDetailMap from \"./data/itemDetailMap.json\";\nimport enhancementLevelTotalMultiplierTable from \"./data/enhancementLevelTotalBonusMultiplierTable.json\";\n\nclass Equipment {\n constructor(hrid, enhancementLevel) {\n this.hrid = hrid;\n let gameItem = itemDetailMap[this.hrid];\n if (!gameItem) {\n throw new Error(\"No equipment found for hrid: \" + this.hrid);\n }\n this.gameItem = gameItem;\n this.enhancementLevel = enhancementLevel;\n }\n\n static createFromDTO(dto) {\n let equipment = new Equipment(dto.hrid, dto.enhancementLevel);\n\n return equipment;\n }\n\n getCombatStat(combatStat) {\n let multiplier = enhancementLevelTotalMultiplierTable[this.enhancementLevel];\n if(this.gameItem.equipmentDetail.combatStats[combatStat]) {\n let enhancementBonus = this.gameItem.equipmentDetail.combatEnhancementBonuses[combatStat] || 0;\n let stat = this.gameItem.equipmentDetail.combatStats[combatStat] + multiplier * enhancementBonus;\n return stat;\n }\n return 0;\n }\n\n getCombatStyle() {\n return this.gameItem.equipmentDetail.combatStats.combatStyleHrids[0];\n }\n\n getDamageType() {\n return this.gameItem.equipmentDetail.combatStats.damageType;\n }\n\n getPrimaryTraining() {\n return this.gameItem.equipmentDetail.combatStats.primaryTraining;\n }\n\n getFocusTraining(){\n return this.gameItem.equipmentDetail.combatStats.focusTraining;\n }\n}\n\nexport default Equipment;\n","import Buff from \"./buff\";\nimport houseRoomDetailMap from \"./data/houseRoomDetailMap.json\";\n\nclass HouseRoom {\n constructor(hrid, level) {\n this.hrid = hrid;\n this.level = level;\n\n let gameHouseRoom = houseRoomDetailMap[this.hrid];\n if (!gameHouseRoom) {\n throw new Error(\"No house room found for hrid: \" + this.hrid);\n }\n\n this.buffs = [];\n if (gameHouseRoom.actionBuffs) {\n for (const actionBuff of gameHouseRoom.actionBuffs) {\n let buff = new Buff(actionBuff, level);\n this.buffs.push(buff);\n }\n }\n if (gameHouseRoom.globalBuffs) {\n for (const globalBuff of gameHouseRoom.globalBuffs) {\n let buff = new Buff(globalBuff, level);\n this.buffs.push(buff);\n }\n }\n }\n}\n\nexport default HouseRoom;","import Ability from \"./ability\";\nimport CombatUnit from \"./combatUnit\";\nimport Consumable from \"./consumable\";\nimport Equipment from \"./equipment\";\nimport HouseRoom from \"./houseRoom\";\nimport Achievement from \"./achievement\";\n\nclass Player extends CombatUnit {\n equipment = {\n \"/equipment_types/head\": null,\n \"/equipment_types/body\": null,\n \"/equipment_types/legs\": null,\n \"/equipment_types/feet\": null,\n \"/equipment_types/hands\": null,\n \"/equipment_types/main_hand\": null,\n \"/equipment_types/two_hand\": null,\n \"/equipment_types/off_hand\": null,\n \"/equipment_types/pouch\": null,\n \"/equipment_types/back\": null,\n };\n\n constructor() {\n super();\n\n this.isPlayer = true;\n this.hrid = \"player\";\n }\n\n static createFromDTO(dto) {\n let player = new Player();\n\n player.staminaLevel = dto.staminaLevel;\n player.intelligenceLevel = dto.intelligenceLevel;\n player.attackLevel = dto.attackLevel;\n player.meleeLevel = dto.meleeLevel;\n player.defenseLevel = dto.defenseLevel;\n player.rangedLevel = dto.rangedLevel;\n player.magicLevel = dto.magicLevel;\n\n player.hrid = dto.hrid;\n\n for (const [key, value] of Object.entries(dto.equipment)) {\n player.equipment[key] = value ? Equipment.createFromDTO(value) : null;\n }\n\n player.food = dto.food.map((food) => (food ? Consumable.createFromDTO(food) : null));\n player.drinks = dto.drinks.map((drink) => (drink ? Consumable.createFromDTO(drink) : null));\n player.abilities = dto.abilities.map((ability) => (ability ? Ability.createFromDTO(ability) : null));\n Object.entries(dto.houseRooms).forEach(houseRoom => {\n if (houseRoom[1] > 0) {\n player.houseRooms.push(new HouseRoom(houseRoom[0], houseRoom[1]))\n }\n });\n\n player.achievements = new Achievement(dto.achievements);\n\n player.debuffOnLevelGap = dto.debuffOnLevelGap;\n\n return player;\n }\n\n updateCombatDetails() {\n if (this.equipment[\"/equipment_types/main_hand\"]) {\n this.combatDetails.combatStats.combatStyleHrid =\n this.equipment[\"/equipment_types/main_hand\"].getCombatStyle();\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/main_hand\"].getDamageType();\n this.combatDetails.combatStats.attackInterval =\n this.equipment[\"/equipment_types/main_hand\"].getCombatStat(\"attackInterval\");\n this.combatDetails.combatStats.primaryTraining = \n this.equipment[\"/equipment_types/main_hand\"].getPrimaryTraining();\n } else if (this.equipment[\"/equipment_types/two_hand\"]) {\n this.combatDetails.combatStats.combatStyleHrid =\n this.equipment[\"/equipment_types/two_hand\"].getCombatStyle();\n this.combatDetails.combatStats.damageType = this.equipment[\"/equipment_types/two_hand\"].getDamageType();\n this.combatDetails.combatStats.attackInterval =\n this.equipment[\"/equipment_types/two_hand\"].getCombatStat(\"attackInterval\");\n this.combatDetails.combatStats.primaryTraining = \n this.equipment[\"/equipment_types/two_hand\"].getPrimaryTraining();\n } else {\n this.combatDetails.combatStats.combatStyleHrid = \"/combat_styles/smash\";\n this.combatDetails.combatStats.damageType = \"/damage_types/physical\";\n this.combatDetails.combatStats.attackInterval = 3000000000;\n this.combatDetails.combatStats.primaryTraining = \"/skills/melee\";\n }\n\n if (this.equipment[\"/equipment_types/charm\"]) {\n this.combatDetails.combatStats.focusTraining = this.equipment[\"/equipment_types/charm\"].getFocusTraining();\n } else {\n this.combatDetails.combatStats.focusTraining = \"\";\n }\n\n [\n \"stabAccuracy\",\n \"slashAccuracy\",\n \"smashAccuracy\",\n \"rangedAccuracy\",\n \"magicAccuracy\",\n \"stabDamage\",\n \"slashDamage\",\n \"smashDamage\",\n \"rangedDamage\",\n \"magicDamage\",\n \"defensiveDamage\",\n \"taskDamage\",\n \"physicalAmplify\",\n \"waterAmplify\",\n \"natureAmplify\",\n \"fireAmplify\",\n \"healingAmplify\",\n \"stabEvasion\",\n \"slashEvasion\",\n \"smashEvasion\",\n \"rangedEvasion\",\n \"magicEvasion\",\n \"armor\",\n \"waterResistance\",\n \"natureResistance\",\n \"fireResistance\",\n \"maxHitpoints\",\n \"maxManapoints\",\n \"lifeSteal\",\n \"hpRegenPer10\",\n \"mpRegenPer10\",\n \"physicalThorns\",\n \"elementalThorns\",\n \"combatDropRate\",\n \"combatRareFind\",\n \"combatDropQuantity\",\n \"combatExperience\",\n \"criticalRate\",\n \"criticalDamage\",\n \"armorPenetration\",\n \"waterPenetration\",\n \"naturePenetration\",\n \"firePenetration\",\n \"abilityHaste\",\n \"tenacity\",\n \"manaLeech\",\n \"castSpeed\",\n \"threat\",\n \"parry\",\n \"mayhem\",\n \"pierce\",\n \"curse\",\n \"fury\",\n \"weaken\",\n \"ripple\",\n \"bloom\",\n \"blaze\",\n \"attackSpeed\",\n \"foodHaste\",\n \"drinkConcentration\",\n \"autoAttackDamage\",\n \"abilityDamage\",\n \"staminaExperience\",\n \"intelligenceExperience\",\n \"attackExperience\",\n \"defenseExperience\",\n \"meleeExperience\",\n \"rangedExperience\",\n \"magicExperience\",\n \"retaliation\"\n ].forEach((stat) => {\n this.combatDetails.combatStats[stat] = Object.values(this.equipment)\n .filter((equipment) => equipment != null)\n .map((equipment) => equipment.getCombatStat(stat))\n .reduce((prev, cur) => prev + cur, 0);\n });\n\n if (this.equipment[\"/equipment_types/pouch\"]) {\n this.combatDetails.combatStats.foodSlots =\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"foodSlots\");\n this.combatDetails.combatStats.drinkSlots =\n 1 + this.equipment[\"/equipment_types/pouch\"].getCombatStat(\"drinkSlots\");\n } else {\n this.combatDetails.combatStats.foodSlots = 1;\n this.combatDetails.combatStats.drinkSlots = 1;\n }\n\n super.updateCombatDetails();\n }\n}\n\nexport default Player;\n","import combatTriggerDependencyDetailMap from \"./data/combatTriggerDependencyDetailMap.json\";\n\nclass Trigger {\n constructor(dependencyHrid, conditionHrid, comparatorHrid, value = 0) {\n this.dependencyHrid = dependencyHrid;\n this.conditionHrid = conditionHrid;\n this.comparatorHrid = comparatorHrid;\n this.value = value;\n }\n\n static createFromDTO(dto) {\n let trigger = new Trigger(dto.dependencyHrid, dto.conditionHrid, dto.comparatorHrid, dto.value);\n\n return trigger;\n }\n\n isActive(source, target, friendlies, enemies, currentTime) {\n if (combatTriggerDependencyDetailMap[this.dependencyHrid].isSingleTarget) {\n return this.isActiveSingleTarget(source, target, currentTime);\n } else {\n return this.isActiveMultiTarget(friendlies, enemies, currentTime);\n }\n }\n\n isActiveSingleTarget(source, target, currentTime) {\n let dependencyValue;\n switch (this.dependencyHrid) {\n case \"/combat_trigger_dependencies/self\":\n dependencyValue = this.getDependencyValue(source, currentTime);\n break;\n case \"/combat_trigger_dependencies/targeted_enemy\":\n if (!target) {\n return false;\n }\n dependencyValue = this.getDependencyValue(target, currentTime);\n break;\n default:\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\n }\n\n return this.compareValue(dependencyValue);\n }\n\n isActiveMultiTarget(friendlies, enemies, currentTime) {\n let dependency;\n switch (this.dependencyHrid) {\n case \"/combat_trigger_dependencies/all_allies\":\n dependency = friendlies;\n break;\n case \"/combat_trigger_dependencies/all_enemies\":\n if (!enemies) {\n return false;\n }\n dependency = enemies;\n break;\n default:\n throw new Error(\"Unknown dependencyHrid in trigger: \" + this.dependencyHrid);\n }\n\n let dependencyValue;\n switch (this.conditionHrid) {\n case \"/combat_trigger_conditions/number_of_active_units\":\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints > 0).length;\n break;\n case \"/combat_trigger_conditions/number_of_dead_units\":\n dependencyValue = dependency.filter((unit) => unit.combatDetails.currentHitpoints <= 0).length;\n break;\n case \"/combat_trigger_conditions/lowest_hp_percentage\":\n dependencyValue = dependency.reduce((prev, curr) => {\n let currentHpPercentage = curr.combatDetails.currentHitpoints / curr.combatDetails.maxHitpoints;\n return currentHpPercentage < prev ? currentHpPercentage : prev;\n }, 2) * 100;\n break;\n default:\n dependencyValue = dependency\n .map((unit) => this.getDependencyValue(unit, currentTime))\n .reduce((prev, cur) => prev + cur, 0);\n break;\n }\n\n return this.compareValue(dependencyValue);\n }\n\n getDependencyValue(source, currentTime) {\n switch (this.conditionHrid) {\n case \"/combat_trigger_conditions/berserk\":\n case \"/combat_trigger_conditions/frenzy\":\n case \"/combat_trigger_conditions/precision\":\n case \"/combat_trigger_conditions/vampirism\":\n case \"/combat_trigger_conditions/attack_coffee\":\n case \"/combat_trigger_conditions/defense_coffee\":\n case \"/combat_trigger_conditions/lucky_coffee\":\n case \"/combat_trigger_conditions/magic_coffee\":\n case \"/combat_trigger_conditions/melee_coffee\":\n case \"/combat_trigger_conditions/ranged_coffee\":\n case \"/combat_trigger_conditions/swiftness_coffee\":\n case \"/combat_trigger_conditions/wisdom_coffee\":\n case \"/combat_trigger_conditions/ice_spear\":\n case \"/combat_trigger_conditions/puncture\":\n case \"/combat_trigger_conditions/frost_surge\":\n case \"/combat_trigger_conditions/elusiveness\":\n case \"/combat_trigger_conditions/channeling_coffee\":\n case \"/combat_trigger_conditions/fierce_aura\":\n case \"/combat_trigger_conditions/invincible_armor\":\n case \"/combat_trigger_conditions/invincible_fire_resistance\":\n case \"/combat_trigger_conditions/invincible_nature_resistance\":\n case \"/combat_trigger_conditions/invincible_water_resistance\":\n case \"/combat_trigger_conditions/provoke\":\n case \"/combat_trigger_conditions/taunt\":\n case \"/combat_trigger_conditions/crippling_slash\":\n case \"/combat_trigger_conditions/mana_spring\":\n case \"/combat_trigger_conditions/retribution\":\n case \"/combat_trigger_conditions/fracturing_impact\":\n case \"/combat_trigger_conditions/maim\":\n case \"/combat_trigger_conditions/curse\":\n case \"/combat_trigger_conditions/weaken\":\n let buffHrid = \"/buff_uniques\";\n buffHrid += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\n return source.combatBuffs[buffHrid];\n case \"/combat_trigger_conditions/critical_aura\":\n case \"/combat_trigger_conditions/critical_coffee\":\n case \"/combat_trigger_conditions/intelligence_coffee\":\n case \"/combat_trigger_conditions/stamina_coffee\":\n case \"/combat_trigger_conditions/elemental_affinity\":\n case \"/combat_trigger_conditions/fury\":\n case \"/combat_trigger_conditions/guardian_aura\":\n case \"/combat_trigger_conditions/insanity\":\n case \"/combat_trigger_conditions/spike_shell\":\n case \"/combat_trigger_conditions/toxic_pollen\":\n case \"/combat_trigger_conditions/invincible\":\n case \"/combat_trigger_conditions/mystic_aura\":\n case \"/combat_trigger_conditions/pestilent_shot\":\n case \"/combat_trigger_conditions/smoke_burst\":\n case \"/combat_trigger_conditions/speed_aura\":\n case \"/combat_trigger_conditions/toughness\":\n case \"/combat_trigger_conditions/enrage\":\n let buffPrefix = \"/buff_uniques\";\n buffPrefix += this.conditionHrid.slice(this.conditionHrid.lastIndexOf(\"/\"));\n let buffs = Object.keys(source.combatBuffs).filter(buff => buff.startsWith(buffPrefix));\n return source.combatBuffs[buffs?.[0]];\n case \"/combat_trigger_conditions/current_hp\":\n return source.combatDetails.currentHitpoints;\n case \"/combat_trigger_conditions/current_mp\":\n return source.combatDetails.currentManapoints;\n case \"/combat_trigger_conditions/missing_hp\":\n return source.combatDetails.maxHitpoints - source.combatDetails.currentHitpoints;\n case \"/combat_trigger_conditions/missing_mp\":\n return source.combatDetails.maxManapoints - source.combatDetails.currentManapoints;\n case \"/combat_trigger_conditions/stun_status\":\n // Replicate the game's behaviour of \"stun status active\" triggers activating\n // immediately after the stun has worn off\n return source.isStunned || source.stunExpireTime == currentTime;\n case \"/combat_trigger_conditions/blind_status\":\n return source.isBlinded || source.blindExpireTime == currentTime;\n case \"/combat_trigger_conditions/silence_status\":\n return source.isSilenced || source.silenceExpireTime == currentTime;\n default:\n throw new Error(\"Unknown conditionHrid in trigger: \" + this.conditionHrid);\n }\n }\n\n compareValue(dependencyValue) {\n switch (this.comparatorHrid) {\n case \"/combat_trigger_comparators/greater_than_equal\":\n return dependencyValue >= this.value;\n case \"/combat_trigger_comparators/less_than_equal\":\n return dependencyValue <= this.value;\n case \"/combat_trigger_comparators/is_active\":\n return !!dependencyValue;\n case \"/combat_trigger_comparators/is_inactive\":\n return !dependencyValue;\n default:\n throw new Error(\"Unknown comparatorHrid in trigger: \" + this.comparatorHrid);\n }\n }\n}\n\nexport default Trigger;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","// This function allow to reference async chunks\n__webpack_require__.u = (chunkId) => {\n\t// return url for filenames based on template\n\treturn \"\" + chunkId + \".bundle.js\";\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","var scriptUrl;\nif (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + \"\";\nvar document = __webpack_require__.g.document;\nif (!scriptUrl && document) {\n\tif (document.currentScript)\n\t\tscriptUrl = document.currentScript.src;\n\tif (!scriptUrl) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tif(scripts.length) {\n\t\t\tvar i = scripts.length - 1;\n\t\t\twhile (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;\n\t\t}\n\t}\n}\n// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration\n// or pass an empty string (\"\") and set the __webpack_public_path__ variable from your code to use your own logic.\nif (!scriptUrl) throw new Error(\"Automatic publicPath is not supported in this browser\");\nscriptUrl = scriptUrl.replace(/#.*$/, \"\").replace(/\\?.*$/, \"\").replace(/\\/[^\\/]+$/, \"/\");\n__webpack_require__.p = scriptUrl;","__webpack_require__.b = document.baseURI || self.location.href;\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t\"main\": 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n// no on chunks loaded\n\n// no jsonp function","import Equipment from \"./combatsimulator/equipment.js\";\nimport Player from \"./combatsimulator/player.js\";\nimport abilityDetailMap from \"./combatsimulator/data/abilityDetailMap.json\";\nimport itemDetailMap from \"./combatsimulator/data/itemDetailMap.json\";\nimport houseRoomDetailMap from \"./combatsimulator/data/houseRoomDetailMap.json\";\nimport Ability from \"./combatsimulator/ability.js\";\nimport Consumable from \"./combatsimulator/consumable.js\";\nimport HouseRoom from \"./combatsimulator/houseRoom\"\nimport Buff from \"./combatsimulator/buff.js\";\nimport Achievement from \"./combatsimulator/achievement.js\";\nimport combatTriggerDependencyDetailMap from \"./combatsimulator/data/combatTriggerDependencyDetailMap.json\";\nimport combatTriggerConditionDetailMap from \"./combatsimulator/data/combatTriggerConditionDetailMap.json\";\nimport combatTriggerComparatorDetailMap from \"./combatsimulator/data/combatTriggerComparatorDetailMap.json\";\nimport abilitySlotsLevelRequirementList from \"./combatsimulator/data/abilitySlotsLevelRequirementList.json\";\nimport actionDetailMap from \"./combatsimulator/data/actionDetailMap.json\";\nimport combatMonsterDetailMap from \"./combatsimulator/data/combatMonsterDetailMap.json\";\nimport damageTypeDetailMap from \"./combatsimulator/data/damageTypeDetailMap.json\";\nimport combatStyleDetailMap from \"./combatsimulator/data/combatStyleDetailMap.json\";\nimport openableLootDropMap from \"./combatsimulator/data/openableLootDropMap.json\";\nimport achievementTierMap from \"./combatsimulator/data/achievementTierDetailMap.json\"\nimport achievementDetailMap from \"./combatsimulator/data/achievementDetailMap.json\"\n\nimport patchNote from \"../patchNote.json\";\n\nconst ONE_SECOND = 1e9;\nconst ONE_HOUR = 60 * 60 * ONE_SECOND;\n\nlet buttonStartSimulation = document.getElementById(\"buttonStartSimulation\");\nlet buttonStopSimulation = document.getElementById(\"buttonStopSimulation\");\nlet progressbar = document.getElementById(\"simulationProgressBar\");\nlet simStartTime = 0;\n\nlet worker = new Worker(new URL(\"worker.js\", import.meta.url));\nlet multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\nlet workerPool = [];\n\n\nlet player = new Player();\nlet selectedPlayers = [];\nlet food = [null, null, null];\nlet drinks = [null, null, null];\nlet abilities = [null, null, null, null];\nlet triggerMap = {};\nlet modalTriggers = [];\nlet currentSimResults = {};\n\nlet currentPlayerTabId = '1';\nlet playerDataMap = {\n \"1\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\n \"2\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\n \"3\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\n \"4\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\",\n \"5\": \"{\\\"player\\\":{\\\"attackLevel\\\":1,\\\"magicLevel\\\":1,\\\"meleeLevel\\\":1,\\\"rangedLevel\\\":1,\\\"defenseLevel\\\":1,\\\"staminaLevel\\\":1,\\\"intelligenceLevel\\\":1,\\\"equipment\\\":[]},\\\"food\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"drinks\\\":{\\\"/action_types/combat\\\":[{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"},{\\\"itemHrid\\\":\\\"\\\"}]},\\\"abilities\\\":[{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"},{\\\"abilityHrid\\\":\\\"\\\",\\\"level\\\":\\\"1\\\"}],\\\"triggerMap\\\":{},\\\"zone\\\":\\\"/actions/combat/fly\\\",\\\"simulationTime\\\":\\\"100\\\",\\\"houseRooms\\\":{\\\"/house_rooms/dairy_barn\\\":0,\\\"/house_rooms/garden\\\":0,\\\"/house_rooms/log_shed\\\":0,\\\"/house_rooms/forge\\\":0,\\\"/house_rooms/workshop\\\":0,\\\"/house_rooms/sewing_parlor\\\":0,\\\"/house_rooms/kitchen\\\":0,\\\"/house_rooms/brewery\\\":0,\\\"/house_rooms/laboratory\\\":0,\\\"/house_rooms/dining_room\\\":0,\\\"/house_rooms/library\\\":0,\\\"/house_rooms/dojo\\\":0,\\\"/house_rooms/gym\\\":0,\\\"/house_rooms/armory\\\":0,\\\"/house_rooms/archery_range\\\":0,\\\"/house_rooms/mystical_study\\\":0,\\\"/house_rooms/observatory\\\":0},\\\"achievements\\\":{}}\"\n};\nwindow.revenue = 0;\nwindow.noRngRevenue = 0;\nwindow.expenses = 0;\nwindow.profit = 0;\nwindow.noRngProfit = 0;\n\n// #region Worker\n\nfunction onWorkerMessage(event) {\n switch (event.data.type) {\n case \"simulation_result\":\n progressbar.style.width = \"100%\";\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\n //console.log(\"SIM RESULTS: \", event.data.simResult);\n showSimulationResult(event.data.simResult);\n updateContent();\n buttonStartSimulation.disabled = false;\n buttonStopSimulation.style.display = 'none';\n document.getElementById('buttonShowAllSimData').style.display = 'none';\n break;\n case \"simulation_progress\":\n let progress = Math.floor(100 * event.data.progress);\n progressbar.style.width = progress + \"%\";\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\n // 实时更新图表\n if (event.data.timeSeriesData && document.getElementById('hpMpVisualizationToggle').checked) {\n updateChartsRealtime(event.data.timeSeriesData);\n }\n break;\n case \"simulation_error\":\n showErrorModal(event.data.error.toString());\n break;\n }\n};\n\nfunction onMultiWorkerMessage(event) {\n switch (event.data.type) {\n case \"simulation_result_allZones\":\n progressbar.style.width = \"100%\";\n progressbar.innerHTML = \"100% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\n showAllSimulationResults(event.data.simResults);\n updateContent();\n buttonStartSimulation.disabled = false;\n buttonStopSimulation.style.display = 'none';\n document.getElementById('buttonShowAllSimData').style.display = 'block';\n break;\n case \"simulation_progress\":\n let progress = Math.floor(100 * event.data.progress);\n progressbar.style.width = progress + \"%\";\n progressbar.innerHTML = progress + \"% (\" + ((Date.now() - simStartTime) / 1000).toFixed(2) + \"s)\";\n break;\n case \"simulation_error\":\n showErrorModal(event.data.error.toString());\n break;\n }\n};\n\n// #endregion\n\n// #region Equipment\n\nfunction initEquipmentSection() {\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\n initEquipmentSelect(type);\n initEnhancementLevelInput(type);\n });\n}\n\nfunction initEquipmentSelect(equipmentType) {\n let selectId = \"selectEquipment_\";\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\n selectId += \"weapon\";\n } else {\n selectId += equipmentType;\n }\n let selectElement = document.getElementById(selectId);\n\n let gameEquipment = Object.values(itemDetailMap)\n .filter((item) => item.categoryHrid == \"/item_categories/equipment\")\n .filter((item) => item.equipmentDetail.type == \"/equipment_types/\" + equipmentType)\n .sort((a, b) => a.sortIndex - b.sortIndex);\n\n for (const equipment of Object.values(gameEquipment)) {\n let opt = new Option(equipment.name, equipment.hrid);\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + equipment.hrid);\n selectElement.add(opt);\n }\n\n selectElement.addEventListener(\"change\", (event) => {\n equipmentSelectHandler(event, equipmentType);\n });\n}\n\nfunction initHouseRoomsModal() {\n let houseRoomsList = document.getElementById(\"houseRoomsList\");\n let newChildren = [];\n let houseRooms = Object.values(houseRoomDetailMap).sort((a, b) => a.sortIndex - b.sortIndex);\n player.houseRooms = {};\n\n for (const room of Object.values(houseRooms)) {\n player.houseRooms[room.hrid] = 0;\n\n let row = createElement(\"div\", \"row mb-2\");\n\n let nameCol = createElement(\"div\", \"col-md-4 offset-md-3 align-self-center\", room.name);\n nameCol.setAttribute(\"data-i18n\", \"houseRoomNames.\" + room.hrid);\n row.appendChild(nameCol);\n\n let levelCol = createElement(\"div\", \"col-md-2\");\n let levelInput = createHouseInput(room.hrid);\n\n levelInput.addEventListener(\"input\", function (e) {\n let inputValue = e.target.value;\n const hrid = e.target.dataset.houseHrid;\n player.houseRooms[hrid] = parseInt(inputValue);\n // 如果开启入战属性模式,实时更新\n if (isCombatReadyMode) {\n updateCombatStatsUI();\n }\n });\n\n levelCol.appendChild(levelInput);\n row.appendChild(levelCol);\n\n newChildren.push(row);\n }\n\n houseRoomsList.replaceChildren(...newChildren);\n}\n\nfunction createHouseInput(hrid) {\n let levelInput = document.createElement(\"input\");\n levelInput.className = \"form-control\";\n levelInput.type = \"number\";\n levelInput.placeholder = 0;\n levelInput.min = 0;\n levelInput.max = 8;\n levelInput.step = 1;\n levelInput.dataset.houseHrid = hrid;\n\n return levelInput;\n}\n\nfunction refreshAchievementStatics() {\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\n for(const tier of Object.values(tierMap)) {\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\n const done = Array.from(checks).filter(cb => cb.checked).length;\n const total = checks.length;\n\n const stat = document.getElementById(`AchTier${tier.sortIndex}Statics`);\n stat.innerText = `(${done}/${total})`;\n if (done == total) {\n // set to green\n stat.classList.remove(\"text-secondary\");\n stat.classList.add(\"text-success\");\n } else {\n // set to secondary\n stat.classList.remove(\"text-success\");\n stat.classList.add(\"text-secondary\");\n }\n }\n}\n\nfunction initAchievementsModal(){\n let achievementsList = document.getElementById(\"achievementsList\");\n let newChildren = [];\n player.achievements = {};\n\n let tierMap = Object.values(achievementTierMap).sort((a, b) => a.sortIndex - b.sortIndex);\n for(const tier of Object.values(tierMap)) {\n let detailMap = Object.values(achievementDetailMap).filter((detail) => detail.tierHrid == tier.hrid).sort((a, b) => a.sortIndex - b.sortIndex);\n let detailMapCount = detailMap.length;\n if (detailMapCount <= 0) continue;\n\n let card = createElement(\"div\", \"card\");\n let cardHeader = createElement(\"div\", \"card-header d-flex align-items-center\");\n\n let cardTitle = createElement(\"a\", \"btn\", tier.name);\n cardTitle.setAttribute(\"data-bs-toggle\",\"collapse\");\n cardTitle.setAttribute(\"href\", `#AchTier${tier.sortIndex}`);\n cardTitle.setAttribute(\"data-i18n\", \"achievementTierNames.\"+tier.hrid);\n cardHeader.appendChild(cardTitle);\n\n let bufDesc = createElement(\"div\", \"small text-secondary\");\n let buffName = createElement(\"i\", \"\");\n buffName.setAttribute(\"data-i18n\", \"buffTypeNames.\"+tier[\"buff\"].typeHrid);\n bufDesc.appendChild(buffName);\n let buffValue = createElement(\"i\", \"\");\n buffValue.innerText = \":+\" + parseFloat(tier[\"buff\"].ratioBoost==0?tier[\"buff\"].flatBoost:tier[\"buff\"].ratioBoost)*100 + \"%\";\n bufDesc.appendChild(buffValue);\n cardHeader.appendChild(bufDesc);\n\n let cardStatics = createElement(\"div\", \"ms-auto btn\", `(0/${detailMapCount})`);\n cardStatics.id = `AchTier${tier.sortIndex}Statics`;\n cardStatics.dataset.checked = \"true\";\n cardStatics.addEventListener(\"click\", function (e) {\n const checks = document.querySelectorAll(`input[data-achievement-hrid][data-tier=\"${tier.sortIndex}\"]`);\n for (const check of checks) {\n check.checked = cardStatics.dataset.checked == \"true\";\n const hrid = check.dataset.achievementHrid;\n player.achievements[hrid] = check.checked;\n }\n cardStatics.dataset.checked = cardStatics.dataset.checked == \"true\" ? \"false\" : \"true\";\n refreshAchievementStatics();\n // 如果开启入战属性模式,实时更新\n if (isCombatReadyMode) {\n updateCombatStatsUI();\n }\n });\n cardHeader.appendChild(cardStatics);\n\n card.appendChild(cardHeader);\n\n let cardMain = createElement(\"div\", \"collapse\");\n cardMain.id = `AchTier${tier.sortIndex}`;\n let cardBody = createElement(\"div\", \"card-body\");\n\n for (const detail of Object.values(detailMap)) {\n let row = createElement(\"div\", \"row mb-2\");\n\n let formCheck = createElement(\"div\", \"form-check\");\n let input = createElement(\"input\", \"form-check-input\");\n input.setAttribute(\"type\", \"checkbox\");\n input.setAttribute(\"data-tier\", tier.sortIndex);\n input.id = `AchDetail${detail.sortIndex}`;\n input.dataset.achievementHrid = detail.hrid;\n input.addEventListener(\"change\", function (e) {\n const hrid = e.target.dataset.achievementHrid;\n player.achievements[hrid] = e.target.checked;\n\n refreshAchievementStatics();\n // 如果开启入战属性模式,实时更新\n if (isCombatReadyMode) {\n updateCombatStatsUI();\n }\n });\n formCheck.appendChild(input);\n\n let name = createElement(\"label\", \"form-check-label\", detail.name);\n name.setAttribute(\"data-i18n\", \"achievementNames.\" + detail.hrid);\n name.setAttribute(\"for\", `AchDetail${detail.sortIndex}`);\n formCheck.appendChild(name);\n row.appendChild(formCheck);\n cardBody.appendChild(row);\n }\n cardMain.appendChild(cardBody);\n card.appendChild(cardMain);\n\n newChildren.push(card);\n }\n\n achievementsList.replaceChildren(...newChildren);\n}\n\nfunction initEnhancementLevelInput(equipmentType) {\n let inputId = \"inputEquipmentEnhancementLevel_\";\n if (equipmentType == \"main_hand\" || equipmentType == \"two_hand\") {\n inputId += \"weapon\";\n } else {\n inputId += equipmentType;\n }\n\n let inputElement = document.getElementById(inputId);\n inputElement.value = 0;\n inputElement.addEventListener(\"change\", enhancementLevelInputHandler);\n}\n\nfunction equipmentSelectHandler(event, type) {\n let equipmentType = \"/equipment_types/\" + type;\n\n if (!event.target.value) {\n updateEquipmentState();\n updateUI();\n return;\n }\n\n let gameItem = itemDetailMap[event.target.value];\n\n // Weapon select has two handlers because of mainhand and twohand weapons. Ignore the handler with the wrong type\n if (gameItem.equipmentDetail.type != equipmentType) {\n return;\n }\n\n if (type == \"two_hand\") {\n document.getElementById(\"selectEquipment_off_hand\").value = \"\";\n document.getElementById(\"inputEquipmentEnhancementLevel_off_hand\").value = 0;\n }\n if (type == \"off_hand\" && player.equipment[\"/equipment_types/two_hand\"]) {\n document.getElementById(\"selectEquipment_weapon\").value = \"\";\n document.getElementById(\"inputEquipmentEnhancementLevel_weapon\").value = 0;\n }\n\n updateEquipmentState();\n updateUI();\n}\n\nfunction enhancementLevelInputHandler() {\n updateEquipmentState();\n updateUI();\n}\n\nfunction updateEquipmentState() {\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"main_hand\", \"two_hand\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\n let equipmentType = \"/equipment_types/\" + type;\n let selectType = type;\n if (type == \"main_hand\" || type == \"two_hand\") {\n selectType = \"weapon\";\n }\n\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + selectType);\n let equipmentHrid = equipmentSelect.value;\n\n if (!equipmentHrid) {\n player.equipment[equipmentType] = null;\n return;\n }\n\n let gameItem = itemDetailMap[equipmentHrid];\n\n // Clear old weapon if a weapon of a different type is equipped\n if (gameItem.equipmentDetail.type != equipmentType) {\n player.equipment[equipmentType] = null;\n return;\n }\n\n let enhancementLevel = Number(document.getElementById(\"inputEquipmentEnhancementLevel_\" + selectType).value);\n player.equipment[equipmentType] = new Equipment(gameItem.hrid, enhancementLevel);\n });\n}\n\ndocument.getElementById(\"selectEquipment_set\").onchange = changeEquipmentSetListener;\n\nfunction changeEquipmentSetListener() {\n let value = this.value\n let optgroupType = this.options[this.selectedIndex].parentNode.label;\n\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\"].forEach((type) => {\n let selectType = type;\n\n let currentEquipment = document.getElementById(\"selectEquipment_\" + selectType);\n if (type === \"feet\") {\n type = \"_boots\";\n }\n if (type === \"hands\") {\n if (optgroupType === \"RANGED\") {\n type = \"_bracers\";\n } else if (optgroupType === \"MAGIC\") {\n type = \"_gloves\";\n } else {\n type = \"_gauntlets\";\n }\n }\n if (type === \"head\") {\n if (optgroupType === \"RANGED\") {\n type = \"_hood\";\n } else if (optgroupType === \"MAGIC\") {\n type = \"_hat\";\n } else {\n type = \"_helmet\";\n }\n }\n if (type === \"legs\") {\n if (optgroupType === \"RANGED\") {\n type = \"_chaps\";\n } else if (optgroupType === \"MAGIC\") {\n type = \"_robe_bottoms\";\n } else {\n type = \"_plate_legs\";\n }\n }\n if (type === \"body\") {\n if (optgroupType === \"RANGED\") {\n type = \"_tunic\";\n } else if (optgroupType === \"MAGIC\") {\n type = \"_robe_top\";\n } else {\n type = \"_plate_body\";\n }\n }\n currentEquipment.value = \"/items/\" + value.toLowerCase() + type;\n });\n updateEquipmentState();\n updateUI();\n}\n\n// #endregion\n\n// #region Combat Stats\n\n// 存储基础属性和入战属性的对比数据\nlet baseStats = {};\nlet combatReadyStats = {};\nlet isCombatReadyMode = false;\n\n// 光环技能列表(按照后放覆盖先放的逻辑)\nconst AURA_ABILITIES = [\n \"/abilities/critical_aura\",\n \"/abilities/fierce_aura\",\n \"/abilities/guardian_aura\",\n \"/abilities/mystic_aura\",\n \"/abilities/speed_aura\"\n];\n\n// 咖啡类饮料列表(用于识别咖啡)\nfunction isCoffee(hrid) {\n return hrid && hrid.includes(\"coffee\");\n}\n\n// 计算入战时的所有永久buff\nfunction calculateCombatReadyBuffs() {\n let buffs = [];\n \n // 1. 房屋buff\n Object.entries(player.houseRooms).forEach(([hrid, level]) => {\n if (level > 0) {\n try {\n let houseRoom = new HouseRoom(hrid, level);\n houseRoom.buffs.forEach(buff => {\n buffs.push(buff);\n });\n } catch (e) {\n // 跳过无效的房间\n }\n }\n });\n \n // 2. 成就buff\n if (player.achievements && Object.keys(player.achievements).length > 0) {\n let achievement = new Achievement(player.achievements);\n achievement.buffs.forEach(buff => {\n buffs.push(buff);\n });\n }\n \n // 3. 社区经验buff\n if (document.getElementById(\"comExpToggle\").checked) {\n let comExp = Number(document.getElementById(\"comExpInput\").value);\n if (comExp > 0) {\n const comExpBuff = {\n uniqueHrid: \"/buff_uniques/experience_community_buff\",\n typeHrid: \"/buff_types/wisdom\",\n ratioBoost: 0,\n ratioBoostLevelBonus: 0,\n flatBoost: 0.005 * (comExp - 1) + 0.2,\n flatBoostLevelBonus: 0,\n duration: 0\n };\n buffs.push(new Buff(comExpBuff));\n }\n }\n \n // 4. 社区掉落buff\n if (document.getElementById(\"comDropToggle\").checked) {\n let comDrop = Number(document.getElementById(\"comDropInput\").value);\n if (comDrop > 0) {\n const comDropBuff = {\n uniqueHrid: \"/buff_uniques/combat_community_buff\",\n typeHrid: \"/buff_types/combat_drop_quantity\",\n ratioBoost: 0,\n ratioBoostLevelBonus: 0,\n flatBoost: 0.005 * (comDrop - 1) + 0.2,\n flatBoostLevelBonus: 0,\n duration: 0\n };\n buffs.push(new Buff(comDropBuff));\n }\n }\n \n // 5. MooPass buff\n if (document.getElementById(\"mooPassToggle\").checked) {\n const mooPassBuff = {\n uniqueHrid: \"/buff_uniques/experience_moo_pass_buff\",\n typeHrid: \"/buff_types/wisdom\",\n ratioBoost: 0,\n ratioBoostLevelBonus: 0,\n flatBoost: 0.05,\n flatBoostLevelBonus: 0,\n duration: 0\n };\n buffs.push(new Buff(mooPassBuff));\n }\n \n // 6. 咖啡buff(考虑饮料浓度)\n let drinkConcentration = player.combatDetails.combatStats.drinkConcentration;\n for (let i = 0; i < 3; i++) {\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\n if (drinkSelect && drinkSelect.value && isCoffee(drinkSelect.value)) {\n let drinkHrid = drinkSelect.value;\n let gameItem = itemDetailMap[drinkHrid];\n if (gameItem && gameItem.consumableDetail && gameItem.consumableDetail.buffs) {\n for (const buffData of gameItem.consumableDetail.buffs) {\n let buffCopy = structuredClone(buffData);\n // 应用饮料浓度加成\n if (drinkConcentration > 0) {\n buffCopy.ratioBoost = buffCopy.ratioBoost * (1 + drinkConcentration);\n buffCopy.flatBoost = buffCopy.flatBoost * (1 + drinkConcentration);\n }\n buffs.push(new Buff(buffCopy));\n }\n }\n }\n }\n \n // 7. 光环技能buff(从所有选中的玩家收集,按顺序处理,后放覆盖先放)\n let auraBuffMap = {}; // 用于处理覆盖\n \n // 获取所有选中的玩家(如果没有选中则默认只看当前玩家)\n let playersToCheck = [];\n const checkboxes = document.querySelectorAll('.player-checkbox');\n checkboxes.forEach(checkbox => {\n if (checkbox.checked) {\n const playerNumber = parseInt(checkbox.id.replace('player', ''));\n playersToCheck.push(playerNumber);\n }\n });\n \n // 如果没有选中任何玩家,只检查当前玩家\n if (playersToCheck.length === 0) {\n playersToCheck.push(parseInt(currentPlayerTabId));\n }\n \n // 遍历所有选中的玩家,按玩家顺序收集光环(模拟战斗时的释放顺序)\n for (const playerNumber of playersToCheck) {\n let playerData;\n \n // 如果是当前显示的玩家,从UI读取\n if (playerNumber.toString() === currentPlayerTabId) {\n for (let i = 0; i < 5; i++) {\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\n if (abilitySelect && abilitySelect.value && AURA_ABILITIES.includes(abilitySelect.value)) {\n let abilityHrid = abilitySelect.value;\n let abilityLevel = Number(document.getElementById(\"inputAbilityLevel_\" + i).value) || 1;\n let gameAbility = abilityDetailMap[abilityHrid];\n if (gameAbility && gameAbility.abilityEffects) {\n for (const effect of gameAbility.abilityEffects) {\n if (effect.buffs) {\n for (const buffData of effect.buffs) {\n let buff = new Buff(buffData, abilityLevel);\n // 使用uniqueHrid作为key,后放覆盖先放\n auraBuffMap[buff.uniqueHrid] = buff;\n }\n }\n }\n }\n }\n }\n } else {\n // 从playerDataMap读取其他玩家的配置\n try {\n playerData = JSON.parse(playerDataMap[playerNumber]);\n if (playerData && playerData.abilities) {\n for (let i = 0; i < playerData.abilities.length; i++) {\n let abilityConfig = playerData.abilities[i];\n if (abilityConfig && abilityConfig.abilityHrid && AURA_ABILITIES.includes(abilityConfig.abilityHrid)) {\n let abilityHrid = abilityConfig.abilityHrid;\n let abilityLevel = Number(abilityConfig.level) || 1;\n let gameAbility = abilityDetailMap[abilityHrid];\n if (gameAbility && gameAbility.abilityEffects) {\n for (const effect of gameAbility.abilityEffects) {\n if (effect.buffs) {\n for (const buffData of effect.buffs) {\n let buff = new Buff(buffData, abilityLevel);\n // 使用uniqueHrid作为key,后放覆盖先放\n auraBuffMap[buff.uniqueHrid] = buff;\n }\n }\n }\n }\n }\n }\n }\n } catch (e) {\n // 跳过无法解析的玩家数据\n }\n }\n }\n \n // 添加光环buff\n Object.values(auraBuffMap).forEach(buff => {\n buffs.push(buff);\n });\n \n return buffs;\n}\n\n// 创建一个临时player来计算入战属性\nfunction calculateCombatReadyPlayer() {\n // 深拷贝当前player的基础数据\n let tempPlayer = new Player();\n tempPlayer.staminaLevel = player.staminaLevel;\n tempPlayer.intelligenceLevel = player.intelligenceLevel;\n tempPlayer.attackLevel = player.attackLevel;\n tempPlayer.meleeLevel = player.meleeLevel;\n tempPlayer.defenseLevel = player.defenseLevel;\n tempPlayer.rangedLevel = player.rangedLevel;\n tempPlayer.magicLevel = player.magicLevel;\n tempPlayer.equipment = player.equipment;\n tempPlayer.food = player.food;\n tempPlayer.drinks = player.drinks;\n tempPlayer.abilities = player.abilities;\n tempPlayer.houseRooms = [];\n tempPlayer.achievements = null;\n \n // 计算基础属性\n tempPlayer.updateCombatDetails();\n \n // 收集所有入战buff\n let combatBuffs = calculateCombatReadyBuffs();\n \n // 将buff添加到permanentBuffs\n combatBuffs.forEach(buff => {\n if (tempPlayer.permanentBuffs[buff.typeHrid]) {\n tempPlayer.permanentBuffs[buff.typeHrid].flatBoost += buff.flatBoost;\n tempPlayer.permanentBuffs[buff.typeHrid].ratioBoost += buff.ratioBoost;\n } else {\n tempPlayer.permanentBuffs[buff.typeHrid] = buff;\n }\n });\n \n // 复制到combatBuffs\n tempPlayer.combatBuffs = structuredClone(tempPlayer.permanentBuffs);\n \n // 重新计算战斗属性\n tempPlayer.updateCombatDetails();\n \n return tempPlayer;\n}\n\n// 保存基础属性快照\nfunction saveBaseStats() {\n player.updateCombatDetails();\n baseStats = {\n combatDetails: structuredClone(player.combatDetails)\n };\n}\n\n// 保存入战属性快照\nfunction saveCombatReadyStats() {\n let combatReadyPlayer = calculateCombatReadyPlayer();\n combatReadyStats = {\n combatDetails: structuredClone(combatReadyPlayer.combatDetails)\n };\n}\n\n// 判断属性是否被增益(用于高亮显示)\nfunction isStatBoosted(statName, isFloor = false, isCombatStats = false) {\n if (!isCombatReadyMode) return false;\n if (!baseStats.combatDetails || !combatReadyStats.combatDetails) return false;\n \n let baseValue, combatValue;\n if (isCombatStats) {\n baseValue = baseStats.combatDetails.combatStats[statName];\n combatValue = combatReadyStats.combatDetails.combatStats[statName];\n } else {\n baseValue = baseStats.combatDetails[statName];\n combatValue = combatReadyStats.combatDetails[statName];\n }\n \n if (baseValue === undefined || combatValue === undefined) return false;\n \n if (isFloor) {\n return Math.floor(combatValue) > Math.floor(baseValue);\n }\n return combatValue > baseValue + 0.0001; // 浮点数比较容差\n}\n\n// 获取当前应该显示的属性源\nfunction getCurrentDisplayStats() {\n return isCombatReadyMode ? combatReadyStats : baseStats;\n}\n\nfunction updateCombatStatsUI() {\n // 先保存基础属性\n saveBaseStats();\n \n // 如果开启入战属性模式,计算入战属性\n if (isCombatReadyMode) {\n saveCombatReadyStats();\n }\n \n // 获取要显示的属性\n let displayStats = isCombatReadyMode ? combatReadyStats.combatDetails : player.combatDetails;\n if (!displayStats) {\n player.updateCombatDetails();\n displayStats = player.combatDetails;\n }\n\n let combatStyleElement = document.getElementById(\"combatStat_combatStyleHrid\");\n let combatStyle = displayStats.combatStats.combatStyleHrid;\n combatStyleElement.setAttribute(\"data-i18n\", \"combatStyleNames.\" + combatStyle);\n combatStyleElement.innerHTML = combatStyleDetailMap[combatStyle].name;\n\n let damageTypeElement = document.getElementById(\"combatStat_damageType\");\n let damageType = damageTypeDetailMap[displayStats.combatStats.damageType];\n damageTypeElement.setAttribute(\"data-i18n\", \"damageTypeNames.\" + damageType.hrid);\n damageTypeElement.innerHTML = damageType.name;\n\n let attackIntervalElement = document.getElementById(\"combatStat_attackInterval\");\n let attackIntervalValue = (displayStats.combatStats.attackInterval / 1e9).toLocaleString() + \"s\";\n attackIntervalElement.innerHTML = attackIntervalValue;\n // 攻速变快时也高亮(数值变小)\n if (isCombatReadyMode && baseStats.combatDetails && \n displayStats.combatStats.attackInterval < baseStats.combatDetails.combatStats.attackInterval - 1000) {\n attackIntervalElement.style.color = \"#0d6efd\";\n attackIntervalElement.style.fontWeight = \"bold\";\n } else {\n attackIntervalElement.style.color = \"\";\n attackIntervalElement.style.fontWeight = \"\";\n }\n\n let primaryTrainingElement = document.getElementById(\"combatStat_primaryTraining\");\n let primaryTraining = displayStats.combatStats.primaryTraining;\n primaryTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + primaryTraining);\n primaryTrainingElement.innerHTML = primaryTraining;\n\n let focusTrainingElement = document.getElementById(\"combatStat_focusTraining\");\n let focusTraining = displayStats.combatStats.focusTraining;\n if (focusTraining) {\n focusTrainingElement.setAttribute(\"data-i18n\", \"skillNames.\" + focusTraining);\n } else {\n focusTrainingElement.setAttribute(\"data-i18n\", \"characterSelectPage.slots.empty\");\n }\n focusTrainingElement.innerHTML = focusTraining;\n\n [\n \"maxHitpoints\",\n \"maxManapoints\",\n \"stabAccuracyRating\",\n \"stabMaxDamage\",\n \"slashAccuracyRating\",\n \"slashMaxDamage\",\n \"smashAccuracyRating\",\n \"smashMaxDamage\",\n \"rangedAccuracyRating\",\n \"rangedMaxDamage\",\n \"magicAccuracyRating\",\n \"magicMaxDamage\",\n \"defensiveMaxDamage\",\n \"stabEvasionRating\",\n \"slashEvasionRating\",\n \"smashEvasionRating\",\n \"rangedEvasionRating\",\n \"magicEvasionRating\",\n \"totalArmor\",\n \"totalWaterResistance\",\n \"totalNatureResistance\",\n \"totalFireResistance\",\n \"totalThreat\"\n ].forEach((stat) => {\n let element = document.getElementById(\"combatStat_\" + stat);\n element.innerHTML = Math.floor(displayStats[stat]);\n if (isStatBoosted(stat, true, false)) {\n element.style.color = \"#0d6efd\";\n element.style.fontWeight = \"bold\";\n } else {\n element.style.color = \"\";\n element.style.fontWeight = \"\";\n }\n });\n\n [\n \"abilityHaste\",\n \"tenacity\"\n ].forEach((stat) => {\n let element = document.getElementById(\"combatStat_\" + stat);\n element.innerHTML = Math.floor(displayStats.combatStats[stat]);\n if (isStatBoosted(stat, true, true)) {\n element.style.color = \"#0d6efd\";\n element.style.fontWeight = \"bold\";\n } else {\n element.style.color = \"\";\n element.style.fontWeight = \"\";\n }\n });\n\n [\n \"physicalAmplify\",\n \"waterAmplify\",\n \"natureAmplify\",\n \"fireAmplify\",\n \"healingAmplify\",\n \"lifeSteal\",\n \"hpRegenPer10\",\n \"mpRegenPer10\",\n \"physicalThorns\",\n \"elementalThorns\",\n \"criticalRate\",\n \"criticalDamage\",\n \"combatExperience\",\n \"taskDamage\",\n \"armorPenetration\",\n \"waterPenetration\",\n \"naturePenetration\",\n \"firePenetration\",\n \"manaLeech\",\n \"castSpeed\",\n \"parry\",\n \"mayhem\",\n \"pierce\",\n \"curse\",\n \"fury\",\n \"weaken\",\n \"ripple\",\n \"bloom\",\n \"blaze\",\n \"attackSpeed\",\n \"autoAttackDamage\",\n \"abilityDamage\",\n \"drinkConcentration\",\n \"foodHaste\",\n \"staminaExperience\",\n \"intelligenceExperience\",\n \"attackExperience\",\n \"defenseExperience\",\n \"meleeExperience\",\n \"rangedExperience\",\n \"magicExperience\"\n\n ].forEach((stat) => {\n let element = document.getElementById(\"combatStat_\" + stat);\n let value = (100 * displayStats.combatStats[stat]).toLocaleString([], {\n minimumFractionDigits: 0,\n maximumFractionDigits: 4,\n });\n element.innerHTML = value + \"%\";\n if (isStatBoosted(stat, false, true)) {\n element.style.color = \"#0d6efd\";\n element.style.fontWeight = \"bold\";\n } else {\n element.style.color = \"\";\n element.style.fontWeight = \"\";\n }\n });\n}\n\n// 初始化入战属性开关\nfunction initCombatReadyStatsToggle() {\n let toggle = document.getElementById(\"combatReadyStatsToggle\");\n if (!toggle) return;\n \n // 从localStorage读取状态\n let savedState = localStorage.getItem('combatReadyStatsEnabled');\n if (savedState === 'true') {\n toggle.checked = true;\n isCombatReadyMode = true;\n }\n \n toggle.addEventListener('change', () => {\n isCombatReadyMode = toggle.checked;\n localStorage.setItem('combatReadyStatsEnabled', toggle.checked);\n updateUI();\n });\n}\n\n// #endregion\n\n// #region Level\n\nfunction initLevelSection() {\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\n levelInput.value = 1;\n levelInput.addEventListener(\"change\", levelInputHandler);\n });\n}\n\nfunction levelInputHandler() {\n updateLevels();\n updateUI();\n}\n\nfunction updateLevels() {\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\n player[skill + \"Level\"] = Number(levelInput.value);\n });\n updateCombatLevel();\n}\n\nfunction calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel) {\n return Math.floor(\n 0.1 * (staminaLevel + intelligenceLevel + attackLevel + defenseLevel + Math.max(meleeLevel, rangedLevel, magicLevel))\n + 0.5 * Math.max(attackLevel, defenseLevel, meleeLevel, rangedLevel, magicLevel)\n );\n}\n\n\nfunction updateCombatLevel() {\n let staminaLevel = player[\"staminaLevel\"];\n let intelligenceLevel = player[\"intelligenceLevel\"];\n let defenseLevel = player[\"defenseLevel\"];\n let attackLevel = player[\"attackLevel\"];\n let meleeLevel = player[\"meleeLevel\"];\n let rangedLevel = player[\"rangedLevel\"];\n let magicLevel = player[\"magicLevel\"];\n\n let levelInput = document.getElementById(\"inputLevel_combat\");\n levelInput.value = calcCombatLevel(staminaLevel, intelligenceLevel, defenseLevel, attackLevel, meleeLevel, rangedLevel, magicLevel);;\n}\n\n// #endregion\n\n// #region Food\n\nfunction initFoodSection() {\n for (let i = 0; i < 3; i++) {\n let element = document.getElementById(\"selectFood_\" + i);\n\n let gameFoods = Object.values(itemDetailMap)\n .filter((item) => item.categoryHrid == \"/item_categories/food\")\n .sort((a, b) => a.sortIndex - b.sortIndex);\n\n for (const food of Object.values(gameFoods)) {\n let opt = new Option(food.name, food.hrid);\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + food.hrid);\n element.add(opt);\n }\n\n element.addEventListener(\"change\", foodSelectHandler);\n }\n}\n\nfunction foodSelectHandler() {\n updateFoodState();\n updateUI();\n}\n\nfunction updateFoodState() {\n for (let i = 0; i < 3; i++) {\n let foodSelect = document.getElementById(\"selectFood_\" + i);\n food[i] = foodSelect.value;\n if (food[i] && !triggerMap[food[i]]) {\n let gameItem = itemDetailMap[food[i]];\n triggerMap[food[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\n }\n }\n}\n\nfunction updateFoodUI() {\n for (let i = 0; i < 3; i++) {\n let selectElement = document.getElementById(\"selectFood_\" + i);\n let triggerButton = document.getElementById(\"buttonFoodTrigger_\" + i);\n\n selectElement.disabled = i >= player.combatDetails.combatStats.foodSlots;\n triggerButton.disabled = i >= player.combatDetails.combatStats.foodSlots || !food[i];\n }\n}\n\n// #endregion\n\n// #region Drinks\n\nfunction initDrinksSection() {\n for (let i = 0; i < 3; i++) {\n let element = document.getElementById(\"selectDrink_\" + i);\n\n let gameDrinks = Object.values(itemDetailMap)\n .filter((item) => item.categoryHrid == \"/item_categories/drink\")\n .filter((item) => item.consumableDetail.usableInActionTypeMap[\"/action_types/combat\"])\n .sort((a, b) => a.sortIndex - b.sortIndex);\n\n for (const drink of Object.values(gameDrinks)) {\n let opt = new Option(drink.name, drink.hrid);\n opt.setAttribute(\"data-i18n\", \"itemNames.\" + drink.hrid);\n element.add(opt);\n }\n\n element.addEventListener(\"change\", drinkSelectHandler);\n }\n}\n\nfunction drinkSelectHandler() {\n updateDrinksState();\n updateDrinksUI();\n // 如果开启入战属性模式,实时更新(咖啡会影响入战属性)\n if (isCombatReadyMode) {\n updateCombatStatsUI();\n }\n}\n\nfunction updateDrinksState() {\n for (let i = 0; i < 3; i++) {\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\n drinks[i] = drinkSelect.value;\n if (drinks[i] && !triggerMap[drinks[i]]) {\n let gameItem = itemDetailMap[drinks[i]];\n triggerMap[drinks[i]] = structuredClone(gameItem.consumableDetail.defaultCombatTriggers);\n }\n }\n}\n\nfunction updateDrinksUI() {\n for (let i = 0; i < 3; i++) {\n let selectElement = document.getElementById(\"selectDrink_\" + i);\n let triggerButton = document.getElementById(\"buttonDrinkTrigger_\" + i);\n\n selectElement.disabled = i >= player.combatDetails.combatStats.drinkSlots;\n triggerButton.disabled = i >= player.combatDetails.combatStats.drinkSlots || !drinks[i];\n }\n}\n\n// #endregion\n\n// #region Abilities\n\nfunction initAbilitiesSection() {\n for (let i = 0; i < 5; i++) {\n let selectElement = document.getElementById(\"selectAbility_\" + i);\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\n\n inputElement.value = 1;\n\n let gameAbilities;\n if (i == 0) {\n gameAbilities = Object.values(abilityDetailMap).filter(x => x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\n } else {\n gameAbilities = Object.values(abilityDetailMap).filter(x => !x.isSpecialAbility).sort((a, b) => a.sortIndex - b.sortIndex);\n }\n\n\n for (const ability of Object.values(gameAbilities)) {\n let opt = new Option(ability.name, ability.hrid);\n opt.setAttribute(\"data-i18n\", \"abilityNames.\" + ability.hrid);\n selectElement.add(opt);\n }\n\n selectElement.addEventListener(\"change\", abilitySelectHandler);\n // 技能等级改变时也需要更新入战属性\n inputElement.addEventListener(\"input\", abilityLevelInputHandler);\n }\n}\n\nfunction abilityLevelInputHandler() {\n // 如果开启入战属性模式,实时更新(光环技能等级会影响入战属性)\n if (isCombatReadyMode) {\n updateCombatStatsUI();\n }\n}\n\nfunction abilitySelectHandler() {\n updateAbilityState();\n updateAbilityUI();\n // 如果开启入战属性模式,实时更新(光环技能会影响入战属性)\n if (isCombatReadyMode) {\n updateCombatStatsUI();\n }\n}\n\nfunction updateAbilityState() {\n for (let i = 0; i < 5; i++) {\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\n abilities[i] = abilitySelect.value;\n if (abilities[i] && !triggerMap[abilities[i]]) {\n let gameAbility = abilityDetailMap[abilities[i]];\n triggerMap[abilities[i]] = structuredClone(gameAbility.defaultCombatTriggers);\n }\n }\n}\n\nfunction updateAbilityUI() {\n for (let i = 0; i < 5; i++) {\n let selectElement = document.getElementById(\"selectAbility_\" + i);\n let inputElement = document.getElementById(\"inputAbilityLevel_\" + i);\n let triggerButton = document.getElementById(\"buttonAbilityTrigger_\" + i);\n\n selectElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\n inputElement.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1];\n triggerButton.disabled = player.intelligenceLevel < abilitySlotsLevelRequirementList[i + 1] || !abilities[i];\n let moveUpButton = document.getElementById(\"selectAbilityMoveUp_\" + i);\n moveUpButton.onclick = () => swapAbilityOrder(i, -1);\n }\n}\n\nfunction swapAbilityOrder(abilityIndex, step) {\n const swapIndex = abilityIndex + step;\n if (swapIndex < 0 || swapIndex > 4) {\n return;\n }\n\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilityIndex);\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilityIndex);\n\n const tempAbility = abilities[abilityIndex];\n abilities[abilityIndex] = abilities[swapIndex];\n abilities[swapIndex] = tempAbility;\n\n const tempLevel = abilityLevelInput.value;\n abilityLevelInput.value = document.getElementById(\"inputAbilityLevel_\" + swapIndex).value;\n document.getElementById(\"inputAbilityLevel_\" + swapIndex).value = tempLevel;\n\n abilitySelect.value = document.getElementById(\"selectAbility_\" + (swapIndex)).value;\n document.getElementById(\"selectAbility_\" + swapIndex).value = abilities[swapIndex];\n\n updateAbilityState();\n updateAbilityUI();\n}\n\n// #endregion\n\n// #region Trigger\n\nfunction initTriggerModal() {\n let modal = document.getElementById(\"triggerModal\");\n modal.addEventListener(\"show.bs.modal\", (event) => triggerModalShownHandler(event));\n\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\n triggerSaveButton.addEventListener(\"click\", (event) => triggerModalSaveHandler(event));\n\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\n triggerAddButton.addEventListener(\"click\", (event) => triggerAddButtonHandler(event));\n\n let triggerDefaultButton = document.getElementById(\"buttonDefaultTrigger\");\n triggerDefaultButton.addEventListener(\"click\", (event) => triggerDefaultButtonHandler(event));\n\n for (let i = 0; i < 4; i++) {\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\n let triggerRemoveButton = document.getElementById(\"buttonRemoveTrigger_\" + i);\n\n triggerDependencySelect.addEventListener(\"change\", (event) => triggerDependencySelectHandler(event, i));\n triggerConditionSelect.addEventListener(\"change\", (event) => triggerConditionSelectHandler(event, i));\n triggerComparatorSelect.addEventListener(\"change\", (event) => triggerComparatorSelectHander(event, i));\n triggerValueInput.addEventListener(\"change\", (event) => triggerValueInputHandler(event, i));\n triggerRemoveButton.addEventListener(\"click\", (event) => triggerRemoveButtonHandler(event, i));\n }\n}\n\nfunction triggerModalShownHandler(event) {\n let triggerButton = event.relatedTarget;\n\n let triggerType = triggerButton.getAttribute(\"data-bs-triggertype\");\n let triggerIndex = Number(triggerButton.getAttribute(\"data-bs-triggerindex\"));\n\n let triggerTarget;\n switch (triggerType) {\n case \"food\":\n triggerTarget = food[triggerIndex];\n break;\n case \"drink\":\n triggerTarget = drinks[triggerIndex];\n break;\n case \"ability\":\n triggerTarget = abilities[triggerIndex];\n break;\n }\n\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\n triggerTargetnput.value = triggerTarget;\n modalTriggers = triggerMap[triggerTarget];\n updateTriggerModal();\n}\n\nfunction triggerModalSaveHandler(event) {\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\n let triggerTarget = triggerTargetnput.value;\n\n triggerMap[triggerTarget] = modalTriggers;\n}\n\nfunction triggerDependencySelectHandler(event, index) {\n modalTriggers[index].dependencyHrid = event.target.value;\n modalTriggers[index].conditionHrid = \"\";\n modalTriggers[index].comparatorHrid = \"\";\n modalTriggers[index].value = 0;\n\n updateTriggerModal();\n}\n\nfunction triggerConditionSelectHandler(event, index) {\n modalTriggers[index].conditionHrid = event.target.value;\n modalTriggers[index].comparatorHrid = \"\";\n modalTriggers[index].value = 0;\n\n updateTriggerModal();\n}\n\nfunction triggerComparatorSelectHander(event, index) {\n modalTriggers[index].comparatorHrid = event.target.value;\n\n updateTriggerModal();\n}\n\nfunction triggerValueInputHandler(event, index) {\n modalTriggers[index].value = Number(event.target.value);\n\n updateTriggerModal();\n}\n\nfunction triggerRemoveButtonHandler(event, index) {\n modalTriggers.splice(index, 1);\n\n updateTriggerModal();\n}\n\nfunction triggerAddButtonHandler(event) {\n if (modalTriggers.length == 4) {\n return;\n }\n\n modalTriggers.push({\n dependencyHrid: \"\",\n conditionHrid: \"\",\n comparatorHrid: \"\",\n value: 0,\n });\n\n updateTriggerModal();\n}\n\nfunction triggerDefaultButtonHandler(event) {\n let triggerTargetnput = document.getElementById(\"inputModalTriggerTarget\");\n let triggerTarget = triggerTargetnput.value;\n\n if (triggerTarget.startsWith(\"/items/\")) {\n modalTriggers = structuredClone(itemDetailMap[triggerTarget].consumableDetail.defaultCombatTriggers);\n } else {\n modalTriggers = structuredClone(abilityDetailMap[triggerTarget].defaultCombatTriggers);\n }\n\n updateTriggerModal();\n}\n\nfunction updateTriggerModal() {\n let triggerStartTextElement = document.getElementById(\"triggerStartText\");\n if (modalTriggers.length == 0) {\n triggerStartTextElement.innerHTML = \"Activate as soon as it's off cooldown\";\n } else {\n triggerStartTextElement.innerHTML = \"Activate when:\";\n }\n\n let triggerAddButton = document.getElementById(\"buttonAddTrigger\");\n triggerAddButton.disabled = modalTriggers.length == 4;\n\n let triggersValid = true;\n\n for (let i = 0; i < 4; i++) {\n let triggerElement = document.getElementById(\"modalTrigger_\" + i);\n\n if (!modalTriggers[i]) {\n hideElement(triggerElement);\n continue;\n }\n\n showElement(triggerElement);\n\n let triggerDependencySelect = document.getElementById(\"selectTriggerDependency_\" + i);\n let triggerConditionSelect = document.getElementById(\"selectTriggerCondition_\" + i);\n let triggerComparatorSelect = document.getElementById(\"selectTriggerComparator_\" + i);\n let triggerValueInput = document.getElementById(\"inputTriggerValue_\" + i);\n\n showElement(triggerDependencySelect);\n fillTriggerDependencySelect(triggerDependencySelect);\n\n if (modalTriggers[i].dependencyHrid == \"\") {\n hideElement(triggerConditionSelect);\n hideElement(triggerComparatorSelect);\n hideElement(triggerValueInput);\n triggersValid = false;\n continue;\n }\n\n triggerDependencySelect.value = modalTriggers[i].dependencyHrid;\n showElement(triggerConditionSelect);\n fillTriggerConditionSelect(triggerConditionSelect, modalTriggers[i].dependencyHrid);\n\n if (modalTriggers[i].conditionHrid == \"\") {\n hideElement(triggerComparatorSelect);\n hideElement(triggerValueInput);\n triggersValid = false;\n continue;\n }\n\n triggerConditionSelect.value = modalTriggers[i].conditionHrid;\n showElement(triggerComparatorSelect);\n fillTriggerComparatorSelect(triggerComparatorSelect, modalTriggers[i].conditionHrid);\n\n if (modalTriggers[i].comparatorHrid == \"\") {\n hideElement(triggerValueInput);\n triggersValid = false;\n continue;\n }\n\n triggerComparatorSelect.value = modalTriggers[i].comparatorHrid;\n\n if (combatTriggerComparatorDetailMap[modalTriggers[i].comparatorHrid].allowValue) {\n showElement(triggerValueInput);\n triggerValueInput.value = modalTriggers[i].value;\n } else {\n hideElement(triggerValueInput);\n }\n }\n\n let triggerSaveButton = document.getElementById(\"buttonTriggerModalSave\");\n triggerSaveButton.disabled = !triggersValid;\n\n updateContent();\n}\n\nfunction fillTriggerDependencySelect(element) {\n element.length = 0;\n element.add(new Option(\"\", \"\"));\n\n for (const dependency of Object.values(combatTriggerDependencyDetailMap).sort(\n (a, b) => a.sortIndex - b.sortIndex\n )) {\n let opt = new Option(dependency.name, dependency.hrid);\n opt.setAttribute(\"data-i18n\", \"combatTriggerDependencyNames.\" + dependency.hrid);\n element.add(opt);\n }\n}\n\nfunction fillTriggerConditionSelect(element, dependencyHrid) {\n let dependency = combatTriggerDependencyDetailMap[dependencyHrid];\n\n let conditions;\n if (dependency.isSingleTarget) {\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isSingleTarget);\n } else {\n conditions = Object.values(combatTriggerConditionDetailMap).filter((condition) => condition.isMultiTarget);\n }\n\n element.length = 0;\n element.add(new Option(\"\", \"\"));\n\n for (const condition of Object.values(conditions).sort((a, b) => a.sortIndex - b.sortIndex)) {\n let opt = new Option(condition.name, condition.hrid);\n opt.setAttribute(\"data-i18n\", \"combatTriggerConditionNames.\" + condition.hrid);\n element.add(opt);\n }\n}\n\nfunction fillTriggerComparatorSelect(element, conditionHrid) {\n let condition = combatTriggerConditionDetailMap[conditionHrid];\n\n let comparators = condition.allowedComparatorHrids.map((hrid) => combatTriggerComparatorDetailMap[hrid]);\n\n element.length = 0;\n element.add(new Option(\"\", \"\"));\n\n for (const comparator of Object.values(comparators).sort((a, b) => a.sortIndex - b.sortIndex)) {\n let opt = new Option(comparator.name, comparator.hrid);\n opt.setAttribute(\"data-i18n\", \"combatTriggerComparatorNames.\" + comparator.hrid);\n element.add(opt);\n }\n}\n\nfunction hideElement(element) {\n element.classList.remove(\"d-flex\");\n element.classList.add(\"d-none\");\n}\n\nfunction showElement(element) {\n element.classList.remove(\"d-none\");\n element.classList.add(\"d-flex\");\n}\n\n// #endregion\n\n// #region Zones\n\nfunction initZones() {\n let zoneSelect = document.getElementById(\"selectZone\");\n\n // TOOD dungeon wave spawns\n let gameZones = Object.values(actionDetailMap)\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\")\n .sort((a, b) => a.sortIndex - b.sortIndex);\n\n for (const zone of Object.values(gameZones)) {\n let opt = new Option(zone.name, zone.hrid);\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + zone.hrid);\n zoneSelect.add(opt);\n }\n\n\n let zoneCheckBox = document.getElementById(\"zoneCheckBox\");\n let checkAllZonesToggle = document.getElementById('checkAllZones');\n\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\n simAllZonesToggle.addEventListener(\"change\", (event) => {\n if (simAllZonesToggle.checked) {\n zoneCheckBox.classList.remove(\"d-none\");\n zoneCheckBox.querySelectorAll(\".zone-checkbox\").forEach(checkbox => checkbox.checked = true);\n checkAllZonesToggle.checked = true;\n } else {\n zoneCheckBox.classList.add(\"d-none\");\n }\n });\n\n let zoneHrids = Object.values(actionDetailMap)\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1)\n .sort((a, b) => a.sortIndex - b.sortIndex)\n .flat();\n\n for (const zoneHrid of zoneHrids) {\n const newZone = document.createElement('div');\n newZone.classList.add('form-check');\n newZone.innerHTML = `\n \n \n `;\n zoneCheckBox.append(newZone);\n }\n\n let checkZoneToggles = document.querySelectorAll('.zone-checkbox');\n checkAllZonesToggle.addEventListener('change', () => {\n checkZoneToggles.forEach(cb => cb.checked = checkAllZonesToggle.checked);\n });\n\n checkZoneToggles.forEach(cb =>\n cb.addEventListener('change', () => {\n checkAllZonesToggle.checked = [...checkZoneToggles].every(x => x.checked);\n })\n );\n\n\n let soloCheckBox = document.getElementById(\"soloCheckBox\");\n let checkAllSolosToggle = document.getElementById('checkAllSolos');\n\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\n simAllSoloToggle.addEventListener(\"change\", (event) => {\n if (simAllSoloToggle.checked) {\n soloCheckBox.classList.remove(\"d-none\");\n soloCheckBox.querySelectorAll(\".solo-checkbox\").forEach(checkbox => checkbox.checked = true);\n checkAllSolosToggle.checked = true;\n } else {\n soloCheckBox.classList.add(\"d-none\");\n }\n });\n\n let soloHrids = Object.values(actionDetailMap)\n .filter((action) => action.type == \"/action_types/combat\" && action.category != \"/action_categories/combat/dungeons\" && action.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount == 1)\n .sort((a, b) => a.sortIndex - b.sortIndex)\n .flat();\n\n for (const zoneHrid of soloHrids) {\n const newZone = document.createElement('div');\n newZone.classList.add('form-check');\n newZone.innerHTML = `\n \n \n `;\n soloCheckBox.append(newZone);\n }\n\n let checkSoloToggles = document.querySelectorAll('.solo-checkbox');\n checkAllSolosToggle.addEventListener('change', () => {\n checkSoloToggles.forEach(cb => cb.checked = checkAllSolosToggle.checked);\n });\n\n checkSoloToggles.forEach(cb =>\n cb.addEventListener('change', () => {\n checkAllSolosToggle.checked = [...checkSoloToggles].every(x => x.checked);\n })\n );\n}\n\nfunction initDungeons() {\n let dungeonSelect = document.getElementById(\"selectDungeon\");\n\n let gameDungeons = Object.values(actionDetailMap)\n .filter((action) => action.type == \"/action_types/combat\" && action.category == \"/action_categories/combat/dungeons\")\n .sort((a, b) => a.sortIndex - b.sortIndex);\n\n for (const dungeon of Object.values(gameDungeons)) {\n let opt = new Option(dungeon.name, dungeon.hrid);\n opt.setAttribute(\"data-i18n\", \"actionNames.\" + dungeon.hrid);\n dungeonSelect.add(opt);\n }\n}\n\n// #endregion\n\n// #region Simulation Result\n\nfunction createDamageDoneAccordion(enemyIndex) {\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageDoneAccordionEnemy${enemyIndex}`);\n\n const colDiv = createElement('div', 'col');\n const accordionMainDiv = createElement('div', 'accordion');\n const accordionItemDiv = createElement('div', 'accordion-item');\n\n const headerH2 = createElement('h2', 'accordion-header');\n const button = createElement('button', 'accordion-button collapsed',\n `Damage Done (Enemy ${enemyIndex})`,\n `buttonSimulationResultDamageDoneAccordionEnemy${enemyIndex}`\n );\n button.setAttribute('type', 'button');\n button.setAttribute('data-bs-toggle', 'collapse');\n button.setAttribute('data-bs-target', `#collapseDamageDone${enemyIndex}`);\n button.style.padding = '0.5em';\n\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageDone${enemyIndex}`);\n const accordionBodyDiv = createElement('div', 'accordion-body');\n\n const headerRow = createElement('div', 'row');\n headerRow.innerHTML = `\n
Source
\n
Hitchance
\n
DPS
\n
%
\n `;\n\n const resultDiv = createElement('div', '', '', `simulationResultDamageDoneEnemy${enemyIndex}`);\n\n accordionBodyDiv.appendChild(headerRow);\n accordionBodyDiv.appendChild(resultDiv);\n collapseDiv.appendChild(accordionBodyDiv);\n headerH2.appendChild(button);\n accordionItemDiv.appendChild(headerH2);\n accordionItemDiv.appendChild(collapseDiv);\n accordionMainDiv.appendChild(accordionItemDiv);\n colDiv.appendChild(accordionMainDiv);\n accordionDiv.appendChild(colDiv);\n\n return accordionDiv;\n}\nfunction createDamageTakenAccordion(enemyIndex) {\n const accordionDiv = createElement('div', 'row d-none', '', `simulationResultDamageTakenAccordionEnemy${enemyIndex}`);\n\n const colDiv = createElement('div', 'col');\n const accordionMainDiv = createElement('div', 'accordion');\n const accordionItemDiv = createElement('div', 'accordion-item');\n\n const headerH2 = createElement('h2', 'accordion-header');\n const button = createElement('button', 'accordion-button collapsed',\n `Damage Taken (Enemy ${enemyIndex})`,\n `buttonSimulationResultDamageTakenAccordionEnemy${enemyIndex}`\n );\n button.setAttribute('type', 'button');\n button.setAttribute('data-bs-toggle', 'collapse');\n button.setAttribute('data-bs-target', `#collapseDamageTaken${enemyIndex}`);\n button.style.padding = '0.5em';\n\n const collapseDiv = createElement('div', 'accordion-collapse collapse', '', `collapseDamageTaken${enemyIndex}`);\n const accordionBodyDiv = createElement('div', 'accordion-body');\n\n const headerRow = createElement('div', 'row');\n headerRow.innerHTML = `\n
Source
\n
Hitchance
\n
DPS
\n
%
\n `;\n\n const resultDiv = createElement('div', '', '', `simulationResultDamageTakenEnemy${enemyIndex}`);\n\n accordionBodyDiv.appendChild(headerRow);\n accordionBodyDiv.appendChild(resultDiv);\n collapseDiv.appendChild(accordionBodyDiv);\n headerH2.appendChild(button);\n accordionItemDiv.appendChild(headerH2);\n accordionItemDiv.appendChild(collapseDiv);\n accordionMainDiv.appendChild(accordionItemDiv);\n colDiv.appendChild(accordionMainDiv);\n accordionDiv.appendChild(colDiv);\n\n return accordionDiv;\n}\n\n\nfunction initDamageDoneTaken() {\n for (let i = 64; i > 0; i--) {\n document.getElementById(\"simulationResultTotalDamageDone\").insertAdjacentElement('afterend', createDamageDoneAccordion(i));\n document.getElementById(\"simulationResultTotalDamageTaken\").insertAdjacentElement('afterend', createDamageTakenAccordion(i));\n }\n}\n\nfunction showSimulationResult(simResult) {\n currentSimResults = simResult;\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\n expensesModalTable.innerHTML = 'ItemPriceAmountTotal';\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\n revenueModalTable.innerHTML = 'ItemPriceAmountTotal';\n let noRngRevenueModalTable = document.querySelector(\"#noRngRevenueTable > tbody\");\n noRngRevenueModalTable.innerHTML = 'ItemPriceAmountTotal';\n let playerToDisplay = \"player1\";\n if (selectedPlayers.includes(parseInt(currentPlayerTabId))) {\n playerToDisplay = \"player\" + currentPlayerTabId;\n }\n if (!simResult.dropRateMultiplier[playerToDisplay]) {\n return;\n }\n\n showKills(simResult, playerToDisplay);\n showDeaths(simResult, playerToDisplay);\n showExperienceGained(simResult, playerToDisplay);\n showConsumablesUsed(simResult, playerToDisplay);\n showHpSpent(simResult, playerToDisplay);\n showManaUsed(simResult, playerToDisplay);\n showHitpointsGained(simResult, playerToDisplay);\n showManapointsGained(simResult, playerToDisplay);\n showDamageDone(simResult, playerToDisplay);\n showDamageTaken(simResult, playerToDisplay);\n renderWipeEvents(simResult);\n window.profit = window.revenue - window.expenses;\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\n window.noRngProfit = window.noRngRevenue - window.expenses;\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\n \n // 显示战斗图表\n if (document.getElementById('hpMpVisualizationToggle').checked) {\n renderCombatCharts(simResult);\n }\n}\n\nfunction showAllSimulationResults(simResults) {\n let displaySimResults = manipulateSimResultsDataForDisplay(simResults);\n updateAllSimsModal(displaySimResults);\n}\n\n// #region 战斗图表功能\n\nlet combatCharts = {\n hpChart: null,\n mpChart: null\n};\n\nlet lastUpdateTime = 0;\nconst UPDATE_INTERVAL = 1000; // 每秒更新一次图表\n\n// 实时更新图表\nfunction updateChartsRealtime(timeSeriesData) {\n // 节流:避免过于频繁的更新\n const now = Date.now();\n if (now - lastUpdateTime < UPDATE_INTERVAL) {\n return;\n }\n lastUpdateTime = now;\n \n if (!timeSeriesData || !timeSeriesData.timestamps || timeSeriesData.timestamps.length === 0) {\n return;\n }\n \n // 显示图表容器\n const container = document.getElementById('combatChartsContainer');\n if (container) {\n container.classList.remove('d-none');\n }\n \n // 如果图表不存在,先创建\n if (!combatCharts.hpChart || !combatCharts.mpChart) {\n initializeRealtimeCharts();\n // 等待下一次更新周期再更新数据\n return;\n }\n \n const timeLabels = timeSeriesData.timestamps.map(t => (t / ONE_SECOND).toFixed(1));\n const playerIds = Object.keys(timeSeriesData.players);\n \n // 生成颜色方案\n const colors = [\n { border: 'rgb(75, 192, 192)', bg: 'rgba(75, 192, 192, 0.2)' },\n { border: 'rgb(255, 99, 132)', bg: 'rgba(255, 99, 132, 0.2)' },\n { border: 'rgb(54, 162, 235)', bg: 'rgba(54, 162, 235, 0.2)' },\n { border: 'rgb(255, 206, 86)', bg: 'rgba(255, 206, 86, 0.2)' },\n { border: 'rgb(153, 102, 255)', bg: 'rgba(153, 102, 255, 0.2)' }\n ];\n \n // 重建datasets以确保完整更新\n const hpDatasets = playerIds.map((playerId, index) => {\n const playerData = timeSeriesData.players[playerId];\n return {\n label: playerId + ' HP',\n data: playerData.hp,\n borderColor: colors[index % colors.length].border,\n backgroundColor: colors[index % colors.length].bg,\n borderWidth: 2,\n pointRadius: 0,\n tension: 0.1\n };\n });\n \n const mpDatasets = playerIds.map((playerId, index) => {\n const playerData = timeSeriesData.players[playerId];\n return {\n label: playerId + ' MP',\n data: playerData.mp,\n borderColor: colors[index % colors.length].border,\n backgroundColor: colors[index % colors.length].bg,\n borderWidth: 2,\n pointRadius: 0,\n tension: 0.1\n };\n });\n \n // 更新HP图表\n combatCharts.hpChart.data.labels = timeLabels;\n combatCharts.hpChart.data.datasets = hpDatasets;\n combatCharts.hpChart.options.plugins.legend.display = true;\n combatCharts.hpChart.options.plugins.title.text = i18next.t('common:Experiment.hpOverTime');\n combatCharts.hpChart.update('none');\n \n // 更新MP图表\n combatCharts.mpChart.data.labels = timeLabels;\n combatCharts.mpChart.data.datasets = mpDatasets;\n combatCharts.mpChart.options.plugins.legend.display = true;\n combatCharts.mpChart.options.plugins.title.text = i18next.t('common:Experiment.mpOverTime');\n combatCharts.mpChart.update('none');\n}\n\nfunction renderCombatCharts(simResult) {\n // 显示图表容器\n const container = document.getElementById('combatChartsContainer');\n if (container) {\n container.classList.remove('d-none');\n }\n \n if (!simResult.timeSeriesData || !simResult.timeSeriesData.timestamps || simResult.timeSeriesData.timestamps.length === 0) {\n // 显示空状态\n showEmptyCharts();\n return;\n }\n \n const timeLabels = simResult.timeSeriesData.timestamps.map(t => (t / ONE_SECOND).toFixed(1));\n \n // 获取所有玩家\n const playerIds = Object.keys(simResult.timeSeriesData.players);\n \n // 生成颜色方案\n const colors = [\n { border: 'rgb(75, 192, 192)', bg: 'rgba(75, 192, 192, 0.2)' },\n { border: 'rgb(255, 99, 132)', bg: 'rgba(255, 99, 132, 0.2)' },\n { border: 'rgb(54, 162, 235)', bg: 'rgba(54, 162, 235, 0.2)' },\n { border: 'rgb(255, 206, 86)', bg: 'rgba(255, 206, 86, 0.2)' },\n { border: 'rgb(153, 102, 255)', bg: 'rgba(153, 102, 255, 0.2)' }\n ];\n \n // HP图表\n destroyChart('hpChart');\n const hpDatasets = playerIds.map((playerId, index) => {\n const playerData = simResult.timeSeriesData.players[playerId];\n return {\n label: playerId + ' HP',\n data: playerData.hp,\n borderColor: colors[index % colors.length].border,\n backgroundColor: colors[index % colors.length].bg,\n borderWidth: 2,\n pointRadius: 0,\n tension: 0.1\n };\n });\n \n combatCharts.hpChart = new Chart(document.getElementById('hpChart'), {\n type: 'line',\n data: {\n labels: timeLabels,\n datasets: hpDatasets\n },\n options: getChartOptions(i18next.t('common:Experiment.hpOverTime'), i18next.t('common:Experiment.timeInSeconds'), 'HP')\n });\n \n // MP图表\n destroyChart('mpChart');\n const mpDatasets = playerIds.map((playerId, index) => {\n const playerData = simResult.timeSeriesData.players[playerId];\n return {\n label: playerId + ' MP',\n data: playerData.mp,\n borderColor: colors[index % colors.length].border,\n backgroundColor: colors[index % colors.length].bg,\n borderWidth: 2,\n pointRadius: 0,\n tension: 0.1\n };\n });\n \n combatCharts.mpChart = new Chart(document.getElementById('mpChart'), {\n type: 'line',\n data: {\n labels: timeLabels,\n datasets: mpDatasets\n },\n options: getChartOptions(i18next.t('common:Experiment.mpOverTime'), i18next.t('common:Experiment.timeInSeconds'), 'MP')\n });\n}\n\nfunction destroyChart(chartName) {\n if (combatCharts[chartName]) {\n combatCharts[chartName].destroy();\n combatCharts[chartName] = null;\n }\n}\n\nfunction getChartOptions(title, xLabel, yLabel) {\n return {\n responsive: true,\n maintainAspectRatio: true,\n plugins: {\n legend: {\n display: true,\n position: 'top',\n labels: {\n color: '#eee',\n font: {\n size: 11\n }\n }\n },\n title: {\n display: true,\n text: title,\n color: '#eee',\n font: {\n size: 14\n }\n }\n },\n scales: {\n x: {\n display: true,\n title: {\n display: true,\n text: xLabel,\n color: '#eee'\n },\n ticks: {\n color: '#ccc',\n maxTicksLimit: 10\n },\n grid: {\n color: 'rgba(255, 255, 255, 0.1)'\n }\n },\n y: {\n display: true,\n title: {\n display: true,\n text: yLabel,\n color: '#eee'\n },\n ticks: {\n color: '#ccc'\n },\n grid: {\n color: 'rgba(255, 255, 255, 0.1)'\n }\n }\n },\n interaction: {\n intersect: false,\n mode: 'index'\n }\n };\n}\n\n// 初始化实时图表(用于模拟过程中更新)\nfunction initializeRealtimeCharts() {\n // 销毁现有图表\n destroyChart('hpChart');\n destroyChart('mpChart');\n \n const hpCanvas = document.getElementById('hpChart');\n const mpCanvas = document.getElementById('mpChart');\n \n if (!hpCanvas || !mpCanvas) {\n console.warn('图表canvas元素未找到');\n return;\n }\n \n // 显示等待状态\n const emptyOptions = {\n responsive: true,\n maintainAspectRatio: true,\n plugins: {\n legend: { display: false },\n title: {\n display: true,\n text: i18next.t('common:Experiment.waitingForData'),\n color: '#888',\n font: { size: 14 }\n }\n },\n scales: {\n x: {\n display: true,\n ticks: { color: '#555' },\n grid: { color: 'rgba(255, 255, 255, 0.05)' }\n },\n y: {\n display: true,\n ticks: { color: '#555' },\n grid: { color: 'rgba(255, 255, 255, 0.05)' }\n }\n }\n };\n \n try {\n combatCharts.hpChart = new Chart(hpCanvas, {\n type: 'line',\n data: { labels: [], datasets: [] },\n options: emptyOptions\n });\n \n combatCharts.mpChart = new Chart(mpCanvas, {\n type: 'line',\n data: { labels: [], datasets: [] },\n options: emptyOptions\n });\n } catch (e) {\n console.error('创建图表时出错:', e);\n }\n}\n\n// 显示空图表状态\nfunction showEmptyCharts() {\n initializeRealtimeCharts();\n}\n\n// 初始化HP/MP可视化开关事件\nfunction initHpMpVisualization() {\n const toggle = document.getElementById('hpMpVisualizationToggle');\n const container = document.getElementById('combatChartsContainer');\n \n if (toggle && container) {\n toggle.addEventListener('change', function() {\n if (this.checked) {\n container.classList.remove('d-none');\n showEmptyCharts();\n } else {\n container.classList.add('d-none');\n destroyChart('hpChart');\n destroyChart('mpChart');\n }\n });\n }\n}\n\n// #endregion\n\nfunction manipulateSimResultsDataForDisplay(simResults) {\n let displaySimResults = [];\n for (let i = 0; i < simResults.length; i++) {\n for (let j = 0; j < selectedPlayers.length; j++) {\n let playerToDisplay = \"player\" + selectedPlayers[j].toString();\n let simResult = simResults[i];\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\n let zoneName = simResult.zoneName;\n let difficultyTier = simResult.difficultyTier;\n let encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\n let playerDeaths = simResult.deaths[playerToDisplay] ?? 0;\n let deathsPerHour = (playerDeaths / hoursSimulated).toFixed(2);\n\n let totalExperience = 0;\n if (simResult.experienceGained[playerToDisplay]) {\n totalExperience = Object.values(simResult.experienceGained[playerToDisplay]).reduce((prev, cur) => prev + cur, 0);\n }\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\n\n let experiencePerHour = {};\n const skills = [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"];\n skills.forEach((skill) => {\n const skillLower = skill.toLowerCase();\n let experience = simResult.experienceGained[playerToDisplay]?.[skillLower] ?? 0;\n let experiencePerHourValue = 0;\n if (experience != 0) {\n experiencePerHourValue = (experience / hoursSimulated).toFixed(0);\n }\n experiencePerHour[skill] = experiencePerHourValue;\n });\n getDropProfit(simResult, playerToDisplay);\n let noRngRevenue = simResult[\"noRngRevenue\"];\n let noRngProfit = simResult[\"noRngProfit\"];\n let expenses = simResult[\"expenses\"];\n\n let displaySimRow = {\n \"ZoneName\": zoneName, \"DifficultyTier\": difficultyTier, \"Player\": playerToDisplay, \"Encounters\": encountersPerHour, \"Deaths\": deathsPerHour,\n \"TotalExperience\": totalExperiencePerHour, \"Stamina\": experiencePerHour[\"Stamina\"],\n \"Intelligence\": experiencePerHour[\"Intelligence\"], \"Attack\": experiencePerHour[\"Attack\"],\n \"Magic\": experiencePerHour[\"Magic\"], \"Ranged\": experiencePerHour[\"Ranged\"],\n \"Melee\": experiencePerHour[\"Melee\"], \"Defense\": experiencePerHour[\"Defense\"],\n \"noRngRevenue\": noRngRevenue,\n \"expenses\": expenses,\n \"noRngProfit\": noRngProfit\n };\n displaySimResults.push(displaySimRow);\n }\n }\n return displaySimResults;\n}\n\nfunction fidDropAmount(dropAmount) {\n if (Number.isInteger(dropAmount)) return dropAmount;\n\n const intPart = Math.floor(dropAmount);\n const fracPart = dropAmount - intPart;\n return Math.random() < fracPart ? intPart + 1 : intPart;\n}\n\nfunction calcDropMaps(simResult, playerToDisplay) {\n let dropRateMultiplier = simResult.dropRateMultiplier[playerToDisplay];\n let rareFindMultiplier = simResult.rareFindMultiplier[playerToDisplay];\n let combatDropQuantity = simResult.combatDropQuantity[playerToDisplay];\n let debuffOnLevelGap = simResult.debuffOnLevelGap[playerToDisplay];\n\n let numberOfPlayers = simResult.numberOfPlayers;\n let monsters = Object.keys(simResult.deaths)\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\n .sort();\n\n const totalDropMap = new Map();\n const noRngTotalDropMap = new Map();\n for (const monster of monsters) {\n const dropMap = new Map();\n const rareDropMap = new Map();\n if (combatMonsterDetailMap[monster].dropTable) {\n for (const drop of combatMonsterDetailMap[monster].dropTable) {\n if (drop.minDifficultyTier > simResult.difficultyTier) {\n continue;\n }\n\n let multiplier = 1.0 + 0.1 * simResult.difficultyTier;\n let dropRate = Math.min(1.0, multiplier * (drop.dropRate + (drop.dropRatePerDifficultyTier ?? 0) * simResult.difficultyTier));\n if (dropRate <= 0) continue;\n\n dropMap.set(drop.itemHrid, { \"dropRate\": Math.min(1.0, dropRate * dropRateMultiplier), \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\n }\n if (combatMonsterDetailMap[monster].rareDropTable)\n for (const drop of combatMonsterDetailMap[monster].rareDropTable) {\n if (drop.minDifficultyTier > simResult.difficultyTier) {\n continue;\n }\n rareDropMap.set(drop.itemHrid, { \"dropRate\": drop.dropRate * rareFindMultiplier, \"number\": 0, \"dropMin\": drop.minCount, \"dropMax\": drop.maxCount, \"noRngDropAmount\": 0 });\n }\n\n for (let dropObject of dropMap.values()) {\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\n\n }\n for (let dropObject of rareDropMap.values()) {\n dropObject.noRngDropAmount += simResult.deaths[monster] * dropObject.dropRate * ((dropObject.dropMax + dropObject.dropMin) / 2) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity) / numberOfPlayers;\n }\n\n for (let i = 0; i < simResult.deaths[monster]; i++) {\n for (let dropObject of dropMap.values()) {\n let chance = Math.random();\n if (chance <= dropObject.dropRate / numberOfPlayers) {\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\n dropObject.number = dropObject.number + fidDropAmount(amount);\n }\n }\n for (let dropObject of rareDropMap.values()) {\n let chance = Math.random();\n if (chance <= dropObject.dropRate / numberOfPlayers) {\n let amount = Math.floor(Math.random() * (dropObject.dropMax - dropObject.dropMin + 1) + dropObject.dropMin) * (1 + debuffOnLevelGap) * (1 + combatDropQuantity);\n dropObject.number = dropObject.number + fidDropAmount(amount);\n }\n }\n }\n for (let [name, dropObject] of dropMap.entries()) {\n if (totalDropMap.has(name)) {\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\n } else {\n totalDropMap.set(name, dropObject.number);\n }\n if (noRngTotalDropMap.has(name)) {\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\n } else {\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\n }\n }\n for (let [name, dropObject] of rareDropMap.entries()) {\n if (totalDropMap.has(name)) {\n totalDropMap.set(name, totalDropMap.get(name) + dropObject.number);\n } else {\n totalDropMap.set(name, dropObject.number);\n }\n if (noRngTotalDropMap.has(name)) {\n noRngTotalDropMap.set(name, noRngTotalDropMap.get(name) + dropObject.noRngDropAmount);\n } else {\n noRngTotalDropMap.set(name, dropObject.noRngDropAmount);\n }\n }\n }\n }\n\n return { totalDropMap, noRngTotalDropMap };\n}\n\nfunction getDropProfit(simResult, playerToDisplay) {\n let { totalDropMap, noRngTotalDropMap } = calcDropMaps(simResult, playerToDisplay);\n\n let noRngTotal = 0;\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\n let price = -1;\n let revenueSetting = document.getElementById('selectPrices_drops').value;\n if (window.prices) {\n let item = window.prices[name];\n if (item) {\n if (revenueSetting == 'bid') {\n if (item['bid'] !== -1) {\n price = item['bid'];\n } else if (item['ask'] !== -1) {\n price = item['ask'];\n }\n } else if (revenueSetting == 'ask') {\n if (item['ask'] !== -1) {\n price = item['ask'];\n } else if (item['bid'] !== -1) {\n price = item['bid'];\n }\n }\n if (price == -1) {\n price = item['vendor'];\n }\n }\n }\n noRngTotal += price * dropAmount;\n }\n\n let consumablesUsed = simResult.consumablesUsed?.[playerToDisplay];\n\n if (consumablesUsed) {\n consumablesUsed = Object.entries(consumablesUsed).sort((a, b) => b[1] - a[1]);\n } else {\n consumablesUsed = [];\n }\n\n let expenses = 0;\n for (const [consumable, amount] of consumablesUsed) {\n let price = -1;\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\n if (window.prices) {\n let item = window.prices[consumable];\n if (item) {\n if (expensesSetting == 'bid') {\n if (item['bid'] !== -1) {\n price = item['bid'];\n } else if (item['ask'] !== -1) {\n price = item['ask'];\n }\n } else if (expensesSetting == 'ask') {\n if (item['ask'] !== -1) {\n price = item['ask'];\n } else if (item['bid'] !== -1) {\n price = item['bid'];\n }\n }\n if (price == -1) {\n price = item['vendor'];\n }\n }\n }\n expenses += price * amount;\n }\n\n simResult[\"noRngRevenue\"] = (noRngTotal).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n simResult[\"expenses\"] = (expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n simResult[\"noRngProfit\"] = (noRngTotal - expenses).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n}\n\nfunction updateAllSimsModal(data) {\n const tableBody = document.getElementById('allZonesData').getElementsByTagName('tbody')[0];\n tableBody.innerHTML = '';\n data.forEach(item => {\n const row = document.createElement('tr');\n\n Object.keys(item).forEach(key => {\n const cell = document.createElement('td');\n cell.textContent = item[key];\n if (key === 'ZoneName') {\n cell.setAttribute(\"data-i18n\", \"actionNames.\" + item[key]);\n }\n row.appendChild(cell);\n });\n\n tableBody.appendChild(row);\n });\n\n const table = document.getElementById('allZonesData');\n const rows = table.getElementsByTagName('tr');\n const numCols = rows[0].cells.length;\n\n // 遍历每一列\n for (let col = 5; col < numCols; col++) {\n let max = -Infinity;\n let maxCell = null;\n\n // 找到最大值及其单元格\n for (let row = 1; row < rows.length; row++) {\n const cell = rows[row].cells[col];\n const value = parseFloat(cell.textContent.replace(/,/g, ''));\n if (value > max) {\n max = value;\n maxCell = cell;\n }\n }\n\n // 将最大值单元格的背景色设置为绿色\n if (maxCell && max != 0) {\n maxCell.style.backgroundColor = 'green';\n maxCell.style.color = 'white'; // 设置文字颜色为白色以提高可读性\n }\n }\n}\n\nlet currentSortColumn = null;\nlet currentSortDirection = 'desc';\n\nfunction sortTable(tableId, columnIndex, direction) {\n const table = document.getElementById(tableId);\n const tbody = table.querySelector('tbody');\n const rows = Array.from(tbody.querySelectorAll('tr'));\n\n const sortedRows = rows.sort((rowA, rowB) => {\n const cellA = rowA.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\n const cellB = rowB.children[columnIndex].textContent.trim().replace(/[\\s,]/g, '');\n\n const valueA = parseFloat(cellA.replace(/,/g, ''));\n const valueB = parseFloat(cellB.replace(/,/g, ''));\n\n return direction === 'asc' ? valueA - valueB : valueB - valueA;\n });\n\n sortedRows.forEach(row => tbody.appendChild(row));\n updateSortIndicators(tableId, columnIndex, direction);\n}\n\nfunction updateSortIndicators(tableId, columnIndex, direction) {\n const headers = document.querySelectorAll(`#${tableId} th`);\n headers.forEach((header, index) => {\n header.classList.remove('sort-asc', 'sort-desc');\n if (index === columnIndex) {\n header.classList.add(direction === 'asc' ? 'sort-asc' : 'sort-desc');\n }\n });\n}\n\ndocument.querySelectorAll('#allZonesData th').forEach((header, index) => {\n if (index === 0) return;\n if (index === 1) return;\n if (index === 2) return;\n\n header.addEventListener('click', () => {\n if (currentSortColumn === index) {\n currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';\n } else {\n currentSortColumn = index;\n currentSortDirection = 'desc';\n }\n sortTable('allZonesData', currentSortColumn, currentSortDirection);\n });\n});\n\ndocument.getElementById('buttonExportResults').addEventListener('click', function () {\n var table = document.getElementById('allZonesData');\n var csv = [];\n var rows = table.querySelectorAll('tr');\n\n for (var i = 0; i < rows.length; i++) {\n var row = rows[i];\n var cols = row.querySelectorAll('th, td');\n var csvRow = [];\n\n cols.forEach(function (col) {\n csvRow.push('\"' + col.innerText.replace(/\"/g, '\"\"') + '\"');\n });\n\n csv.push(csvRow.join(','));\n }\n\n var csvFile = new Blob([csv.join('\\n')], { type: 'text/csv' });\n var downloadLink = document.createElement('a');\n downloadLink.download = 'simData.csv';\n downloadLink.href = URL.createObjectURL(csvFile);\n downloadLink.style.display = 'none';\n document.body.appendChild(downloadLink);\n downloadLink.click();\n document.body.removeChild(downloadLink);\n});\n\nfunction showKills(simResult, playerToDisplay) {\n let resultDiv = document.getElementById(\"simulationResultKills\");\n let dropsResultDiv = document.getElementById(\"simulationResultDrops\");\n let noRngDropsResultDiv = document.getElementById(\"noRngDrops\");\n let newChildren = [];\n let newDropChildren = [];\n let newNoRngDropChildren = [];\n\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\n let encountersPerHour = 0;\n let encountersRow = null;\n if (simResult.isDungeon) {\n let wavesCompletedRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Wave Reached\", simResult.maxWaveReached]);\n wavesCompletedRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxWaveReached\");\n newChildren.push(wavesCompletedRow);\n let completedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Completed Dungeons\", simResult.dungeonsCompleted]);\n completedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsCompleted\");\n newChildren.push(completedDungeonsRow);\n if (simResult.dungeonsFailed > 0) {\n let failedDungeonsRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Failed Dungeons\", simResult.dungeonsFailed]);\n failedDungeonsRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.dungeonsFailed\");\n newChildren.push(failedDungeonsRow);\n }\n encountersPerHour = (simResult.dungeonsCompleted / hoursSimulated).toFixed(1);\n let averageTime = (hoursSimulated * 60 / simResult.dungeonsCompleted).toFixed(1);\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Average Time\", averageTime]);\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.averageTime\");\n if (simResult.minDungenonTime > 0) {\n let minimumTime = (simResult.minDungenonTime / ONE_SECOND / 60).toFixed(1);\n let minimumTimeRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Minimum Time\", minimumTime]);\n minimumTimeRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.minimumTime\");\n newChildren.push(minimumTimeRow);\n }\n } else {\n encountersPerHour = (simResult.encounters / hoursSimulated).toFixed(1);\n encountersRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Encounters\", encountersPerHour]);\n encountersRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.encounters\");\n }\n\n if (simResult.maxEnrageStack > 0) {\n let enrageRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Max Enrage Stack\", simResult.maxEnrageStack]);\n enrageRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.maxEnrageStack\");\n newChildren.push(enrageRow);\n }\n\n if (simResult.debuffOnLevelGap[playerToDisplay] != 0) {\n let debuffOnLevelGapRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Debuff on Level Gap\", Math.round(simResult.debuffOnLevelGap[playerToDisplay] * 100) + \"%\"]);\n debuffOnLevelGapRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.debuffOnLevelGap\");\n newChildren.push(debuffOnLevelGapRow);\n }\n\n newChildren.push(encountersRow);\n\n Object.keys(simResult.deaths)\n .filter(enemy => enemy !== \"player1\" && enemy !== \"player2\" && enemy !== \"player3\" && enemy !== \"player4\" && enemy !== \"player5\")\n .sort()\n .forEach(monster => {\n let killsPerHour = (simResult.deaths[monster] / hoursSimulated).toFixed(1);\n let monsterRow = createRow(\n [\"col-md-6\", \"col-md-6 text-end\"],\n [combatMonsterDetailMap[monster].name, killsPerHour]\n );\n monsterRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + monster);\n newChildren.push(monsterRow);\n });\n\n let { totalDropMap, noRngTotalDropMap } = !simResult.isDungeon ? calcDropMaps(simResult, playerToDisplay) : {totalDropMap:new Map(), noRngTotalDropMap:new Map()};\n\n let revenueModalTable = document.querySelector(\"#revenueTable > tbody\");\n let total = 0;\n for (let [name, dropAmount] of totalDropMap.entries()) {\n let dropRow = createRow(\n [\"col-md-6\", \"col-md-6 text-end\"],\n [name, dropAmount.toLocaleString()]\n );\n dropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\n newDropChildren.push(dropRow);\n\n let tableRow = ' tbody\");\n let noRngTotal = 0;\n for (let [name, dropAmount] of noRngTotalDropMap.entries()) {\n let noRngDropRow = createRow(\n [\"col-md-6\", \"col-md-6 text-end\"],\n [name, dropAmount.toLocaleString()]\n );\n noRngDropRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + name);\n newNoRngDropChildren.push(noRngDropRow);\n\n let tableRow = ' prev + cur, 0);\n }\n let totalExperiencePerHour = (totalExperience / hoursSimulated).toFixed(0);\n let totalRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Total\", totalExperiencePerHour]);\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\n newChildren.push(totalRow);\n\n [\"Stamina\", \"Intelligence\", \"Attack\", \"Melee\", \"Defense\", \"Ranged\", \"Magic\"].forEach((skill) => {\n let experience = simResult.experienceGained[playerToDisplay]?.[skill.toLowerCase()] ?? 0;\n if (experience == 0) {\n return;\n }\n let experiencePerHour = (experience / hoursSimulated).toFixed(0);\n let experienceRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [skill, experiencePerHour]);\n experienceRow.firstElementChild.setAttribute(\"data-i18n\", \"leaderboardCategoryNames.\" + skill.toLowerCase());\n newChildren.push(experienceRow);\n });\n\n resultDiv.replaceChildren(...newChildren);\n}\n\nfunction showHpSpent(simResult, playerToDisplay) {\n let hpSpentHeadingDiv = document.getElementById(\"simulationHpSpentHeading\");\n hpSpentHeadingDiv.classList.add(\"d-none\");\n let hpSpentDiv = document.getElementById(\"simulationHpSpent\");\n hpSpentDiv.classList.add(\"d-none\");\n\n if (simResult.hitpointsSpent[playerToDisplay]) {\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\n let hpSpentSources = [];\n for (const source of Object.keys(simResult.hitpointsSpent[playerToDisplay])) {\n let hpSpentPerHour = (simResult.hitpointsSpent[playerToDisplay][source] / hoursSimulated).toFixed(2);\n let hpSpentRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [abilityDetailMap[source].name, hpSpentPerHour]);\n hpSpentRow.firstElementChild.setAttribute(\"data-i18n\", \"abilityNames.\" + source);\n hpSpentSources.push(hpSpentRow);\n }\n hpSpentDiv.replaceChildren(...hpSpentSources);\n hpSpentHeadingDiv.classList.remove(\"d-none\");\n hpSpentDiv.classList.remove(\"d-none\");\n }\n}\n\nfunction showConsumablesUsed(simResult, playerToDisplay) {\n let resultDiv = document.getElementById(\"simulationResultConsumablesUsed\");\n let newChildren = [];\n\n let hoursSimulated = simResult.simulatedTime / ONE_HOUR;\n\n if (!simResult.consumablesUsed[playerToDisplay]) {\n resultDiv.replaceChildren(...newChildren);\n window.expenses = 0;\n return;\n }\n\n let consumablesUsed = Object.entries(simResult.consumablesUsed[playerToDisplay]).sort((a, b) => b[1] - a[1]);\n\n let expensesModalTable = document.querySelector(\"#expensesTable > tbody\");\n let total = 0;\n for (const [consumable, amount] of consumablesUsed) {\n let consumablesPerHour = (amount / hoursSimulated).toFixed(0);\n let consumableRow = createRow(\n [\"col-md-6\", \"col-md-6 text-end\"],\n [itemDetailMap[consumable].name, consumablesPerHour]\n );\n consumableRow.firstElementChild.setAttribute(\"data-i18n\", \"itemNames.\" + consumable);\n newChildren.push(consumableRow);\n\n let tableRow = ' b[1] - a[1]);\n\n let totalHitpointsGained = hitpointsGained.reduce((prev, cur) => prev + cur[1], 0);\n let totalHitpointsPerSecond = (totalHitpointsGained / secondsSimulated).toFixed(2);\n let totalRow = createRow(\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\n [\"Total\", totalHitpointsPerSecond, \"100%\"]\n );\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\n newChildren.push(totalRow);\n\n for (const [source, amount] of hitpointsGained) {\n if (amount == 0) {\n continue;\n }\n\n let sourceText;\n let sourceFullHrid;\n switch (source) {\n case \"regen\":\n sourceText = \"Regen\";\n sourceFullHrid = \"combatStats.hpRegenPer10\";\n break;\n case \"lifesteal\":\n sourceText = \"Life Steal\";\n sourceFullHrid = \"combatStats.lifeSteal\";\n break;\n case \"bloom\":\n sourceText = \"Bloom\";\n sourceFullHrid = \"combatStats.bloom\";\n break;\n default:\n if (itemDetailMap[source]) {\n sourceText = itemDetailMap[source].name;\n sourceFullHrid = \"itemNames.\" + source;\n } else if (abilityDetailMap[source]) {\n sourceText = abilityDetailMap[source].name;\n sourceFullHrid = \"abilityNames.\" + source;\n }\n break;\n }\n let hitpointsPerSecond = (amount / secondsSimulated).toFixed(2);\n let percentage = ((100 * amount) / totalHitpointsGained).toFixed(0);\n\n let row = createRow(\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\n [sourceText, hitpointsPerSecond, percentage + \"%\"]\n );\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\n newChildren.push(row);\n }\n\n resultDiv.replaceChildren(...newChildren);\n}\n\nfunction showManapointsGained(simResult, playerToDisplay) {\n let resultDiv = document.getElementById(\"simulationResultManaRestored\");\n let newChildren = [];\n\n let secondsSimulated = simResult.simulatedTime / ONE_SECOND;\n\n if (!simResult.manapointsGained[playerToDisplay]) {\n resultDiv.replaceChildren(...newChildren);\n return;\n }\n\n let manapointsGained = Object.entries(simResult.manapointsGained[playerToDisplay]).sort((a, b) => b[1] - a[1]);\n\n let totalManapointsGained = manapointsGained.reduce((prev, cur) => prev + cur[1], 0);\n let totalManapointsPerSecond = (totalManapointsGained / secondsSimulated).toFixed(2);\n let totalRow = createRow(\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\n [\"Total\", totalManapointsPerSecond, \"100%\"]\n );\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\n newChildren.push(totalRow);\n\n for (const [source, amount] of manapointsGained) {\n if (amount == 0) {\n continue;\n }\n\n let sourceText;\n let sourceFullHrid;\n switch (source) {\n case \"regen\":\n sourceText = \"Regen\";\n sourceFullHrid = \"combatStats.mpRegenPer10\";\n break;\n case \"manaLeech\":\n sourceText = \"Mana Leech\";\n sourceFullHrid = \"combatStats.manaLeech\";\n break;\n case \"ripple\":\n sourceText = \"Ripple\";\n sourceFullHrid = \"combatStats.ripple\";\n break;\n default:\n sourceText = itemDetailMap[source].name;\n sourceFullHrid = \"itemNames.\" + source;\n break;\n }\n let manapointsPerSecond = (amount / secondsSimulated).toFixed(2);\n let percentage = ((100 * amount) / totalManapointsGained).toFixed(0);\n\n let row = createRow(\n [\"col-md-6\", \"col-md-3 text-end\", \"col-md-3 text-end\"],\n [sourceText, manapointsPerSecond, percentage + \"%\"]\n );\n row.firstElementChild.setAttribute(\"data-i18n\", sourceFullHrid);\n newChildren.push(row);\n }\n\n let ranOutOfManaText = simResult.playerRanOutOfMana[playerToDisplay] ? \"Yes\" : \"No\";\n let ranOutOfManaRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [\"Ran out of mana\", ranOutOfManaText]);\n ranOutOfManaRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfMana\");\n ranOutOfManaRow.lastElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.\" + ranOutOfManaText);\n newChildren.push(ranOutOfManaRow);\n\n if (simResult.playerRanOutOfMana[playerToDisplay]) {\n let ranOutOfManaStat = simResult.playerRanOutOfManaTime[playerToDisplay]; // {isOutOfMana: false, startTimeForOutOfMana:0, totalTimeForOutOfMana:0};\n let totalTimeForOut = ranOutOfManaStat.totalTimeForOutOfMana + (ranOutOfManaStat.isOutOfMana ? (simResult.simulatedTime - ranOutOfManaStat.startTimeForOutOfMana) : 0);\n\n let ranOutOfManaStatRow = createRow(\n [\"col-md-6\", \"col-md-6 text-end\"],\n [\n \"Run Out Ratio\",\n (totalTimeForOut / simResult.simulatedTime * 100).toFixed(2) + \"%\"\n ]\n );\n ranOutOfManaStatRow.firstElementChild.setAttribute(\"data-i18n\", \"common:simulationResults.ranOutOfManaRatio\");\n newChildren.push(ranOutOfManaStatRow);\n }\n\n resultDiv.replaceChildren(...newChildren);\n}\n\nfunction showDamageDone(simResult, playerToDisplay) {\n let totalDamageDone = {};\n let enemyIndex = 1;\n\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\n\n for (let i = 1; i < 64; i++) {\n let accordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + i);\n hideElement(accordion);\n }\n\n let bossTimeHeadingDiv = document.getElementById(\"simulationBossTimeHeading\");\n bossTimeHeadingDiv.classList.add(\"d-none\");\n let bossTimeDiv = document.getElementById(\"simulationBossTime\");\n bossTimeDiv.classList.add(\"d-none\");\n\n if (!simResult.attacks[playerToDisplay]) {\n return;\n }\n\n for (const [target, abilities] of Object.entries(simResult.attacks[playerToDisplay])) {\n let targetDamageDone = {};\n\n const i = simResult.timeSpentAlive.findIndex(e => e.name === target);\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\n\n for (const [ability, abilityCasts] of Object.entries(abilities)) {\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\n let misses = abilityCasts[\"miss\"] ?? 0;\n let damage = Object.entries(abilityCasts)\n .filter((entry) => entry[0] != \"miss\")\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\n\n targetDamageDone[ability] = {\n casts,\n misses,\n damage,\n };\n if (totalDamageDone[ability]) {\n totalDamageDone[ability].casts += casts;\n totalDamageDone[ability].misses += misses;\n totalDamageDone[ability].damage += damage;\n } else {\n totalDamageDone[ability] = {\n casts,\n misses,\n damage,\n };\n }\n }\n\n let resultDiv = document.getElementById(\"simulationResultDamageDoneEnemy\" + enemyIndex);\n createDamageTable(resultDiv, targetDamageDone, aliveSecondsSimulated);\n\n let resultAccordion = document.getElementById(\"simulationResultDamageDoneAccordionEnemy\" + enemyIndex);\n showElement(resultAccordion);\n\n let resultAccordionButton = document.getElementById(\n \"buttonSimulationResultDamageDoneAccordionEnemy\" + enemyIndex\n );\n let targetName = combatMonsterDetailMap[target].name;\n resultAccordionButton.innerHTML = \"Damage Done (\" + \"\" + targetName + \"\" + \")\";\n\n if (simResult.bossSpawns.includes(target)) {\n let hoursSpentOnBoss = (aliveSecondsSimulated / 60 / 60).toFixed(2);\n let percentSpentOnBoss = (aliveSecondsSimulated / totalSecondsSimulated * 100).toFixed(2);\n\n let bossRow = createRow([\"col-md-6\", \"col-md-6 text-end\"], [targetName, hoursSpentOnBoss + \"h(\" + percentSpentOnBoss + \"%)\"]);\n bossRow.firstElementChild.setAttribute(\"data-i18n\", \"monsterNames.\" + target);\n bossTimeDiv.replaceChildren(bossRow);\n\n bossTimeHeadingDiv.classList.remove(\"d-none\");\n bossTimeDiv.classList.remove(\"d-none\");\n }\n\n enemyIndex++;\n }\n\n if (simResult.isDungeon) {\n let newChildren = [];\n for (const waveName of simResult.bossSpawns) {\n // waveName is something like \"#15,/monsters/jackalope,/monsters/butterjerry\"\n let waveNumber = waveName.split(\",\")[0];\n const idx = simResult.timeSpentAlive.findIndex(e => e.name === waveNumber);\n if (idx == -1 || simResult.timeSpentAlive[idx].count == 0) {\n continue;\n }\n let aliveSecondsSimulated = simResult.timeSpentAlive[idx].timeSpentAlive / ONE_SECOND / simResult.timeSpentAlive[idx].count;\n let bossRow = createRow([\"col-md-6\", \"col-md-2\", \"col-md-4 text-end\"], [waveNumber, simResult.timeSpentAlive[idx].count, aliveSecondsSimulated.toFixed(1) + \"s\"]);\n newChildren.push(bossRow);\n }\n if (newChildren.length > 0) {\n bossTimeHeadingDiv.classList.remove(\"d-none\");\n bossTimeDiv.classList.remove(\"d-none\");\n bossTimeDiv.replaceChildren(...newChildren);\n }\n }\n\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageDone\");\n createDamageTable(totalResultDiv, totalDamageDone, totalSecondsSimulated);\n}\n\nfunction showDamageTaken(simResult, playerToDisplay) {\n let totalDamageTaken = {};\n let enemyIndex = 1;\n\n let totalSecondsSimulated = simResult.simulatedTime / ONE_SECOND;\n\n for (let i = 1; i < 64; i++) {\n let accordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + i);\n hideElement(accordion);\n }\n\n for (const [source, targets] of Object.entries(simResult.attacks)) {\n const validSources = [\"player1\", \"player2\", \"player3\", \"player4\", \"player5\"];\n if (validSources.includes(source)) {\n continue;\n }\n const i = simResult.timeSpentAlive.findIndex(e => e.name === source);\n let aliveSecondsSimulated = simResult.timeSpentAlive[i].timeSpentAlive / ONE_SECOND;\n let sourceDamageTaken = {};\n if (targets[playerToDisplay] && Object.keys(targets[playerToDisplay]).length > 0) {\n for (const [ability, abilityCasts] of Object.entries(targets[playerToDisplay])) {\n let casts = Object.values(abilityCasts).reduce((prev, cur) => prev + cur, 0);\n let misses = abilityCasts[\"miss\"] ?? 0;\n let damage = Object.entries(abilityCasts)\n .filter((entry) => entry[0] != \"miss\")\n .reduce((prev, cur) => prev + Number(cur[0]) * cur[1], 0);\n\n sourceDamageTaken[ability] = {\n casts,\n misses,\n damage,\n };\n if (totalDamageTaken[ability]) {\n totalDamageTaken[ability].casts += casts;\n totalDamageTaken[ability].misses += misses;\n totalDamageTaken[ability].damage += damage;\n } else {\n totalDamageTaken[ability] = {\n casts,\n misses,\n damage,\n };\n }\n }\n }\n\n let resultDiv = document.getElementById(\"simulationResultDamageTakenEnemy\" + enemyIndex);\n createDamageTable(resultDiv, sourceDamageTaken, aliveSecondsSimulated);\n\n let resultAccordion = document.getElementById(\"simulationResultDamageTakenAccordionEnemy\" + enemyIndex);\n showElement(resultAccordion);\n\n let resultAccordionButton = document.getElementById(\n \"buttonSimulationResultDamageTakenAccordionEnemy\" + enemyIndex\n );\n let sourceName = combatMonsterDetailMap[source].name;\n resultAccordionButton.innerHTML = \"Damage Taken (\" + \"\" + sourceName + \"\" + \")\";\n\n enemyIndex++;\n }\n\n let totalResultDiv = document.getElementById(\"simulationResultTotalDamageTaken\");\n createDamageTable(totalResultDiv, totalDamageTaken, totalSecondsSimulated);\n}\n\nfunction createDamageTable(resultDiv, damageDone, secondsSimulated) {\n let newChildren = [];\n\n let sortedDamageDone = Object.entries(damageDone).sort((a, b) => b[1].damage - a[1].damage);\n\n let totalCasts = sortedDamageDone.reduce((prev, cur) => prev + cur[1].casts, 0);\n let totalMisses = sortedDamageDone.reduce((prev, cur) => prev + cur[1].misses, 0);\n let totalDamage = sortedDamageDone.reduce((prev, cur) => prev + cur[1].damage, 0);\n let totalHitChance = ((100 * (totalCasts - totalMisses)) / totalCasts).toFixed(1);\n let totalDamagePerSecond = (totalDamage / secondsSimulated).toFixed(2);\n\n let totalRow = createRow(\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\n [\"Total\", totalHitChance + \"%\", totalDamagePerSecond, \"100%\"]\n );\n totalRow.firstElementChild.setAttribute(\"data-i18n\", \"common:total\");\n newChildren.push(totalRow);\n\n for (const [ability, damageInfo] of sortedDamageDone) {\n let abilityText;\n let abilityFullHrid;\n switch (ability) {\n case \"autoAttack\":\n abilityText = \"Auto Attack\";\n abilityFullHrid = \"combatUnit.autoAttack\";\n break;\n case \"parry\":\n abilityText = \"Parry Attack\";\n abilityFullHrid = \"common:simulationResults.parryAttack\";\n break;\n case \"damageOverTime\":\n abilityText = \"Damage Over Time\";\n abilityFullHrid = \"common:simulationResults.damageOverTime\";\n break;\n case \"physicalThorns\":\n abilityText = \"Physical Thorns\";\n abilityFullHrid = \"combatStats.physicalThorns\";\n break;\n case \"elementalThorns\":\n abilityText = \"Elemental Thorns\";\n abilityFullHrid = \"combatStats.elementalThorns\";\n break;\n case \"retaliation\":\n abilityText = \"Retaliation\";\n abilityFullHrid = \"combatStats.retaliation\";\n break;\n case 'blaze':\n abilityText = \"Blaze\";\n abilityFullHrid = \"combatStats.blaze\";\n break;\n default:\n abilityText = abilityDetailMap[ability].name;\n abilityFullHrid = \"abilityNames.\" + ability;\n break;\n }\n\n let hitChance = ((100 * (damageInfo.casts - damageInfo.misses)) / damageInfo.casts).toFixed(1);\n let damagePerSecond = (damageInfo.damage / secondsSimulated).toFixed(2);\n let percentage = ((100 * damageInfo.damage) / totalDamage).toFixed(0);\n\n let row = createRow(\n [\"col-md-5\", \"col-md-3 text-end\", \"col-md-2 text-end\", \"col-md-2 text-end\"],\n [abilityText, hitChance + \"%\", damagePerSecond, percentage + \"%\"]\n );\n row.firstElementChild.setAttribute(\"data-i18n\", abilityFullHrid);\n newChildren.push(row);\n }\n\n resultDiv.replaceChildren(...newChildren);\n}\n\nfunction createRow(columnClassNames, columnValues) {\n let row = createElement(\"div\", \"row\");\n\n for (let i = 0; i < columnClassNames.length; i++) {\n let column = createElement(\"div\", columnClassNames[i], columnValues[i]);\n row.appendChild(column);\n }\n\n return row;\n}\n\nfunction createElement(tagName, className, innerHTML = \"\", id = \"\") {\n let element = document.createElement(tagName);\n element.className = className;\n element.innerHTML = innerHTML;\n if (id) element.id = id;\n return element;\n}\n\n// #endregion\n\n// #region Simulation Controls\n\ndocument.addEventListener('DOMContentLoaded', function () {\n const simDungeonToggle = document.getElementById('simDungeonToggle');\n const playerContainer = document.getElementById('playerCheckBox');\n\n // 处理玩家 checkbox 变化时更新入战属性\n function onPlayerCheckboxChange() {\n if (isCombatReadyMode) {\n updateUI();\n }\n }\n\n // 给 checkbox 添加事件监听器\n function addCheckboxListener(checkbox) {\n checkbox.addEventListener('change', onPlayerCheckboxChange);\n }\n\n // 给初始的 player1, player2, player3 checkbox 添加事件监听器\n document.querySelectorAll('.player-checkbox').forEach(addCheckboxListener);\n\n function addPlayers() {\n const player4 = document.createElement('div');\n player4.classList.add('form-check');\n player4.innerHTML = `\n \n \n `;\n\n const player5 = document.createElement('div');\n player5.classList.add('form-check');\n player5.innerHTML = `\n \n \n `;\n\n playerContainer.appendChild(player4);\n playerContainer.appendChild(player5);\n\n // 给新添加的 checkbox 添加事件监听器\n addCheckboxListener(document.getElementById('player4'));\n addCheckboxListener(document.getElementById('player5'));\n }\n\n function removePlayers() {\n const player4 = document.getElementById('player4');\n const player5 = document.getElementById('player5');\n if (player4) player4.parentElement.remove();\n if (player5) player5.parentElement.remove();\n }\n\n function updatePlayerNames() {\n const tabLinks = document.querySelectorAll('#playerTab .nav-link');\n tabLinks.forEach((tabLink, index) => {\n const label = document.querySelector(`label[for=\"player${index + 1}\"]`);\n if (label) {\n label.textContent = tabLink.textContent.trim();\n }\n });\n }\n\n function updatePlayersCheckbox(isCheck) {\n const boxes = playerContainer.querySelectorAll('.player-checkbox');\n boxes.forEach((checkBox) => { checkBox.checked = isCheck });\n // 勾选状态变化后更新入战属性\n if (isCombatReadyMode) {\n updateUI();\n }\n }\n\n function updateDifficultySelect(isCheck) {\n const difficultySelect = document.getElementById('selectDifficulty');\n // disable last four option\n if (isCheck && Number(difficultySelect.value) >= 3) {\n difficultySelect.value = 0;\n }\n for (let i = 3; i < difficultySelect.options.length; i++) {\n difficultySelect.options[i].disabled = isCheck;\n }\n }\n\n simDungeonToggle.addEventListener('change', function () {\n if (simDungeonToggle.checked) {\n addPlayers();\n updatePlayersCheckbox(true);\n updateDifficultySelect(true);\n } else {\n removePlayers();\n updatePlayersCheckbox(false);\n updateDifficultySelect(false);\n }\n updatePlayerNames();\n });\n\n document.getElementById('buttonSimulationSetup').addEventListener('click', function () {\n updatePlayerNames();\n });\n});\n\nfunction onTabChange(event) {\n const nextPlayerTabId = event.target.getAttribute('href').substring(7);\n savePreviousPlayer(currentPlayerTabId);\n updateNextPlayer(nextPlayerTabId);\n currentPlayerTabId = nextPlayerTabId;\n updateState();\n updateUI();\n if (Object.keys(currentSimResults).length !== 0) {\n showSimulationResult(currentSimResults);\n }\n\n updateContent();\n}\n\ndocument.querySelectorAll('#playerTab .nav-link').forEach(tab => {\n tab.addEventListener('shown.bs.tab', onTabChange);\n});\n\nfunction initSimulationControls() {\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\n simulationTimeInput.value = 24;\n\n buttonStartSimulation.addEventListener(\"click\", (event) => {\n let invalidElements = document.querySelectorAll(\":invalid\");\n if (invalidElements.length > 0) {\n invalidElements.forEach((element) => element.reportValidity());\n return;\n }\n savePreviousPlayer(currentPlayerTabId);\n\n const simDungeonToggle = document.getElementById(\"simDungeonToggle\");\n const checkboxes = document.querySelectorAll('.player-checkbox');\n selectedPlayers = [];\n checkboxes.forEach(checkbox => {\n if (checkbox.checked) {\n const playerNumber = parseInt(checkbox.id.replace('player', ''));\n selectedPlayers.push(playerNumber);\n }\n });\n\n if (selectedPlayers.length === 0) {\n alert(\"You need to select at least one player to sim.\");\n return;\n }\n // buttonStartSimulation.disabled = true;\n buttonStopSimulation.style.display = 'block';\n startSimulation(selectedPlayers);\n });\n\n buttonStopSimulation.style.display = 'none';\n buttonStopSimulation.addEventListener(\"click\", (event) => {\n progressbar.style.width = \"0%\";\n progressbar.innerHTML = \"0%\";\n if (worker) {\n worker.terminate();\n }\n worker = new Worker(new URL(\"worker.js\", import.meta.url));\n\n if (multiWorker) {\n multiWorker.terminate();\n }\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\n\n for (let worker of workerPool) {\n worker.worker.terminate();\n }\n\n buttonStartSimulation.disabled = false;\n buttonStopSimulation.style.display = 'none';\n });\n}\n\nfunction startSimulation(selectedPlayers) {\n let playersToSim = [];\n for (let j = 1; j < 6; j++) {\n if (selectedPlayers.includes(j)) {\n updateNextPlayer(j);\n updateState();\n updateUI();\n player.hrid = \"player\" + j.toString();\n for (let i = 0; i < 3; i++) {\n if (food[i] && i < player.combatDetails.combatStats.foodSlots) {\n let consumable = new Consumable(food[i], triggerMap[food[i]]);\n player.food[i] = consumable;\n } else {\n player.food[i] = null;\n }\n\n if (drinks[i] && i < player.combatDetails.combatStats.drinkSlots) {\n let consumable = new Consumable(drinks[i], triggerMap[drinks[i]]);\n player.drinks[i] = consumable;\n } else {\n player.drinks[i] = null;\n }\n }\n\n for (let i = 0; i < 5; i++) {\n if (abilities[i] && player.intelligenceLevel >= abilitySlotsLevelRequirementList[i + 1]) {\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\n let ability = new Ability(abilities[i], Number(abilityLevelInput.value), triggerMap[abilities[i]]);\n player.abilities[i] = ability;\n } else {\n player.abilities[i] = null;\n }\n }\n\n playersToSim.push(structuredClone(player));\n }\n }\n updateNextPlayer(currentPlayerTabId);\n updateState();\n updateUI();\n\n let maxPlayerCombatLevel = 1;\n for (let player of playersToSim) {\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\n }\n\n for (let player of playersToSim) {\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\n const maxDebuffOnLevelGap = 0.9;\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\n\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\n\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\n }\n else {\n player.debuffOnLevelGap = 0;\n }\n }\n\n let extra = {};\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\n extra.comExp = 0;\n if (document.getElementById(\"comExpToggle\").checked) {\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\n }\n extra.comDrop = 0;\n if (document.getElementById(\"comDropToggle\").checked) {\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\n }\n extra.enableHpMpVisualization = document.getElementById(\"hpMpVisualizationToggle\").checked;\n\n let simAllZonesToggle = document.getElementById(\"simAllZoneToggle\");\n let simAllSoloToggle = document.getElementById(\"simAllSoloToggle\");\n let simDungeonToggle = document.getElementById(\"simDungeonToggle\");\n let zoneSelect = document.getElementById(\"selectZone\");\n let dungeonSelect = document.getElementById(\"selectDungeon\");\n let difficultySelect = document.getElementById(\"selectDifficulty\");\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\n let simulationTimeLimit = Number(simulationTimeInput.value) * ONE_HOUR;\n buttonStopSimulation.style.display = 'block';\n if (!simAllZonesToggle.checked && !simAllSoloToggle.checked) {\n let zoneHrid = zoneSelect.value;\n let difficultyTier = Number(difficultySelect.value);\n if (simDungeonToggle.checked) {\n zoneHrid = dungeonSelect.value;\n }\n let workerMessage = {\n type: \"start_simulation\",\n workerId: Math.floor(Math.random() * 1e9).toString(),\n players: playersToSim,\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\n simulationTimeLimit: simulationTimeLimit,\n extra : extra\n };\n simStartTime = Date.now();\n if (!worker) {\n worker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\n }\n worker.onmessage = onWorkerMessage;\n worker.postMessage(workerMessage);\n } else {\n let targetHrids = {};\n\n if (simAllZonesToggle.checked) {\n Object.values(actionDetailMap)\n .filter(a =>\n a.type === \"/action_types/combat\" &&\n a.category !== \"/action_categories/combat/dungeons\" &&\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1 &&\n document.getElementById(a.hrid)?.checked\n )\n .forEach(a => { targetHrids[a.hrid] = a; });\n }\n\n if (simAllSoloToggle.checked) {\n Object.values(actionDetailMap)\n .filter(a =>\n a.type === \"/action_types/combat\" &&\n a.category !== \"/action_categories/combat/dungeons\" &&\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount === 1 &&\n document.getElementById(a.hrid)?.checked\n )\n .forEach(a => { targetHrids[a.hrid] = a; });\n }\n\n let simHrids = Object.values(targetHrids)\n .sort((a, b) => a.sortIndex - b.sortIndex)\n .map(action => {\n let result = [];\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\n }\n return result;\n })\n .flat();\n\n let workerMessage = {\n type: \"start_simulation_all_zones\",\n workerId: Math.floor(Math.random() * 1e9).toString(),\n players: playersToSim,\n zones: simHrids,\n simulationTimeLimit: simulationTimeLimit,\n extra: extra\n };\n simStartTime = Date.now();\n if (!multiWorker) {\n multiWorker = new Worker(new URL(\"multiWorker.js\", import.meta.url));\n }\n multiWorker.onmessage = onMultiWorkerMessage;\n multiWorker.postMessage(workerMessage);\n }\n}\n\nfunction parsePlayerJson(playerJson, hrid) {\n let playerData = {\n hrid: hrid,\n food: [],\n drinks: [],\n abilities: [],\n ...playerJson.player,\n houseRooms: playerJson.houseRooms,\n };\n playerData.equipment = {};\n const triggerMap = playerJson.triggerMap;\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"main_hand\", \"two_hand\", \"charm\"].forEach((type) => {\n let currentEquipment = playerJson.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\n if (currentEquipment){\n playerData.equipment[`/equipment_types/${type}`] = new Equipment(currentEquipment.itemHrid, currentEquipment.enhancementLevel);\n }\n });\n\n for (const foodHrid of playerJson.food[\"/action_types/combat\"]) {\n if (foodHrid.itemHrid === \"\") continue;\n const food = new Consumable(foodHrid.itemHrid, triggerMap[foodHrid.itemHrid]);\n playerData.food.push(food);\n }\n for (const drinkHrid of playerJson.drinks[\"/action_types/combat\"]) {\n if (drinkHrid.itemHrid === \"\") continue;\n const drink = new Consumable(drinkHrid.itemHrid, triggerMap[drinkHrid.itemHrid]);\n playerData.drinks.push(drink);\n }\n for (const ability of playerJson.abilities) {\n if (ability.abilityHrid === \"\") continue;\n const abilityLevel = Number(ability.level);\n const abilityHrid = ability.abilityHrid;\n if (abilityLevel > 0) {\n const abilityObj = new Ability(abilityHrid, abilityLevel, triggerMap[abilityHrid]);\n playerData.abilities.push(abilityObj);\n }\n }\n const player = Player.createFromDTO(playerData)\n player.updateCombatDetails();\n player.houseRooms = playerJson.houseRooms;\n player.achievements = playerJson.achievements ?? {};\n return player;\n}\n// read JSON file to simulate\ndocument.getElementById(\"buttonUploadJSONSimulate\").addEventListener(\"click\", (event) => {\n let extra = {};\n extra.mooPass = document.getElementById(\"mooPassToggle\").checked;\n extra.comExp = 0;\n if (document.getElementById(\"comExpToggle\").checked) {\n extra.comExp = Number(document.getElementById(\"comExpInput\").value);\n }\n extra.comDrop = 0;\n if (document.getElementById(\"comDropToggle\").checked) {\n extra.comDrop = Number(document.getElementById(\"comDropInput\").value);\n }\n\n let fileInput = document.getElementById(\"inputUploadJSONSimulation\");\n let file = fileInput.files[0];\n if (!file) {\n alert(\"Please select a file to upload.\");\n return;\n }\n\n let reader = new FileReader();\n reader.onload = function (event) {\n let fileContent = event.target.result;\n const jsonDataList = JSON.parse(fileContent);\n try {\n const simDataList = [];\n for (const key in jsonDataList) {\n if (jsonDataList[key].cases) {\n const cases = getProductCases(jsonDataList[key], jsonDataList[key].cases);\n simDataList.push(...cases);\n } else {\n simDataList.push(jsonDataList[key]);\n }\n }\n for (const key in simDataList) {\n const jsonData = simDataList[key];\n if (!jsonData || !jsonData.zone || !jsonData.players) {\n alert(\"Invalid JSON file format. Please ensure it contains a 'simulationResult' property.\");\n return;\n }\n const playersToSim = Object.values(jsonData.players).map(\n (player, index) => parsePlayerJson(player, `player${index + 1}`)\n );\n\n let maxPlayerCombatLevel = 1;\n for (let player of playersToSim) {\n player.combatLevel = calcCombatLevel(player.staminaLevel, player.intelligenceLevel, player.defenseLevel, player.attackLevel, player.meleeLevel, player.rangedLevel, player.magicLevel);\n maxPlayerCombatLevel = Math.max(maxPlayerCombatLevel, player.combatLevel);\n }\n\n for (let player of playersToSim) {\n if ((maxPlayerCombatLevel / player.combatLevel) > 1.2) {\n const maxDebuffOnLevelGap = 0.9;\n let levelPercent = Math.floor(((maxPlayerCombatLevel / player.combatLevel) - 1.2) * 100) / 100;\n player.debuffOnLevelGap = -1 * Math.min(maxDebuffOnLevelGap, 3 * levelPercent);\n console.log(\"player \" + player.hrid + \" debuff on level gap: \" + player.debuffOnLevelGap * 100 + \"% for \" + (maxPlayerCombatLevel / player.combatLevel));\n }\n else {\n player.debuffOnLevelGap = 0;\n }\n }\n\n const simulationTimeLimit = (jsonData.simulationTimeLimit || 24) * ONE_HOUR;\n const simName = jsonData.name || `Json ${key}`;\n const zoneHrid = jsonData.zone;\n if (zoneHrid === \"all\") {\n let targetHrids = {};\n\n if (simAllZonesToggle.checked) {\n Object.values(actionDetailMap)\n .filter(a =>\n a.type === \"/action_types/combat\" &&\n a.category !== \"/action_categories/combat/dungeons\" &&\n a.combatZoneInfo.fightInfo.randomSpawnInfo.maxSpawnCount > 1\n )\n .forEach(a => { targetHrids[a.hrid] = a; });\n }\n\n let simHrids = Object.values(targetHrids)\n .sort((a, b) => a.sortIndex - b.sortIndex)\n .map(action => {\n let result = [];\n for (let difficultyTier = 0; difficultyTier <= action.maxDifficulty; difficultyTier++) {\n result.push({ zoneHrid: action.hrid, difficultyTier: difficultyTier });\n }\n return result;\n })\n .flat();\n\n let workerMessage = {\n simulationName: simName,\n type: \"start_simulation_all_zones\",\n workerId: Math.floor(Math.random() * 1e9).toString(),\n players: playersToSim,\n zones: simHrids,\n simulationTimeLimit: simulationTimeLimit,\n extra : extra\n };\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \n worker.onmessage = mainWorkerOnMessage;\n worker.postMessage(workerMessage);\n customAlert(\"Simulation task Created\", \"info\")\n workerPool.push({\n workerId: workerMessage.workerId,\n worker: worker,\n });\n } else {\n let difficultyTier = jsonData.difficultyTier || 0;\n let workerMessage = {\n simulationName: simName,\n type: \"start_simulation\",\n workerId: Math.floor(Math.random() * 1e9).toString(),\n players: playersToSim,\n zone: { zoneHrid: zoneHrid, difficultyTier: difficultyTier },\n simulationTimeLimit: simulationTimeLimit,\n extra : extra\n };\n const worker = new Worker(new URL(\"worker.js\", import.meta.url)); \n worker.onmessage = mainWorkerOnMessage;\n worker.postMessage(workerMessage);\n customAlert(\"Simulation task Created\", \"info\")\n workerPool.push({\n workerId: workerMessage.workerId,\n worker: worker,\n });\n }\n }\n } catch (error) {\n // alert(\"Error parsing JSON file: \" + error.message);\n customAlert(\"Error parsing JSON file: \" + error.message, \"danger\");\n }\n }\n reader.readAsText(file);\n});\n\n\n// #endregion\n\n// #region WipeEvents\n\nfunction renderWipeEvents(simResult) {\n const selector = document.getElementById('wipeEventSelector');\n const logsContainer = document.getElementById('wipeLogsContainer');\n const waveBadge = document.getElementById('wipeWaveBadge');\n const timeInfo = document.getElementById('wipeTimeInfo');\n\n selector.innerHTML = '';\n logsContainer.innerHTML = '';\n\n if (!simResult.wipeEvents || simResult.wipeEvents.length === 0) {\n selector.innerHTML = ``;\n logsContainer.innerHTML = `
No Wipe Events Detected
`;\n waveBadge.textContent = '';\n timeInfo.textContent = '';\n return;\n }\n\n simResult.wipeEvents.forEach((event, index) => {\n const wave = event.wave || '?';\n // const time = (event.simulationTime / 1e9).toFixed(2);\n // const timestamp = new Date(event.timestamp).toLocaleTimeString();\n\n const option = document.createElement('option');\n option.value = index;\n option.textContent = `#${index + 1} - 波次: ${wave}`;\n selector.appendChild(option);\n });\n\n selector.value = 0;\n renderSelectedWipeEvent(0, simResult);\n\n selector.addEventListener('change', () => {\n renderSelectedWipeEvent(selector.value, simResult);\n });\n}\n\n// 渲染选中的团灭事件\nfunction renderSelectedWipeEvent(index, simResult) {\n const logsContainer = document.getElementById('wipeLogsContainer');\n const waveBadge = document.getElementById('wipeWaveBadge');\n const timeInfo = document.getElementById('wipeTimeInfo');\n\n logsContainer.innerHTML = '';\n\n if (index < 0 || index >= simResult.wipeEvents.length) {\n logsContainer.innerHTML = `
No Wipe Events
`;\n waveBadge.textContent = '';\n timeInfo.textContent = '';\n return;\n }\n\n const wipeEvent = simResult.wipeEvents[index];\n const wave = wipeEvent.wave || '?';\n const time = (wipeEvent.simulationTime / 1e9).toFixed(2);\n const timestamp = new Date(wipeEvent.timestamp).toLocaleString();\n\n waveBadge.textContent = `波次: ${wave}`;\n timeInfo.textContent = `模拟时间: ${time}s | 记录时间: ${timestamp}`;\n\n const logsByTime = groupLogsByTime(wipeEvent.logs);\n\n const baseTime = logsByTime.length > 0 ? logsByTime[0].time : 0;\n\n logsByTime.forEach(group => {\n const timeGroupElement = document.createElement('div');\n timeGroupElement.className = 'log-time-group';\n\n const relativeTime = (group.time - baseTime) / 1e9;\n\n // 时间标题\n const timeHeader = document.createElement('div');\n timeHeader.className = 'log-time-header';\n timeHeader.textContent = `[${relativeTime.toFixed(2)}s] [Wave#${group.wave}]`;\n timeGroupElement.appendChild(timeHeader);\n\n // 事件列表\n const eventsList = document.createElement('div');\n eventsList.className = 'log-events';\n\n const damagedPlayers = new Set();\n\n group.logs.forEach(log => {\n const eventElement = document.createElement('div');\n eventElement.className = 'log-event';\n\n damagedPlayers.add(log.target);\n\n const sourceSpan = document.createElement('span');\n sourceSpan.className = 'log-source';\n if (log.ability === \"damageOverTime\") {\n sourceSpan.textContent = log.target;\n } else if(log.source == 'UNKNOWN_SOURCE') {\n sourceSpan.textContent = 'UNKNOWN';\n } else {\n sourceSpan.setAttribute('data-i18n', `monsterNames.${log.source}`);\n sourceSpan.textContent = log.source;\n }\n\n const castSpan = document.createElement('span');\n castSpan.className = 'log-cast';\n castSpan.setAttribute('data-i18n', `common:cast`);\n castSpan.textContent = ' cast ';\n\n const abilitySpan = document.createElement('span');\n abilitySpan.className = 'log-ability';\n if (log.ability === \"autoAttack\") {\n abilitySpan.setAttribute('data-i18n', 'combatUnit.autoAttack');\n abilitySpan.textContent = 'Auto Attack';\n } else if (log.ability === \"physicalThorns\") {\n abilitySpan.setAttribute('data-i18n', `combatStats.physicalThorns`);\n abilitySpan.textContent = 'Physical Thorns';\n } else if (log.ability === \"elementalThorns\") {\n abilitySpan.setAttribute('data-i18n', `combatStats.elementalThorns`);\n abilitySpan.textContent = 'Elemental Thorns';\n } else if (log.ability === \"retaliation\") {\n abilitySpan.setAttribute('data-i18n', `combatStats.retaliation`);\n abilitySpan.textContent = 'Retaliation';\n } else if (log.ability === \"damageOverTime\") {\n abilitySpan.setAttribute('data-i18n', `common:simulationResults.damageOverTime`);\n abilitySpan.textContent = 'Damage Over Time';\n } else {\n abilitySpan.setAttribute('data-i18n', `abilityNames.${log.ability}`);\n abilitySpan.textContent = log.ability;\n }\n\n const toSpan = document.createElement('span');\n toSpan.className = 'log-to';\n toSpan.setAttribute('data-i18n', `common:to`);\n toSpan.textContent = ' to ';\n\n const targetSpan = document.createElement('span');\n targetSpan.className = 'log-target';\n targetSpan.textContent = log.target;\n\n const dealDamageSpan = document.createElement('span');\n dealDamageSpan.className = 'log-deal-damage';\n dealDamageSpan.setAttribute('data-i18n', `common:dealDamage`);\n dealDamageSpan.textContent = ' deal damage ';\n\n const damageDoneSpan = document.createElement('span');\n damageDoneSpan.className = 'log-damage-done';\n damageDoneSpan.textContent = log.damage;\n if (log.isCrit) {\n damageDoneSpan.style.fontWeight = 'bold';\n damageDoneSpan.textContent += '!!!';\n }\n\n eventElement.appendChild(sourceSpan);\n eventElement.appendChild(castSpan);\n eventElement.appendChild(abilitySpan);\n eventElement.appendChild(toSpan);\n eventElement.appendChild(targetSpan);\n eventElement.appendChild(dealDamageSpan);\n eventElement.appendChild(damageDoneSpan);\n eventElement.appendChild(document.createTextNode(` , HP ${log.beforeHp} → ${log.afterHp}`));\n\n eventsList.appendChild(eventElement);\n });\n\n timeGroupElement.appendChild(eventsList);\n\n const lastLog = group.logs[group.logs.length - 1];\n const playersHpElement = document.createElement('div');\n\n const playerHpTitle = document.createElement('span');\n playerHpTitle.className = 'log-players-hp';\n playerHpTitle.setAttribute('data-i18n', `common:playersHp`);\n playerHpTitle.textContent = 'Players HP: ';\n playersHpElement.appendChild(playerHpTitle);\n\n lastLog.playersHp.forEach((player, idx) => {\n const playerElement = document.createElement('span');\n playerElement.className = 'log-player-hp';\n playerElement.textContent = `${player.hrid}: ${player.current}/${player.max}`;\n\n if (player.current <= 0) {\n playerElement.style.color = darkModeToggle.checked ? '#FF6347' : '#CC0000';\n } else if (damagedPlayers.has(player.hrid)) {\n playerElement.style.color = darkModeToggle.checked ? '#00BFFF' : '#007BFF';\n }\n\n if (idx > 0) {\n playersHpElement.appendChild(document.createTextNode(' | '));\n }\n playersHpElement.appendChild(playerElement);\n });\n const spacer = document.createElement('div');\n spacer.style.height = '15px';\n logsContainer.appendChild(spacer);\n timeGroupElement.appendChild(playersHpElement);\n logsContainer.appendChild(timeGroupElement);\n });\n\n // 更新汉化\n updateContent()\n}\n\n// 按时间分组日志\nfunction groupLogsByTime(logs) {\n const groups = [];\n let currentGroup = null;\n\n logs.forEach(log => {\n if (!currentGroup || currentGroup.time !== log.time) {\n currentGroup = {\n time: log.time,\n wave: log.wave,\n logs: [log]\n };\n groups.push(currentGroup);\n } else {\n currentGroup.logs.push(log);\n }\n });\n\n groups.forEach(group => {\n let hpMap = {};\n if (group.logs.length > 0) {\n group.logs[0].playersHp.forEach(p => {\n hpMap[p.hrid] = { current: p.current, max: p.max };\n });\n }\n group.logs.forEach(log => {\n if (hpMap[log.target]) {\n hpMap[log.target].current = log.afterHp;\n }\n });\n group.logs.forEach(log => {\n log.playersHp = Object.entries(hpMap).map(([hrid, val]) => ({\n hrid,\n current: val.current,\n max: val.max\n }));\n });\n });\n\n return groups;\n}\n\n// #endregion\n\n\n// #region Equipment Sets\n\nfunction initEquipmentSetsModal() {\n let equipmentSetsModal = document.getElementById(\"equipmentSetsModal\");\n equipmentSetsModal.addEventListener(\"show.bs.modal\", equipmentSetsModalShownHandler);\n\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\n equipmentSetNameInput.addEventListener(\"input\", (event) => equipmentSetNameChangedHandler(event));\n\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\n createEquipmentSetButton.addEventListener(\"click\", createNewEquipmentSetHandler);\n}\n\nfunction equipmentSetsModalShownHandler() {\n resetNewEquipmentSetControls();\n updateEquipmentSetList();\n}\n\nfunction resetNewEquipmentSetControls() {\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\n equipmentSetNameInput.value = \"\";\n\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\n createEquipmentSetButton.disabled = true;\n}\n\nfunction updateEquipmentSetList() {\n let newChildren = [];\n let equipmentSets = loadEquipmentSets();\n\n for (const equipmentSetName of Object.keys(equipmentSets)) {\n let row = createElement(\"div\", \"row mb-2\");\n\n let nameCol = createElement(\"div\", \"col align-self-center\", equipmentSetName);\n row.appendChild(nameCol);\n\n let loadButtonCol = createElement(\"div\", \"col-md-auto\");\n let loadButton = createElement(\"button\", \"btn btn-primary\", \"Load\");\n loadButton.setAttribute(\"data-i18n\", \"common:controls.load\");\n loadButton.setAttribute(\"type\", \"button\");\n loadButton.addEventListener(\"click\", (_) => loadEquipmentSetHandler(equipmentSetName));\n loadButtonCol.appendChild(loadButton);\n row.appendChild(loadButtonCol);\n\n let saveButtonCol = createElement(\"div\", \"col-md-auto\");\n let saveButton = createElement(\"button\", \"btn btn-primary\", \"Save\");\n saveButton.setAttribute(\"data-i18n\", \"common:controls.save\");\n saveButton.setAttribute(\"type\", \"button\");\n saveButton.addEventListener(\"click\", (_) => updateEquipmentSetHandler(equipmentSetName));\n saveButtonCol.appendChild(saveButton);\n row.appendChild(saveButtonCol);\n\n let deleteButtonCol = createElement(\"div\", \"col-md-auto\");\n let deleteButton = createElement(\"button\", \"btn btn-danger\", \"Delete\");\n deleteButton.setAttribute(\"data-i18n\", \"common:controls.delete\");\n deleteButton.setAttribute(\"type\", \"button\");\n deleteButton.addEventListener(\"click\", (_) => deleteEquipmentSetHandler(equipmentSetName));\n deleteButtonCol.appendChild(deleteButton);\n row.appendChild(deleteButtonCol);\n\n newChildren.push(row);\n }\n\n let equipmentSetList = document.getElementById(\"equipmentSetList\");\n equipmentSetList.replaceChildren(...newChildren);\n\n updateContent();\n}\n\nfunction equipmentSetNameChangedHandler(event) {\n let invalid = false;\n\n if (event.target.value.length == 0) {\n invalid = true;\n }\n\n let equipmentSets = loadEquipmentSets();\n if (equipmentSets[event.target.value]) {\n invalid = true;\n }\n\n let createEquipmentSetButton = document.getElementById(\"buttonCreateNewEquipmentSet\");\n createEquipmentSetButton.disabled = invalid;\n}\n\nfunction createNewEquipmentSetHandler() {\n let equipmentSetNameInput = document.getElementById(\"inputEquipmentSetName\");\n let equipmentSetName = equipmentSetNameInput.value;\n\n let equipmentSet = getEquipmentSetFromUI();\n let equipmentSets = loadEquipmentSets();\n equipmentSets[equipmentSetName] = equipmentSet;\n saveEquipmentSets(equipmentSets);\n\n resetNewEquipmentSetControls();\n updateEquipmentSetList();\n}\n\nfunction loadEquipmentSetHandler(name) {\n let equipmentSets = loadEquipmentSets();\n loadEquipmentSetIntoUI(equipmentSets[name]);\n}\n\nfunction updateEquipmentSetHandler(name) {\n let equipmentSet = getEquipmentSetFromUI();\n let equipmentSets = loadEquipmentSets();\n equipmentSets[name] = equipmentSet;\n saveEquipmentSets(equipmentSets);\n}\n\nfunction deleteEquipmentSetHandler(name) {\n let equipmentSets = loadEquipmentSets();\n delete equipmentSets[name];\n saveEquipmentSets(equipmentSets);\n\n updateEquipmentSetList();\n}\n\nfunction loadEquipmentSets() {\n return JSON.parse(localStorage.getItem(\"equipmentSets\")) ?? {};\n}\n\nfunction saveEquipmentSets(equipmentSets) {\n localStorage.setItem(\"equipmentSets\", JSON.stringify(equipmentSets));\n}\n\nfunction getEquipmentSetFromUI() {\n let equipmentSet = {\n levels: {},\n equipment: {},\n food: {},\n drinks: {},\n abilities: {},\n triggerMap: {},\n houseRooms: {},\n achievements: {},\n };\n\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\n equipmentSet.levels[skill] = Number(levelInput.value);\n });\n\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\n\n equipmentSet.equipment[type] = {\n equipment: equipmentSelect.value,\n enhancementLevel: Number(enhancementLevelInput.value),\n };\n });\n\n for (let i = 0; i < 3; i++) {\n let foodSelect = document.getElementById(\"selectFood_\" + i);\n equipmentSet.food[i] = foodSelect.value;\n }\n\n for (let i = 0; i < 3; i++) {\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\n equipmentSet.drinks[i] = drinkSelect.value;\n }\n\n for (let i = 0; i < 5; i++) {\n let abilitySelect = document.getElementById(\"selectAbility_\" + i);\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\n equipmentSet.abilities[i] = {\n ability: abilitySelect.value,\n level: Number(abilityLevelInput.value),\n };\n }\n\n equipmentSet.triggerMap = triggerMap;\n\n equipmentSet.houseRooms = player.houseRooms;\n equipmentSet.achievements = player.achievements;\n\n return equipmentSet;\n}\n\nfunction fixTriggerMap(triggerMap) {\n let delKeys = []\n for (const key of Object.keys(triggerMap)) {\n let err = false;\n if (null == triggerMap[key]) {\n triggerMap[key] = [];\n }\n for (const trigger of triggerMap[key]) {\n if (!combatTriggerConditionDetailMap[trigger.conditionHrid]) {\n err = true;\n break;\n }\n }\n if (err) {\n delKeys.push(key);\n }\n }\n for (const key of delKeys) {\n delete triggerMap[key];\n }\n}\n\nfunction loadEquipmentSetIntoUI(equipmentSet) {\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\n if (skill == \"melee\" && !equipmentSet.levels[\"meleeLevel\"] && equipmentSet.levels[\"powerLevel\"]) {\n equipmentSet.levels[\"meleeLevel\"] = equipmentSet.levels[\"powerLevel\"];\n }\n levelInput.value = equipmentSet.levels[skill] ?? 1;\n });\n\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"weapon\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\n\n let currentEquipment = equipmentSet.equipment[type];\n if (currentEquipment !== undefined) {\n equipmentSelect.value = currentEquipment.equipment;\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\n } else {\n equipmentSelect.value = \"\";\n enhancementLevelInput.value = 0;\n }\n });\n\n for (let i = 0; i < 3; i++) {\n let foodSelect = document.getElementById(\"selectFood_\" + i);\n foodSelect.value = equipmentSet.food[i];\n }\n\n for (let i = 0; i < 3; i++) {\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\n drinkSelect.value = equipmentSet.drinks[i].replace(\"power\", \"melee\");\n }\n\n let hasSpecial = false;\n if (equipmentSet.abilities && Object.keys(equipmentSet.abilities).length == 5) {\n hasSpecial = true;\n }\n\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\n let abilitySlot = hasSpecial ? i : (i + 1);\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\n\n if (hasSpecial && i == 0 && (\n equipmentSet.abilities[i].ability == \"/abilities/aqua_aura\" ||\n equipmentSet.abilities[i].ability == \"/abilities/flame_aura\" ||\n equipmentSet.abilities[i].ability == \"/abilities/sylvan_aura\"\n )\n ) {\n equipmentSet.abilities[i].ability = \"/abilities/mystic_aura\";\n }\n\n if (equipmentSet.abilities[i].ability == \"/abilities/arcane_reflection\") {\n equipmentSet.abilities[i].ability = \"/abilities/retribution\";\n }\n\n abilitySelect.value = equipmentSet.abilities[i].ability;\n abilityLevelInput.value = equipmentSet.abilities[i].level;\n }\n\n triggerMap = equipmentSet.triggerMap;\n fixTriggerMap(triggerMap);\n\n if (equipmentSet.houseRooms) {\n for (const room in equipmentSet.houseRooms) {\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\n if (equipmentSet.houseRooms[room]) {\n field.value = equipmentSet.houseRooms[room];\n } else {\n field.value = '';\n }\n }\n player.houseRooms = equipmentSet.houseRooms;\n } else {\n let houseRooms = Object.values(houseRoomDetailMap);\n for (const room of Object.values(houseRooms)) {\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\n field.value = '';\n player.houseRooms[room.hrid] = 0;\n }\n }\n\n if (equipmentSet.achievements) {\n for (const achievement in equipmentSet.achievements) {\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\n if (equipmentSet.achievements[achievement]) {\n field.checked = true;\n } else {\n field.checked = false;\n }\n player.achievements[achievement] = field.checked;\n }\n } else {\n let achievements = Object.values(achievementDetailMap);\n for (const detail of Object.values(achievements)) {\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\n field.checked = false;\n player.achievements[detail.hrid] = false;\n }\n }\n refreshAchievementStatics();\n\n updateState();\n updateUI();\n\n updateContent();\n}\n\n// #endregion\n\n// #region Error Handling\n\nfunction initErrorHandling() {\n window.addEventListener(\"error\", (event) => {\n showErrorModal(event.message);\n });\n\n let copyErrorButton = document.getElementById(\"buttonCopyError\");\n copyErrorButton.addEventListener(\"click\", (event) => {\n let errorInput = document.getElementById(\"inputError\");\n navigator.clipboard.writeText(errorInput.value);\n });\n}\n\nfunction initImportExportModal() {\n let exportSetButton = document.getElementById(\"buttonExportSet\");\n exportSetButton.addEventListener(\"click\", (event) => {\n savePreviousPlayer(currentPlayerTabId);\n const activeTab = document.querySelector('#importTab .nav-link.active');\n if (activeTab.id === 'group-combat-tab') {\n doGroupExport();\n } else if (activeTab.id === 'solo-tab') {\n doSoloExport();\n }\n });\n\n let importSetButton = document.getElementById(\"buttonImportSet\");\n importSetButton.addEventListener(\"click\", (event) => {\n const activeTab = document.querySelector('#importTab .nav-link.active');\n if (activeTab.id === 'group-combat-tab') {\n doGroupImport();\n } else if (activeTab.id === 'solo-tab') {\n doSoloImport();\n }\n updateState();\n updateUI();\n resetImportInputs();\n });\n}\n\nfunction resetImportInputs() {\n document.getElementById('inputSetGroupCombatAll').value = '';\n document.getElementById('inputSetGroupCombatplayer1').value = '';\n document.getElementById('inputSetGroupCombatplayer2').value = '';\n document.getElementById('inputSetGroupCombatplayer3').value = '';\n document.getElementById('inputSetGroupCombatplayer4').value = '';\n document.getElementById('inputSetGroupCombatplayer5').value = '';\n document.getElementById('inputSetSolo').value = '';\n}\n\nfunction doGroupExport() {\n try {\n navigator.clipboard.writeText(JSON.stringify(playerDataMap)).then(() => alert(\"Current Group has been copied to clipboard.\"));\n } catch (err) {\n alert('Error copying to clipboard: ' + err);\n }\n}\n\nfunction doSoloExport() {\n let zoneSelect = document.getElementById(\"selectZone\");\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\n let equipmentArray = [];\n for (const item in player.equipment) {\n if (player.equipment[item] != null) {\n equipmentArray.push({\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\n \"itemHrid\": player.equipment[item].hrid,\n \"enhancementLevel\": player.equipment[item].enhancementLevel\n });\n }\n }\n let playerArray = {\n \"attackLevel\": player.attackLevel,\n \"magicLevel\": player.magicLevel,\n \"meleeLevel\": player.meleeLevel,\n \"rangedLevel\": player.rangedLevel,\n \"defenseLevel\": player.defenseLevel,\n \"staminaLevel\": player.staminaLevel,\n \"intelligenceLevel\": player.intelligenceLevel,\n \"equipment\": equipmentArray\n };\n let abilitiesArray = [];\n for (let i = 0; i < 5; i++) {\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\n let abilityName = document.getElementById(\"selectAbility_\" + i);\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\n }\n let drinksArray = [];\n for (let i = 0; i < drinks?.length; i++) {\n drinksArray.push({ \"itemHrid\": drinks[i] });\n }\n let foodArray = [];\n for (let i = 0; i < food?.length; i++) {\n foodArray.push({ \"itemHrid\": food[i] });\n }\n let state = {\n player: playerArray,\n food: { \"/action_types/combat\": foodArray },\n drinks: { \"/action_types/combat\": drinksArray },\n abilities: abilitiesArray,\n triggerMap: triggerMap,\n zone: zoneSelect.value,\n simulationTime: simulationTimeInput.value,\n houseRooms: player.houseRooms,\n achievements: player.achievements\n };\n try {\n navigator.clipboard.writeText(JSON.stringify(state)).then(() => alert(\"Current set has been copied to clipboard.\"));\n } catch (err) {\n alert('Error copying to clipboard: ' + err);\n }\n}\n\nfunction setPlayerData(playerId, inputElementId) {\n const inputElement = document.getElementById(inputElementId);\n const value = inputElement ? inputElement.value.trim() : \"\";\n\n // Only set the value in the map if it's not null, undefined, or empty\n if (value) {\n playerDataMap[playerId] = value;\n return true;\n }\n return false;\n}\n\nfunction doGroupImport() {\n let needUpdateCurrentTab = false;\n let importedPlayers = []; // 跟踪导入了哪些玩家的数据\n const value = document.getElementById(\"inputSetGroupCombatAll\")?.value || \"\";\n if (!value.trim()) {\n for (let i of ['1', '2', '3', '4', '5']) {\n if (setPlayerData(i, \"inputSetGroupCombatplayer\" + i)) {\n importedPlayers.push(i);\n if (currentPlayerTabId == i) {\n needUpdateCurrentTab = true;\n }\n }\n }\n } else {\n playerDataMap = JSON.parse(value);\n needUpdateCurrentTab = true;\n // 检查导入的组数据中哪些玩家有有效数据\n for (let i of ['1', '2', '3', '4', '5']) {\n if (playerDataMap[i]) {\n try {\n let data = JSON.parse(playerDataMap[i]);\n // 检查是否有有效的玩家数据(至少有等级设置)\n if (data && data.player && (data.player.attackLevel > 1 || data.player.meleeLevel > 1 || \n data.player.defenseLevel > 1 || data.player.rangedLevel > 1 || data.player.magicLevel > 1)) {\n importedPlayers.push(i);\n }\n } catch (e) {\n // 跳过无法解析的数据\n }\n }\n }\n }\n\n // 自动勾选导入了有效数据的玩家的 checkbox\n if (importedPlayers.length > 0) {\n for (let i of ['1', '2', '3', '4', '5']) {\n let checkbox = document.getElementById('player' + i);\n if (checkbox) {\n checkbox.checked = importedPlayers.includes(i);\n }\n }\n }\n\n if (needUpdateCurrentTab) {\n updateNextPlayer(currentPlayerTabId);\n }\n}\n\nfunction doSoloImport() {\n let importSet = document.getElementById(\"inputSetSolo\").value;\n importSet = JSON.parse(importSet);\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\n }\n levelInput.value = importSet.player[skill + \"Level\"];\n });\n\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\n if (currentEquipment !== undefined) {\n equipmentSelect.value = currentEquipment.itemHrid;\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\n } else {\n equipmentSelect.value = \"\";\n enhancementLevelInput.value = 0;\n }\n });\n\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\n if (mainhandWeapon !== undefined) {\n weaponSelect.value = mainhandWeapon.itemHrid;\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\n } else if (twohandWeapon !== undefined) {\n weaponSelect.value = twohandWeapon.itemHrid;\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\n } else {\n weaponSelect.value = \"\";\n weaponEnhancementLevelInput.value = 0;\n }\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\n importSet.food = importSet.food[\"/action_types/combat\"];\n for (let i = 0; i < 3; i++) {\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\n let foodSelect = document.getElementById(\"selectFood_\" + i);\n if (importSet.drinks[i] != null) {\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\n } else {\n drinkSelect.value = \"\";\n }\n if (importSet.food[i] != null) {\n foodSelect.value = importSet.food[i].itemHrid;\n } else {\n foodSelect.value = \"\";\n }\n }\n\n let hasSpecial = false;\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\n hasSpecial = true;\n }\n\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\n let abilitySlot = hasSpecial ? i : (i + 1);\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\n\n if (hasSpecial && i == 0 && (\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\n )\n ) {\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\n }\n\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\n }\n\n if (importSet.abilities[i] != null) {\n abilitySelect.value = importSet.abilities[i].abilityHrid;\n abilityLevelInput.value = String(importSet.abilities[i].level);\n } else {\n abilitySelect.value = \"\";\n abilityLevelInput.value = \"1\";\n }\n }\n\n if (importSet.triggerMap) {\n triggerMap = importSet.triggerMap;\n fixTriggerMap(triggerMap);\n }\n\n if (importSet.houseRooms) {\n for (const room in importSet.houseRooms) {\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\n if (importSet.houseRooms[room]) {\n field.value = importSet.houseRooms[room];\n } else {\n field.value = '';\n }\n }\n player.houseRooms = importSet.houseRooms;\n } else {\n let houseRooms = Object.values(houseRoomDetailMap);\n for (const room of Object.values(houseRooms)) {\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\n field.value = '';\n player.houseRooms[room.hrid] = 0;\n }\n }\n\n if (importSet.achievements) {\n for (const achievement in importSet.achievements) {\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\n if (importSet.achievements[achievement]) {\n field.checked = true;\n } else {\n field.checked = false;\n }\n player.achievements[achievement] = field.checked;\n }\n } else {\n let achievements = Object.values(achievementDetailMap);\n for (const detail of Object.values(achievements)) {\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\n field.checked = false;\n player.achievements[detail.hrid] = false;\n }\n }\n refreshAchievementStatics();\n\n if (\"zone\" in importSet) {\n let zoneSelect = document.getElementById(\"selectZone\");\n zoneSelect.value = importSet[\"zone\"];\n }\n\n if (\"simulationTime\" in importSet) {\n let simulationDuration = document.getElementById(\"inputSimulationTime\");\n simulationDuration.value = importSet[\"simulationTime\"];\n }\n}\n\nfunction savePreviousPlayer(playerId) {\n let zoneSelect = document.getElementById(\"selectZone\");\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\n let equipmentArray = [];\n for (const item in player.equipment) {\n if (player.equipment[item] != null) {\n equipmentArray.push({\n \"itemLocationHrid\": player.equipment[item].gameItem.equipmentDetail.type.replaceAll(\"equipment_types\", \"item_locations\"),\n \"itemHrid\": player.equipment[item].hrid,\n \"enhancementLevel\": player.equipment[item].enhancementLevel\n });\n }\n }\n let playerArray = {\n \"attackLevel\": player.attackLevel,\n \"magicLevel\": player.magicLevel,\n \"meleeLevel\": player.meleeLevel,\n \"rangedLevel\": player.rangedLevel,\n \"defenseLevel\": player.defenseLevel,\n \"staminaLevel\": player.staminaLevel,\n \"intelligenceLevel\": player.intelligenceLevel,\n \"equipment\": equipmentArray\n };\n let abilitiesArray = [];\n for (let i = 0; i < 5; i++) {\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\n let abilityName = document.getElementById(\"selectAbility_\" + i);\n abilitiesArray[i] = { \"abilityHrid\": abilityName.value, \"level\": abilityLevelInput.value };\n }\n let drinksArray = [];\n for (let i = 0; i < drinks?.length; i++) {\n drinksArray.push({ \"itemHrid\": drinks[i] });\n }\n let foodArray = [];\n for (let i = 0; i < food?.length; i++) {\n foodArray.push({ \"itemHrid\": food[i] });\n }\n let state = {\n player: playerArray,\n food: { \"/action_types/combat\": foodArray },\n drinks: { \"/action_types/combat\": drinksArray },\n abilities: abilitiesArray,\n triggerMap: triggerMap,\n zone: zoneSelect.value,\n simulationTime: simulationTimeInput.value,\n houseRooms: player.houseRooms,\n achievements: player.achievements\n };\n try {\n playerDataMap[playerId] = JSON.stringify(state);\n } catch (err) {\n alert('Error copying to clipboard: ' + err);\n }\n}\n\nfunction updateNextPlayer(currentPlayerNumber) {\n let playerImportData = playerDataMap[currentPlayerNumber];\n let importSet = JSON.parse(playerImportData);\n [\"stamina\", \"intelligence\", \"attack\", \"melee\", \"defense\", \"ranged\", \"magic\"].forEach((skill) => {\n let levelInput = document.getElementById(\"inputLevel_\" + skill);\n if (skill == \"melee\" && !importSet.player[\"meleeLevel\"] && importSet.player[\"powerLevel\"]) {\n importSet.player[\"meleeLevel\"] = importSet.player[\"powerLevel\"];\n }\n levelInput.value = importSet.player[skill + \"Level\"];\n });\n\n [\"head\", \"body\", \"legs\", \"feet\", \"hands\", \"off_hand\", \"pouch\", \"neck\", \"earrings\", \"ring\", \"back\", \"charm\"].forEach((type) => {\n\n let equipmentSelect = document.getElementById(\"selectEquipment_\" + type);\n let enhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_\" + type);\n let currentEquipment = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/\" + type);\n if (currentEquipment !== undefined) {\n equipmentSelect.value = currentEquipment.itemHrid;\n enhancementLevelInput.value = currentEquipment.enhancementLevel;\n } else {\n equipmentSelect.value = \"\";\n enhancementLevelInput.value = 0;\n }\n });\n\n let weaponSelect = document.getElementById(\"selectEquipment_weapon\");\n let weaponEnhancementLevelInput = document.getElementById(\"inputEquipmentEnhancementLevel_weapon\");\n let mainhandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/main_hand\");\n let twohandWeapon = importSet.player.equipment.find(item => item.itemLocationHrid === \"/item_locations/two_hand\");\n if (mainhandWeapon !== undefined) {\n weaponSelect.value = mainhandWeapon.itemHrid;\n weaponEnhancementLevelInput.value = mainhandWeapon.enhancementLevel;\n } else if (twohandWeapon !== undefined) {\n weaponSelect.value = twohandWeapon.itemHrid;\n weaponEnhancementLevelInput.value = twohandWeapon.enhancementLevel;\n } else {\n weaponSelect.value = \"\";\n weaponEnhancementLevelInput.value = 0;\n }\n importSet.drinks = importSet.drinks[\"/action_types/combat\"];\n importSet.food = importSet.food[\"/action_types/combat\"];\n for (let i = 0; i < 3; i++) {\n let drinkSelect = document.getElementById(\"selectDrink_\" + i);\n let foodSelect = document.getElementById(\"selectFood_\" + i);\n if (importSet.drinks[i] != null) {\n drinkSelect.value = importSet.drinks[i].itemHrid.replace('power', 'melee');\n } else {\n drinkSelect.value = \"\";\n }\n if (importSet.food[i] != null) {\n foodSelect.value = importSet.food[i].itemHrid;\n } else {\n foodSelect.value = \"\";\n }\n }\n\n let hasSpecial = false;\n if (importSet.abilities && Object.keys(importSet.abilities).length == 5) {\n hasSpecial = true;\n }\n\n for (let i = 0; i < (hasSpecial ? 5 : 4); i++) {\n let abilitySlot = hasSpecial ? i : (i + 1);\n let abilitySelect = document.getElementById(\"selectAbility_\" + abilitySlot);\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + abilitySlot);\n\n if (hasSpecial && i == 0 && (\n importSet.abilities[i].abilityHrid == \"/abilities/aqua_aura\" ||\n importSet.abilities[i].abilityHrid == \"/abilities/flame_aura\" ||\n importSet.abilities[i].abilityHrid == \"/abilities/sylvan_aura\"\n )\n ) {\n importSet.abilities[i].abilityHrid = \"/abilities/mystic_aura\";\n }\n\n if (importSet.abilities[i].abilityHrid == \"/abilities/arcane_reflection\") {\n importSet.abilities[i].abilityHrid = \"/abilities/retribution\";\n }\n\n if (importSet.abilities[i] != null) {\n abilitySelect.value = importSet.abilities[i].abilityHrid;\n abilityLevelInput.value = String(importSet.abilities[i].level);\n } else {\n abilitySelect.value = \"\";\n abilityLevelInput.value = \"1\";\n }\n }\n\n if (importSet.triggerMap) {\n triggerMap = importSet.triggerMap;\n fixTriggerMap(triggerMap);\n }\n\n { // reset all houseRooms\n let houseRooms = Object.values(houseRoomDetailMap);\n for (const room of Object.values(houseRooms)) {\n const field = document.querySelector('[data-house-hrid=\"' + room.hrid + '\"]');\n field.value = '';\n player.houseRooms[room.hrid] = 0;\n }\n }\n if (importSet.houseRooms) {\n for (const room in importSet.houseRooms) {\n const field = document.querySelector('[data-house-hrid=\"' + room + '\"]');\n if (importSet.houseRooms[room]) {\n field.value = importSet.houseRooms[room];\n } else {\n field.value = '';\n }\n }\n player.houseRooms = importSet.houseRooms;\n }\n\n { // reset all achievements\n let achievements = Object.values(achievementDetailMap);\n for (const detail of Object.values(achievements)) {\n const field = document.querySelector('[data-achievement-hrid=\"' + detail.hrid + '\"]');\n field.checked = false;\n player.achievements[detail.hrid] = false;\n }\n }\n if (importSet.achievements) {\n for (const achievement in importSet.achievements) {\n const field = document.querySelector('[data-achievement-hrid=\"' + achievement + '\"]');\n if (importSet.achievements[achievement]) {\n field.checked = true;\n player.achievements[achievement] = true;\n } else {\n field.checked = false;\n player.achievements[achievement] = false;\n }\n }\n }\n refreshAchievementStatics();\n}\n\nfunction showErrorModal(error) {\n let zoneSelect = document.getElementById(\"selectZone\");\n let simulationTimeInput = document.getElementById(\"inputSimulationTime\");\n\n let state = {\n error: error,\n player: player,\n food: food,\n drinks: drinks,\n abilities: abilities,\n triggerMap: triggerMap,\n modalTriggers: modalTriggers,\n zone: zoneSelect.value,\n simulationTime: simulationTimeInput.value,\n };\n\n for (let i = 0; i < 5; i++) {\n let abilityLevelInput = document.getElementById(\"inputAbilityLevel_\" + i);\n state[\"abilityLevel\" + i] = abilityLevelInput.value;\n }\n\n let errorInput = document.getElementById(\"inputError\");\n errorInput.value = JSON.stringify(state);\n\n let errorModal = new bootstrap.Modal(document.getElementById(\"errorModal\"));\n errorModal.show();\n}\n\nwindow.prices;\n\nasync function fetchPrices() {\n let response = null;\n try {\n response = await fetch('https://www.milkywayidle.com/game_data/marketplace.json'\n , {\n mode: 'cors'\n }\n );\n if (!response.ok) {\n console.log('Error fetching prices');\n }\n } catch (error) {\n console.error(error);\n }\n\n if (response == null) {\n try {\n response = await fetch('https://www.milkywayidlecn.com/game_data/marketplace.json'\n , {\n mode: 'cors'\n }\n );\n if (!response.ok) {\n console.log('Error fetching prices');\n }\n } catch (error) {\n console.error(error);\n }\n }\n\n if (!response || !response.ok) {\n return;\n }\n\n try {\n\n let btn = document.querySelector('#buttonGetPrices');\n btn.style.backgroundColor = 'green';\n\n const pricesJson = await response.json();\n\n const priceTmp = pricesJson['marketData'];\n window.prices = {};\n for (const item in itemDetailMap) {\n const hrid = itemDetailMap[item].hrid;\n if (hrid in priceTmp) {\n window.prices[hrid] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\n if (priceTmp[hrid]['0']) {\n window.prices[hrid].ask = priceTmp[hrid]['0'].a;\n window.prices[hrid].bid = priceTmp[hrid]['0'].b;\n }\n }\n } \n\n window.prices[\"/items/coin\"] = { \"ask\": 1, \"bid\": 1, \"vendor\": 1 };\n\n window.prices[\"/items/small_treasure_chest\"] = {\n \"ask\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\n }).reduce((a, b) => a + b, 0),\n \"bid\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\n }).reduce((a, b) => a + b, 0),\n \"vendor\": openableLootDropMap[\"/items/small_treasure_chest\"].map((item) => {\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\n }).reduce((a, b) => a + b, 0),\n };\n\n window.prices[\"/items/medium_treasure_chest\"] = {\n \"ask\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\n }).reduce((a, b) => a + b, 0),\n \"bid\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\n }).reduce((a, b) => a + b, 0),\n \"vendor\": openableLootDropMap[\"/items/medium_treasure_chest\"].map((item) => {\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\n }).reduce((a, b) => a + b, 0),\n };\n\n window.prices[\"/items/large_treasure_chest\"] = {\n \"ask\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].ask * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\n }).reduce((a, b) => a + b, 0),\n \"bid\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].bid * item.dropRate * (item.maxCount + item.minCount) / 2 : 0;\n }).reduce((a, b) => a + b, 0),\n \"vendor\": openableLootDropMap[\"/items/large_treasure_chest\"].map((item) => {\n return item.itemHrid in window.prices ? window.prices[item.itemHrid].vendor : 0;\n }).reduce((a, b) => a + b, 0),\n };\n\n } catch (error) {\n console.error(error);\n }\n}\n\ndocument.getElementById(\"buttonGetPrices\").onclick = async () => {\n await fetchPrices();\n};\n\ndocument.addEventListener(\"input\", (e) => {\n let element = e.target;\n if (element.tagName == \"TD\" && element.parentNode.parentNode.parentNode.classList.value.includes('profit-table')) {\n let tableId = element.parentNode.parentNode.parentNode.id;\n let row = element.parentNode.querySelectorAll('td');\n let item = row[0].getAttribute('data-i18n').split('.')[1];\n let newPrice = element.innerText;\n\n let revenueSetting = document.getElementById('selectPrices_drops').value;\n let expensesSetting = document.getElementById('selectPrices_consumables').value;\n\n let expensesDifference = 0;\n let revenueDifference = 0;\n let noRngRevenueDifference = 0;\n\n if (tableId == 'expensesTable') {\n expensesDifference = updateTable('expensesTable', item, newPrice);\n if (revenueSetting == expensesSetting) {\n revenueDifference = updateTable('revenueTable', item, newPrice);\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\n }\n if (window.prices) {\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\n if (expensesSetting == 'bid') {\n window.prices[item]['bid'] = newPrice;\n } else {\n window.prices[item]['ask'] = newPrice;\n }\n }\n } else {\n revenueDifference = updateTable('revenueTable', item, newPrice);\n noRngRevenueDifference = updateTable('noRngRevenueTable', item, newPrice);\n if (revenueSetting == expensesSetting) {\n expensesDifference = updateTable('expensesTable', item, newPrice);\n }\n if (window.prices) {\n if (!window.prices[item]) window.prices[item] = { \"ask\": -1, \"bid\": -1, \"vendor\": itemDetailMap[item].sellPrice };\n if (revenueSetting == 'bid') {\n window.prices[item]['bid'] = newPrice;\n } else {\n window.prices[item]['ask'] = newPrice;\n }\n }\n }\n\n window.expenses += expensesDifference;\n document.getElementById('expensesSpan').innerText = window.expenses.toLocaleString();\n window.revenue += revenueDifference;\n document.getElementById('revenueSpan').innerText = window.revenue.toLocaleString();\n window.noRngRevenue += noRngRevenueDifference;\n document.getElementById('noRngRevenueSpan').innerText = window.noRngRevenue.toLocaleString();\n\n window.profit = window.revenue - window.expenses;\n document.getElementById('profitPreview').innerText = window.profit.toLocaleString();\n document.getElementById('profitSpan').innerText = window.profit.toLocaleString();\n window.noRngProfit = window.noRngRevenue - window.expenses;\n document.getElementById('noRngProfitSpan').innerText = window.noRngProfit.toLocaleString();\n document.getElementById('noRngProfitPreview').innerText = window.noRngProfit.toLocaleString();\n }\n});\n\nfunction updateTable(tableId, item, price) {\n let row = document.querySelector('#' + tableId + ' .' + CSS.escape(item));\n if (row == null) {\n return 0;\n }\n\n row = row.querySelectorAll('td');\n let priceTd = row[1];\n let amountTd = row[2];\n let totalTd = row[3];\n let oldTotal = totalTd.innerText;\n let newTotal = price * amountTd.innerText;\n\n if (priceTd.innerText != price) {\n priceTd.innerText = price;\n }\n totalTd.innerText = newTotal;\n\n return newTotal - oldTotal;\n}\n\n// #endregion\n\nfunction initPatchNotes() {\n const patchNotesRows = document.getElementById(\"patchNotes\");\n for (const pn in patchNote) {\n const patchNoteContainer = document.createElement(\"div\");\n patchNotesRows.setAttribute('class', 'col-12 mb-4');\n\n const patchNoteElement = document.createElement(\"h6\");\n patchNoteElement.innerHTML = pn;\n const patchNoteList = document.createElement(\"ul\");\n for (const note of patchNote[pn]) {\n const noteElement = document.createElement(\"li\");\n noteElement.innerHTML = note;\n patchNoteList.appendChild(noteElement);\n }\n patchNoteContainer.appendChild(patchNoteElement);\n patchNoteContainer.appendChild(patchNoteList);\n\n patchNotesRows.appendChild(patchNoteContainer);\n }\n}\n\nfunction initExtraBuffSection() {\n // mooPass\n let mooPassToggle = document.getElementById(\"mooPassToggle\");\n let mooPass = localStorage.getItem('mooPass');\n if (mooPass) {\n mooPassToggle.checked = Boolean(mooPass);\n }\n mooPassToggle.onchange = () => {\n localStorage.setItem('mooPass', mooPassToggle.checked);\n // 如果开启入战属性模式,实时更新\n if (isCombatReadyMode) {\n updateCombatStatsUI();\n }\n }\n \n // comExp\n let comExpToggle = document.getElementById(\"comExpToggle\");\n let comExpInput = document.getElementById(\"comExpInput\");\n let comExp = localStorage.getItem('comExp');\n if (comExp) {\n let comExpNumber = Number(comExp);\n if (comExpNumber > 0) {\n comExpToggle.checked = true;\n comExpInput.value = comExpNumber;\n } else {\n comExpToggle.checked = false;\n comExpInput.disabled = true;\n }\n }\n const updateComExp = () => {\n if (comExpToggle.checked) {\n let comExp = Number(comExpInput.value);\n localStorage.setItem('comExp', comExp); \n comExpInput.disabled = false;\n } else {\n localStorage.setItem('comExp', 0);\n comExpInput.disabled = true;\n }\n // 如果开启入战属性模式,实时更新\n if (isCombatReadyMode) {\n updateCombatStatsUI();\n }\n }\n comExpToggle.onchange = updateComExp;\n comExpInput.onchange = updateComExp;\n\n // comDrop\n let comDropToggle = document.getElementById(\"comDropToggle\");\n let comDropInput = document.getElementById(\"comDropInput\");\n let comDrop = localStorage.getItem('comDrop');\n if (comDrop) {\n let comDropNumber = Number(comDrop);\n if (comDropNumber > 0) {\n comDropToggle.checked = true;\n comDropInput.value = comDropNumber;\n } else {\n comDropToggle.checked = false;\n comDropInput.disabled = true;\n }\n }\n const updateComDrop = () => {\n if (comDropToggle.checked) {\n let comDrop = Number(comDropInput.value);\n localStorage.setItem('comDrop', comDrop); \n comDropInput.disabled = false;\n } else {\n localStorage.setItem('comDrop', 0);\n comDropInput.disabled = true;\n }\n // 如果开启入战属性模式,实时更新\n if (isCombatReadyMode) {\n updateCombatStatsUI();\n }\n }\n comDropToggle.onchange = updateComDrop;\n comDropInput.onchange = updateComDrop;\n}\n\n\nfunction updateState() {\n updateEquipmentState();\n updateLevels();\n updateFoodState();\n updateDrinksState();\n updateAbilityState();\n}\n\nfunction updateUI() {\n updateCombatStatsUI();\n updateFoodUI();\n updateDrinksUI();\n updateAbilityUI();\n\n updateContent();\n}\n\nconst darkModeToggle = document.getElementById('darkModeToggle');\nconst body = document.body;\n\nif (localStorage.getItem('darkModeEnabled') === 'true') {\n body.classList.add('dark-mode');\n const tables = document.getElementsByClassName('profit-table');\n for (const table of tables) {\n table.classList.toggle('table-striped');\n }\n darkModeToggle.checked = true;\n}\n\ndarkModeToggle.addEventListener('change', () => {\n body.classList.toggle('dark-mode');\n const tables = document.getElementsByClassName('profit-table');\n for (const table of tables) {\n table.classList.toggle('table-striped');\n }\n localStorage.setItem('darkModeEnabled', darkModeToggle.checked);\n});\n\nfunction updateContent() {\n document.querySelectorAll('[data-i18n]').forEach(function (element) {\n const key = element.getAttribute('data-i18n');\n if (key) {\n element.textContent = i18next.t(key);\n }\n });\n\n document.querySelectorAll('[data-i18n-placeholder]').forEach(function (element) {\n const key = element.getAttribute('data-i18n-placeholder');\n if (key) {\n element.placeholder = i18next.t(key);\n }\n });\n\n document.querySelectorAll('option[data-i18n]').forEach(function (element) {\n const key = element.getAttribute('data-i18n');\n if (key) {\n element.textContent = i18next.t(key);\n }\n });\n}\n\ninitEquipmentSection();\ninitHouseRoomsModal();\ninitAchievementsModal();\ninitLevelSection();\ninitFoodSection();\ninitDrinksSection();\ninitAbilitiesSection();\ninitZones();\ninitDungeons();\ninitTriggerModal();\ninitSimulationControls();\ninitEquipmentSetsModal();\ninitErrorHandling();\ninitImportExportModal();\ninitDamageDoneTaken();\ninitPatchNotes();\ninitExtraBuffSection();\ninitHpMpVisualization();\ninitCombatReadyStatsToggle();\n\nupdateState();\nupdateUI();\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/dist/index.html b/dist/index.html index 1e7dd54a..c878ff3d 100644 --- a/dist/index.html +++ b/dist/index.html @@ -1127,6 +1127,27 @@

+ +
+
+
+ HP/MP 可视化图表 +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+ +
+
+ HP/MP 可视化 +
+
+
+
+
+ + +
+
+
+
+
+
+
+ 显示入战属性 +
+
+
+
+
+ + +
+
+
+
+
+ 注:仅光环类技能会计入入战属性,其他技能因需要战斗触发条件故不计算。 +
+
@@ -1848,6 +1902,7 @@