From c9aca35c8cd372cd9df8ea241e2aed7c36629185 Mon Sep 17 00:00:00 2001
From: Foolishmetal <notfoolishmetal@gmail.com>
Date: Sun, 13 Aug 2023 03:18:48 +0200
Subject: [PATCH] Added an option to calculate rune efficiency using SWOP's
 updated formula.

---
 app/mapping.js                           |  42 ++++++
 app/plugins/rune-drop-efficiency-swop.js | 183 +++++++++++++++++++++++
 2 files changed, 225 insertions(+)
 create mode 100644 app/plugins/rune-drop-efficiency-swop.js

diff --git a/app/mapping.js b/app/mapping.js
index fbd7807e..442ba85e 100644
--- a/app/mapping.js
+++ b/app/mapping.js
@@ -1968,6 +1968,48 @@ module.exports = {
       max: (efficiency + ((Math.max(Math.ceil((12 - rune.upgrade_curr) / 3.0), 0) * 0.2) / 2.8) * 100).toFixed(toFixed),
     };
   },
+getRuneEfficiencySWOP(rune, toFixed = 2) {
+  let ratio = 0.0;
+
+  // main stat
+  ratio +=
+    this.rune.mainstat[rune.pri_eff[0]].max[this.isAncient(rune) ? rune.class - 10 : rune.class] / this.rune.mainstat[rune.pri_eff[0]].max[6];
+
+  // sub stats
+  rune.sec_eff.forEach((stat) => {
+    let value = stat[3] && stat[3] > 0 ? stat[1] + stat[3] : stat[1];
+
+    if (this.isFlatStat(stat[0])) {
+      value *= 0.5; // Multiply flat stats by 0.5
+    }
+
+    ratio += value / this.rune.substat[stat[0]].max[6];
+  });
+
+  // innate stat
+  if (rune.prefix_eff && rune.prefix_eff[0] > 0) {
+    let innateValue = rune.prefix_eff[1];
+
+    if (this.isFlatStat(rune.prefix_eff[0])) {
+      innateValue *= 0.5; // Multiply flat stats by 0.5
+    }
+
+    ratio += innateValue / this.rune.substat[rune.prefix_eff[0]].max[6];
+  }
+
+  let efficiency = (ratio / 2.8) * 100;
+
+  return {
+    current: ((ratio / 2.8) * 100).toFixed(toFixed),
+    max: (efficiency + ((Math.max(Math.ceil((12 - rune.upgrade_curr) / 3.0), 0) * 0.2) / 2.8) * 100).toFixed(toFixed),
+  };
+},
+isFlatStat(statType) {
+  // List of stat types that are considered flat
+  const flatStats = [1, 3, 5]; // ATK flat, DEF flat, HP flat
+
+  return flatStats.includes(statType);
+},
   getRuneEffect(eff) {
     const type = eff[0];
     const value = eff[1];
diff --git a/app/plugins/rune-drop-efficiency-swop.js b/app/plugins/rune-drop-efficiency-swop.js
new file mode 100644
index 00000000..14bbd9dd
--- /dev/null
+++ b/app/plugins/rune-drop-efficiency-swop.js
@@ -0,0 +1,183 @@
+module.exports = {
+    defaultConfig: {
+      enabled: true,
+    },
+    pluginName: 'RuneDropEfficiencySWOP',
+    pluginDescription: 'As per the updated SWOP formula: Logs the maximum possible efficiency for runes as they drop.',
+    init(proxy) {
+      proxy.on('apiCommand', (req, resp) => {
+        if (config.Config.Plugins[this.pluginName].enabled) {
+          this.processCommand(proxy, req, resp);
+        }
+      });
+    },
+    processCommand(proxy, req, resp) {
+      const { command } = req;
+      let runesInfo = [];
+  
+      // Extract the rune and display it's efficiency stats.
+      switch (command) {
+        case 'BattleDungeonResult':
+        case 'BattleScenarioResult':
+        case 'BattleDimensionHoleDungeonResult':
+          if (resp.win_lose === 1) {
+            const reward = resp.reward ? resp.reward : {};
+  
+            if (reward.crate && reward.crate.rune) {
+              runesInfo.push(this.logRuneDrop(reward.crate.rune));
+            }
+          }
+          break;
+        case 'BattleDungeonResult_V2':
+          if (resp.win_lose === 1) {
+            const rewards = resp.changed_item_list ? resp.changed_item_list : [];
+  
+            if (rewards) {
+              rewards.forEach((reward) => {
+                if (reward.type === 8) {
+                  runesInfo.push(this.logRuneDrop(reward.info));
+                }
+              });
+            }
+          }
+          break;
+        case 'upgradeRune_v2': {
+          const newLevel = resp.rune.upgrade_curr;
+  
+          if (newLevel % 3 === 0 && newLevel <= 12) {
+            runesInfo.push(this.logRuneDrop(resp.rune));
+          }
+          break;
+        }
+        case 'AmplifyRune':
+        case 'AmplifyRune_v2':
+        case 'ConvertRune':
+        case 'ConvertRune_v2':
+        case 'ConfirmRune':
+          runesInfo.push(this.logRuneDrop(resp.rune));
+          break;
+  
+        case 'BuyBlackMarketItem':
+          if (resp.runes && resp.runes.length === 1) {
+            runesInfo.push(this.logRuneDrop(resp.runes[0]));
+          }
+          break;
+  
+        case 'BuyGuildBlackMarketItem':
+          if (resp.runes && resp.runes.length === 1) {
+            runesInfo.push(this.logRuneDrop(resp.runes[0]));
+          }
+          break;
+  
+        case 'BuyShopItem':
+          if (resp.reward && resp.reward.crate && resp.reward.crate.runes) {
+            runesInfo.push(this.logRuneDrop(resp.reward.crate.runes[0]));
+          }
+          break;
+  
+        case 'GetBlackMarketList':
+          resp.market_list.forEach((item) => {
+            if (item.item_master_type === 8 && item.runes) {
+              runesInfo.push(this.logRuneDrop(item.runes[0]));
+            }
+          });
+          break;
+  
+        case 'GetGuildBlackMarketList':
+          resp.market_list.forEach((item) => {
+            if (item.item_master_type === 8 && item.runes) {
+              runesInfo.push(this.logRuneDrop(item.runes[0]));
+            }
+          });
+          break;
+  
+        case 'BattleWorldBossResult': {
+          const reward = resp.reward ? resp.reward : {};
+  
+          if (reward.crate && reward.crate.runes) {
+            reward.crate.runes.forEach((rune) => {
+              runesInfo.push(this.logRuneDrop(rune));
+            });
+          }
+          break;
+        }
+        case 'BattleRiftDungeonResult':
+          if (resp.item_list) {
+            resp.item_list.forEach((item) => {
+              if (item.type === 8) {
+                runesInfo.push(this.logRuneDrop(item.info));
+              }
+            });
+          }
+          break;
+  
+        case 'RevalueRune':
+          runesInfo.push('New rune efficiency value:' + this.logRuneDrop(resp.rune));
+  
+        default:
+          break;
+      }
+  
+      if (runesInfo.length > 0) {
+        proxy.log({
+          type: 'info',
+          source: 'plugin',
+          name: this.pluginName,
+          message: this.mountRuneListHtml(runesInfo),
+        });
+      }
+    },
+  
+    logRuneDrop(rune) {
+      const efficiency = gMapping.getRuneEfficiencySWOP(rune);
+      const runeQuality = gMapping.rune.quality[rune.rank];
+      const colorTable = {
+        Common: 'grey',
+        Magic: 'green',
+        Rare: 'blue',
+        Hero: 'purple',
+        Legend: 'orange',
+      };
+  
+      let color = colorTable[runeQuality];
+      let starHtml = this.mountStarsHtml(rune);
+  
+      return `<div class="rune item">
+                <div class="ui image ${color} label">
+                  <img src="../assets/runes/${gMapping.rune.sets[rune.set_id]}.png" />
+                  <span class="upgrade">+${rune.upgrade_curr}</span>  
+                </div>
+  
+                <div class="content">
+                  ${starHtml}
+                  <div class="header">${gMapping.isAncient(rune) ? 'Ancient ' : ''}${gMapping.rune.sets[rune.set_id]} Rune (${rune.slot_no}) ${
+        gMapping.rune.effectTypes[rune.pri_eff[0]]
+      }</div>
+                  <div class="description">Efficiency: ${efficiency.current}%. ${rune.upgrade_curr < 12 ? `Max: ${efficiency.max}%` : ''}</div>
+                </div>
+              </div>`;
+    },
+  
+    mountStarsHtml(rune) {
+      let count = 0;
+      let html = '<div class="star-line">';
+      let runeClass = gMapping.isAncient(rune) ? rune.class - 10 : rune.class;
+      while (count < runeClass) {
+        html = html.concat('<span class="star"><img src="../assets/icons/star-unawakened.png" /></span>');
+        count += 1;
+      }
+  
+      return html.concat('</div>');
+    },
+  
+    mountRuneListHtml(runes) {
+      let message = '<div class="runes ui list relaxed">';
+  
+      runes.forEach((rune) => {
+        message = message.concat(rune);
+      });
+  
+      return message.concat('</div>');
+    },
+  };
+  
\ No newline at end of file