NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Kamihime Project R - Raid log summary live // @description Shows summary table of raid participants' Damage/Actions as overlay in battle // @updateURL https://openuserjs.org/meta/nike/Kamihime_Project_R_-_Raid_log_summary_live.meta.js // @license MIT // @match https://gnkh-api-r.prod.nkh.dmmgames.com/front/cocos2d-proj/components-pc/game/app.html // @run-at document-start // ==/UserScript== (function() { var fontSize = 12 var intervalRaidMessages = setInterval(function() { if (typeof kh !== 'undefined' && kh.RaidMessageHandler && kh.BattleWorld && kh.RaidScenarioPlayer) { clearInterval(intervalRaidMessages); if (kh.raidMessages) { return; } kh.raidMessages = {} //object model: //{ // "job_id": 36, // "job_skin": 0, // "line_1_action": "Musyi's party ", // "line_2_action": " activated Multiple Erosion", // "is_mine": false, // "line_3_damage_result": "312,063 damage dealt", // "line_4_effect_result": "", // "timeStamp": 1672191944260 //} kh.RaidMessageHandler.prototype._postLog = function(message) { var battleId = cc?.director?._runningScene?.getBattleId() || 0 var battleLog = kh.raidMessages[battleId] if (!battleLog) { kh.raidMessages = {} kh.raidMessages[battleId] = [] battleLog = kh.raidMessages[battleId] } message.timeStamp = Date.now(); var existingMessage = battleLog.find((oldMsg) => { if (oldMsg.job_id != message.job_id || oldMsg.job_skin != message.job_skin || oldMsg.line_1_action != message.line_1_action || oldMsg.line_2_action != message.line_2_action || oldMsg.is_mine != message.is_mine || oldMsg.line_3_damage_result != message.line_3_damage_result || oldMsg.line_4_effect_result != message.line_4_effect_result) { return false; } var timeStampDiff = Math.abs(oldMsg.timeStamp - message.timeStamp) if (timeStampDiff < 50) { // detect duplicate messages return true; } return false; }) if (!existingMessage) { battleLog.push(message) } kh.createInstance("battleUI").raidMessageWindow.postMessage({ job_id: message.job_id, job_skin: message.job_skin }, message.line_1_action || "", message.line_2_action || "", message.line_3_damage_result || "", message.line_4_effect_result || "", message.play_sound ); }; var orig_start = kh.BattleWorld.prototype._start; kh.BattleWorld.prototype._start = async function new_start(sceneInstanceId) { this.UIBackLayer.getChildren()[1]?.getChildren()[0]?.setOpacity(0) kh.BattleWorld.prototype.updateRaidLog(cc?.director?._runningScene?.getBattleId()) return orig_start.apply(this, [sceneInstanceId]); } kh.BattleWorld.prototype.updateRaidLog = function(battleId) { if (!this.UIBackLayer) { return; } if (!this.raidLogNames) { this.raidLogNames = new ccui.Text() this.raidLogNames.setName("raidLogNames") this.raidLogNames.setFontSize(fontSize); this.raidLogNames.setAnchorPoint(0, 0) this.raidLogNames.setPosition(10, 80) this.raidLogNames.enableOutline(cc.color.BLACK, 2) this.raidLogNames.setFontName("GameFont") this.raidLogTotalDamage = new ccui.Text() this.raidLogTotalDamage.setName("raidLogTotalDamage") this.raidLogTotalDamage.setFontSize(fontSize); this.raidLogTotalDamage.setAnchorPoint(0, 0) this.raidLogTotalDamage.setPosition(10, 80) this.raidLogTotalDamage.enableOutline(cc.color.BLACK, 2) this.raidLogTotalDamage.setFontName("GameFont") this.raidLogNatkDamage = new ccui.Text() this.raidLogNatkDamage.setName("raidLogNatkDamage") this.raidLogNatkDamage.setFontSize(fontSize); this.raidLogNatkDamage.setAnchorPoint(0, 0) this.raidLogNatkDamage.setPosition(10, 80) this.raidLogNatkDamage.enableOutline(cc.color.BLACK, 2) this.raidLogNatkDamage.setFontName("GameFont") this.raidLogBurstDamage = new ccui.Text() this.raidLogBurstDamage.setName("raidLogBurstDamage") this.raidLogBurstDamage.setFontSize(fontSize); this.raidLogBurstDamage.setAnchorPoint(0, 0) this.raidLogBurstDamage.setPosition(10, 80) this.raidLogBurstDamage.enableOutline(cc.color.BLACK, 2) this.raidLogBurstDamage.setFontName("GameFont") this.raidLogAbilityDamage = new ccui.Text() this.raidLogAbilityDamage.setName("raidLogAbilityDamage") this.raidLogAbilityDamage.setFontSize(fontSize); this.raidLogAbilityDamage.setAnchorPoint(0, 0) this.raidLogAbilityDamage.setPosition(10, 80) this.raidLogAbilityDamage.enableOutline(cc.color.BLACK, 2) this.raidLogAbilityDamage.setFontName("GameFont") this.raidLogEidolonDamage = new ccui.Text() this.raidLogEidolonDamage.setName("raidLogEidolonDamage") this.raidLogEidolonDamage.setFontSize(fontSize); this.raidLogEidolonDamage.setAnchorPoint(0, 0) this.raidLogEidolonDamage.setPosition(10, 80) this.raidLogEidolonDamage.enableOutline(cc.color.BLACK, 2) this.raidLogEidolonDamage.setFontName("GameFont") this.raidLogSpeed = new ccui.Text() this.raidLogSpeed.setName("raidLogSpeed") this.raidLogSpeed.setFontSize(fontSize); this.raidLogSpeed.setAnchorPoint(0, 0) this.raidLogSpeed.setPosition(10, 80) this.raidLogSpeed.enableOutline(cc.color.BLACK, 2) this.raidLogSpeed.setFontName("GameFont") this.raidLogLayout = new ccui.Layout() this.raidLogLayout.setName("raidLogLayout") this.raidLogLayout.setAnchorPoint(0, 0) this.raidLogLayout.setPosition(10, 80) this.raidLogLayout.setBackGroundColorType(ccui.Layout.BG_COLOR_SOLID) this.raidLogLayout.setOpacity(128) this.raidLogLayout.setBackGroundColor(cc.color(0, 0, 0)) this.UIBackLayer.getChildren()[1]?.addChild(this.raidLogLayout) this.UIBackLayer.getChildren()[1]?.addChild(this.raidLogNames) this.UIBackLayer.getChildren()[1]?.addChild(this.raidLogTotalDamage) this.UIBackLayer.getChildren()[1]?.addChild(this.raidLogNatkDamage) this.UIBackLayer.getChildren()[1]?.addChild(this.raidLogBurstDamage) this.UIBackLayer.getChildren()[1]?.addChild(this.raidLogAbilityDamage) this.UIBackLayer.getChildren()[1]?.addChild(this.raidLogEidolonDamage) this.UIBackLayer.getChildren()[1]?.addChild(this.raidLogSpeed) } var battleEntries = kh.raidMessages[battleId] if (!battleEntries) { return } var repo = kh.createInstance("myselfInfoRepository") repo.getMe().then((me) => { var grouped = _.groupBy(battleEntries, (entry) => entry.line_1_action.trim() + "|" + entry.is_mine) var data = Object.entries(grouped).map((group) => { var split = group[0].split("|") var player = split[0] var isMine = split[1] == "true" var data = group[1] var totalEntries = data.length var totalDamage = data.filter((entry) => (entry.line_3_damage_result || "").includes("damage dealt")) .map((entry) => parseInt(entry.line_3_damage_result.replace(" damage dealt", "").replaceAll(",", "")) || 0) .reduce((a, b) => a + b, 0) var normalAttackEntries = data.filter((entry) => (entry.line_2_action || "") == "Attacked") var normalAttacksDamage = normalAttackEntries .map((entry) => parseInt(entry.line_3_damage_result.replace(" damage dealt", "").replaceAll(",", "")) || 0) .reduce((a, b) => a + b, 0) var normalAttackCount = normalAttackEntries.length var burstAttackEntries = data.filter((entry) => (entry.line_2_action || "") == "Burst Activated") var burstAttacksDamage = burstAttackEntries .map((entry) => parseInt(entry.line_3_damage_result.replace(" damage dealt", "").replaceAll(",", "")) || 0) .reduce((a, b) => a + b, 0) var burstAttackCount = burstAttackEntries.length var eidolonAttackEntries = data.filter((entry) => (entry.line_2_action || "") != "Burst Activated" && (entry.line_2_action || "").endsWith("Activated")) var eidolonAttacksDamage = eidolonAttackEntries .map((entry) => parseInt(entry.line_3_damage_result.replace(" damage dealt", "").replaceAll(",", "")) || 0) .reduce((a, b) => a + b, 0) var eidolonAttackCount = eidolonAttackEntries.length var abilityAttackEntries = data.filter((entry) => (entry.line_2_action || "").startsWith(" activated")) var abilityAttacksDamage = abilityAttackEntries .map((entry) => parseInt(entry.line_3_damage_result.replace(" damage dealt", "").replaceAll(",", "")) || 0) .reduce((a, b) => a + b, 0) var abilityAttackCount = abilityAttackEntries.length var speed = undefined if (data.length > 3) { var oldestEntry = data.reduce(function(prev, current) { return (prev.timeStamp < current.timeStamp) ? prev : current }).timeStamp var newestEntry = data.reduce(function(prev, current) { return (prev.timeStamp > current.timeStamp) ? prev : current }).timeStamp speed = ((newestEntry - oldestEntry) / (data.length - 1)) / 1000 } var defeated = !!data.find((entry) => entry.line_2_action == "has been defeated.") return { player: player.replace("'s party", ""), damage: totalDamage, actions: totalEntries, defeated: defeated, normalAttackCount: normalAttackCount, normalAttackDamage: normalAttacksDamage, burstAttackCount: burstAttackCount, burstAttackDamage: burstAttacksDamage, eidolonAttackCount: eidolonAttackCount, eidolonAttackDamage: eidolonAttacksDamage, abilityAttackCount: abilityAttackCount, abilityAttackDamage: abilityAttacksDamage, speed: speed, isMine: isMine } }) var sortedByDamage = data.sort((a, b) => (a.damage < b.damage) ? 1 : -1) //"Player: 125.6M 84.4M⚔(3) 3💥(84.4M) 16☄️(54.3M) 7🐦(0.4M) 5.6🏃 var namesText = "" sortedByDamage.forEach((entry) => { var entryText = "" var defeated = entry.defeated if (defeated) { entryText += "☠ " } if(entry.isMine) { var playerName = me.plainBody.name entryText += "⭐" + playerName } else { entryText += entry.player } namesText += entryText namesText += "\n" }) var totalDamageText = "" sortedByDamage.forEach((entry) => { var entryText = "" var damageText = entry.damage //.toLocaleString(entry.damage) var actions = entry.actions var damageTextMillions = ((damageText / 1000000).toFixed(1)).replace(".0", "") entryText += damageTextMillions + "M" totalDamageText += entryText totalDamageText += "\n" }) var natkDamageText = "" sortedByDamage.forEach((entry) => { var entryText = "" if (entry.normalAttackCount > 0) { var millionsStr = ((entry.normalAttackDamage / 1000000).toFixed(1)).replace(".0", "") entryText += "⚔️" + millionsStr + "M" + " (" + entry.normalAttackCount + ")" } natkDamageText += entryText natkDamageText += "\n" }) var burstDamageText = "" sortedByDamage.forEach((entry) => { var entryText = "" if (entry.burstAttackCount > 0) { var millionsStr = ((entry.burstAttackDamage / 1000000).toFixed(1)).replace(".0", "") entryText += "💥" + millionsStr + "M" + " (" + entry.burstAttackCount + ")" } burstDamageText += entryText burstDamageText += "\n" }) var abilityDamageText = "" sortedByDamage.forEach((entry) => { var entryText = "" if (entry.abilityAttackCount > 0) { var millionsStr = ((entry.abilityAttackDamage / 1000000).toFixed(1)).replace(".0", "") entryText += "☄️" + millionsStr + "M" + " (" + entry.abilityAttackCount + ")" } abilityDamageText += entryText abilityDamageText += "\n" }) var eidolonDamageText = "" sortedByDamage.forEach((entry) => { var entryText = "" if (entry.eidolonAttackCount > 0) { entryText += "🐦" + entry.eidolonAttackCount } entryText = entryText.trim() eidolonDamageText += entryText eidolonDamageText += "\n" }) var speedText = "" sortedByDamage.forEach((entry) => { var entryText = "" if (entry.speed) { entryText += "🏃" + entry.speed.toFixed(1) } speedText += entryText speedText += "\n" }) namesText = namesText.replace(/\n$/, "") totalDamageText = totalDamageText.replace(/\n$/, "") natkDamageText = natkDamageText.replace(/\n$/, "") burstDamageText = burstDamageText.replace(/\n$/, "") abilityDamageText = abilityDamageText.replace(/\n$/, "") eidolonDamageText = eidolonDamageText.replace(/\n$/, "") speedText = speedText.replace(/\n$/, "") var spacing = 5 this.raidLogNames.setText(namesText); this.raidLogTotalDamage.setText(totalDamageText) this.raidLogTotalDamage.setPosition(10 + this.raidLogNames.width + spacing, 80) this.raidLogNatkDamage.setText(natkDamageText) this.raidLogNatkDamage.setPosition(10 + this.raidLogNames.width + spacing + this.raidLogTotalDamage.width + spacing, 80) this.raidLogBurstDamage.setText(burstDamageText) this.raidLogBurstDamage.setPosition(10 + this.raidLogNames.width + spacing + this.raidLogTotalDamage.width + spacing + this.raidLogNatkDamage.width + spacing, 80) this.raidLogAbilityDamage.setText(abilityDamageText) this.raidLogAbilityDamage.setPosition(10 + this.raidLogNames.width + spacing + this.raidLogTotalDamage.width + spacing + this.raidLogNatkDamage.width + spacing + this.raidLogBurstDamage.width + spacing, 80) this.raidLogEidolonDamage.setText(eidolonDamageText) this.raidLogEidolonDamage.setPosition(10 + this.raidLogNames.width + spacing + this.raidLogTotalDamage.width + spacing + this.raidLogNatkDamage.width + spacing + this.raidLogBurstDamage.width + spacing + this.raidLogAbilityDamage.width + spacing, 80) this.raidLogSpeed.setText(speedText) this.raidLogSpeed.setPosition(10 + this.raidLogNames.width + spacing + this.raidLogTotalDamage.width + spacing + this.raidLogNatkDamage.width + spacing + this.raidLogBurstDamage.width + spacing + this.raidLogAbilityDamage.width + spacing + this.raidLogEidolonDamage.width + spacing, 80) var totalWidth = this.raidLogNames.width + spacing + this.raidLogTotalDamage.width + spacing + this.raidLogNatkDamage.width + spacing + this.raidLogBurstDamage.width + spacing + this.raidLogAbilityDamage.width + spacing + this.raidLogEidolonDamage.width + spacing + this.raidLogSpeed.width this.raidLogLayout.setContentSize(totalWidth, this.raidLogNames.height); }) } // simulate current player entries kh.RaidScenarioPlayer.prototype.enqueueOwnScenario = function(scenarioData) { if (!scenarioData || !scenarioData.length) { return Q.resolve(); } var ability = scenarioData.find((entry) => entry.cmd == "ability" && entry.from == "player") // contains damage var damage = scenarioData.filter((entry) => entry.cmd == "damage") var attacks = scenarioData.filter((entry) => entry.cmd == "attack" && entry.from == "player") // each attack contains damage var summons = scenarioData.filter((entry) => entry.cmd == "summon_damage") // each attack contains damage var bursts = scenarioData.filter((entry) => entry.cmd == "burst" && entry.from == "player") // each attack contains damage var burstStreak = scenarioData.filter((entry) => entry.cmd == "burst_streak") var bonusDamage = damage.map((e) => e.damage).flat(2).filter((e)=>e.to =="enemy").map((e) => e.value).reduce((a, b) => a + b, 0) var attackDamage = attacks.map((e) => e.damage).flat(2).map((e) => e.value).reduce((a, b) => a + b, 0) var summonDamage = summons.map((e) => e.damage).flat(2).map((e) => e.value).reduce((a, b) => a + b, 0) var burstStreakDamage = burstStreak.map((e) => e.damage).flat(2).map((e) => e.value).reduce((a, b) => a + b, 0) var totalDamage = bonusDamage + attackDamage + summonDamage + burstStreakDamage var raidLogModel = undefined var commaNumber = totalDamage.toLocaleString() var damageMessage = (totalDamage) ? (commaNumber + " damage dealt") : "" var timeStamp = Date.now() if (summons.length > 0) { raidLogModel = { "job_id": -1, "job_skin": 0, "line_1_action": "You's party ", "line_2_action": "Eidolon Activated", "is_mine": true, "line_3_damage_result": damageMessage, "line_4_effect_result": "", "timeStamp": timeStamp } } else if (bursts.length > 0) { raidLogModel = { "job_id": -1, "job_skin": 0, "line_1_action": "You's party ", "line_2_action": "Burst Activated", "is_mine": true, "line_3_damage_result": damageMessage, "line_4_effect_result": "", "timeStamp": timeStamp } } else if (ability) { raidLogModel = { "job_id": -1, "job_skin": 0, "line_1_action": "You's party ", "line_2_action": " activated " + ability.name, "is_mine": true, "line_3_damage_result": damageMessage, "line_4_effect_result": "", "timeStamp": timeStamp } } else if (attacks.length > 0) { raidLogModel = { "job_id": -1, "job_skin": 0, "line_1_action": "You's party ", "line_2_action": "Attacked", "is_mine": true, "line_3_damage_result": damageMessage, "line_4_effect_result": "", "timeStamp": timeStamp } } if (raidLogModel) { var message = raidLogModel var battleId = cc?.director?._runningScene?.getBattleId() || 0 var battleLog = kh.raidMessages[battleId] if (!battleLog) { kh.raidMessages = {} kh.raidMessages[battleId] = [] battleLog = kh.raidMessages[battleId] } message.timeStamp = Date.now(); var existingMessage = battleLog.find((oldMsg) => { if (oldMsg.job_id != message.job_id || oldMsg.job_skin != message.job_skin || oldMsg.line_1_action != message.line_1_action || oldMsg.line_2_action != message.line_2_action || oldMsg.is_mine != message.is_mine || oldMsg.line_3_damage_result != message.line_3_damage_result || oldMsg.line_4_effect_result != message.line_4_effect_result) { return false; } var timeStampDiff = Math.abs(oldMsg.timeStamp - message.timeStamp) if (timeStampDiff < 50) { // detect duplicate messages return true; } return false; }) if (!existingMessage) { battleLog.push(message) } } return this._assignDataToChannels(scenarioData, /* isOwnScenario */ true); } setInterval(() => { if (kh) { var battleId = cc?.director?._runningScene?.getBattleId?.() if (battleId) { kh.createInstance("battleWorld")?.updateRaidLog(battleId) } } }, 1000) } }, 10); })();