diff --git a/src/background/actionLog.ts b/src/background/actionLog.ts index d27d3aab..0c063b57 100644 --- a/src/background/actionLog.ts +++ b/src/background/actionLog.ts @@ -1,28 +1,14 @@ -import format from "date-fns/format"; - import postChannelMessage from "../broadcastChannel/postChannelMessage"; +import { ActionLogLine } from "../components/action-log-v2/types"; import globalStore from "./store"; -const actionLog = ( - seat: number, - time = new Date(), - str: string, - _grpId = 0 -): void => { - if (seat == -99) { - globalStore.currentActionLog = "version: 1\r\n"; - } else { - // const parsedStr = str.replace(/(<([^>]+)>)/gi, ""); - - globalStore.currentActionLog += `${seat}\r\n`; - globalStore.currentActionLog += `${format(time, "HH:mm:ss")}\r\n`; - globalStore.currentActionLog += `${str}\r\n`; +const actionLog = (line: ActionLogLine): void => { + globalStore.currentActionLog.lines.push(line); - postChannelMessage({ - type: "ACTION_LOG", - value: globalStore.currentActionLog, - }); - } + postChannelMessage({ + type: "ACTION_LOG", + value: globalStore.currentActionLog, + }); }; export default actionLog; diff --git a/src/background/greToClientInterpreter.ts b/src/background/greToClientInterpreter.ts index bd3d744d..fac00076 100644 --- a/src/background/greToClientInterpreter.ts +++ b/src/background/greToClientInterpreter.ts @@ -22,13 +22,11 @@ import { Annotations, DetailsKeyType, DetailsSrcDestCategoryType, - GameObject, } from "../types/greInterpreter"; import db from "../utils/database-wrapper"; import actionLog from "./actionLog"; import forceDeckUpdate from "./forceDeckUpdate"; import getMatchGameStats from "./getMatchGameStats"; -import getNameBySeat from "./getNameBySeat"; import globalStore from "./store"; import { addCardCast, @@ -69,7 +67,7 @@ function changePriority(previous: number, current: number, time: number): void { }); } -function setHeat(seat: number, value: number): void { +function _setHeat(seat: number, value: number): void { const { turnInfo } = globalStore.currentMatch; const heat = { value, @@ -91,7 +89,7 @@ function setHeat(seat: number, value: number): void { } } -function getGameObject(id: number): GameObject { +function getGameObject(id: number): GameObjectInfo { return globalStore.currentMatch.gameObjects[id]; } @@ -133,18 +131,9 @@ function isAnnotationProcessed(id: number): boolean { return anns.includes(id); } -const actionLogGenerateLink = (grpId: number): string => { - const card = db.card(grpId); - return card ? `${card.Name}` : ""; -}; - -const actionLogGenerateAbilityLink = (abId: number): string => { - return `ability`; -}; - const FACE_DOWN_CARD = 3; -function isObjectACard(card: GameObject): boolean { +function isObjectACard(card: GameObjectInfo): boolean { return ( card.type == "GameObjectType_Card" || card.type == "GameObjectType_SplitCard" @@ -166,7 +155,7 @@ class NoInstanceException { } } -function instanceIdToObject(iid: number): GameObject { +function instanceIdToObject(iid: number): GameObjectInfo { let instanceID = iid; const orig = instanceID; const { idChanges } = globalStore.currentMatch; @@ -188,6 +177,7 @@ const AnnotationType_ObjectIdChanged = (ann: Annotations): void => { setIdChange(ann.details); }; +// eslint-disable-next-line complexity const AnnotationType_ZoneTransfer = (ann: Annotations): void => { if (ann.type !== "AnnotationType_ZoneTransfer") return; @@ -195,7 +185,7 @@ const AnnotationType_ZoneTransfer = (ann: Annotations): void => { const fromZone = getZone(ann.details.zone_src); if (fromZone.type == "ZoneType_Sideboard") { const obj = instanceIdToObject(ann.affectedIds[0]); - if (obj.ownerSeatId == globalStore.currentMatch.playerSeat) { + if (obj.ownerSeatId == globalStore.currentMatch.playerSeat && obj.grpId) { addCardFromSideboard([obj.grpId]); } } @@ -205,37 +195,33 @@ const AnnotationType_ZoneTransfer = (ann: Annotations): void => { const affected = instanceIdToObject(ann.affectedIds[0]); const grpId = affected.grpId || 0; - const playerName = getNameBySeat(affected.controllerSeatId); - setHeat(affected.controllerSeatId, 1); - actionLog( - affected.controllerSeatId, - globalStore.currentMatch.logTime, - `${playerName} played ${actionLogGenerateLink(grpId)}`, - grpId - ); + actionLog({ + seat: affected.controllerSeatId || 0, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "PLAY", + grpId, + }); } // A player drew a card if (ann.details.category == "Draw") { const zone = getZone(ann.details.zone_src); - const playerName = getNameBySeat(zone.ownerSeatId || 0); const obj = getGameObject(ann.affectedIds[0]); const { playerSeat } = globalStore.currentMatch; - if (zone.ownerSeatId == playerSeat && obj) { - setHeat(zone.ownerSeatId, 1); - const grpId = obj.grpId || 0; - actionLog( - zone.ownerSeatId || 0, - globalStore.currentMatch.logTime, - `${playerName} drew ${actionLogGenerateLink(grpId)}`, - grpId - ); + if (zone.ownerSeatId == playerSeat && obj && obj.grpId) { + actionLog({ + seat: zone.ownerSeatId, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "DRAW", + grpId: obj.grpId, + }); } else { - actionLog( - zone.ownerSeatId || 0, - globalStore.currentMatch.logTime, - `${playerName} drew a card` - ); + actionLog({ + seat: zone.ownerSeatId || 0, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "DRAW", + grpId: null, + }); } } @@ -244,22 +230,22 @@ const AnnotationType_ZoneTransfer = (ann: Annotations): void => { const obj = instanceIdToObject(ann.affectedIds[0]) as GameObjectInfo; const grpId = obj.grpId || 0; const seat = obj.ownerSeatId || 0; - const playerName = getNameBySeat(seat); const { turnNumber } = globalStore.currentMatch.turnInfo; + // console.log("CAST", grpId, obj, ann); const cast = { grpId: grpId, turn: turnNumber || 0, player: seat, }; addCardCast(cast); - setHeat(seat, 1); - actionLog( - seat, - globalStore.currentMatch.logTime, - `${playerName} cast ${actionLogGenerateLink(grpId)}`, - grpId - ); + + actionLog({ + seat: obj.controllerSeatId || seat, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "CAST", + grpId, + }); } // A player discards a card @@ -267,13 +253,13 @@ const AnnotationType_ZoneTransfer = (ann: Annotations): void => { const obj = instanceIdToObject(ann.affectedIds[0]); const grpId = obj.grpId || 0; const seat = obj.ownerSeatId || 0; - const playerName = getNameBySeat(seat); - actionLog( - seat, - globalStore.currentMatch.logTime, - `${playerName} discarded ${actionLogGenerateLink(grpId)}`, - grpId - ); + + actionLog({ + seat: seat, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "DISCARD", + grpId, + }); } // A player puts a card in a zone @@ -283,23 +269,29 @@ const AnnotationType_ZoneTransfer = (ann: Annotations): void => { const { grpId } = obj; const affector = instanceIdToObject(ann.affectorId); const seat = obj.ownerSeatId || 0; - let text = getNameBySeat(seat); - if (affector.type == "GameObjectType_Ability") { - text = `${actionLogGenerateLink( - affector.objectSourceGrpId - )}'s ${actionLogGenerateAbilityLink(affector.grpId)}`; + + if (affector.type == "GameObjectType_Ability" && grpId) { + actionLog({ + seat: seat, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "ZONE_PUT", + sourceGrpId: affector.objectSourceGrpId || 0, + abilityId: affector.grpId, + grpId, + zone: getZoneName(zone || "ZoneType_None"), + }); } - if (isObjectACard(affector)) { - text = actionLogGenerateLink(affector.grpId); + if (isObjectACard(affector) && grpId && affector.grpId) { + actionLog({ + seat: seat, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "ZONE_PUT", + sourceGrpId: affector.grpId, + abilityId: undefined, + grpId, + zone: getZoneName(zone || "ZoneType_None"), + }); } - actionLog( - seat, - globalStore.currentMatch.logTime, - `${text} put ${actionLogGenerateLink(grpId)} in ${getZoneName( - zone || "ZoneType_None" - )}`, - grpId - ); } // A card is returned to a zone @@ -307,26 +299,34 @@ const AnnotationType_ZoneTransfer = (ann: Annotations): void => { const zone = getZone(ann.details.zone_dest).type; const affected = instanceIdToObject(ann.affectedIds[0]); const affector = instanceIdToObject(ann.affectorId); + const seat = affected.ownerSeatId || 0; - let text = ""; - if (affector.type == "GameObjectType_Ability") { - text = `${actionLogGenerateLink( - affector.objectSourceGrpId - )}'s ${actionLogGenerateAbilityLink(affector.grpId)}`; + if ( + affector.type == "GameObjectType_Ability" && + affected.grpId && + affector.objectSourceGrpId + ) { + actionLog({ + seat: seat, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "ZONE_RETURN", + sourceGrpId: affector.objectSourceGrpId, + abilityId: affector.grpId, + grpId: affected.grpId, + zone: getZoneName(zone || "ZoneType_None"), + }); } - if (isObjectACard(affector)) { - text = actionLogGenerateLink(affector.grpId); + if (isObjectACard(affector) && affected.grpId && affector.grpId) { + actionLog({ + seat: seat, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "ZONE_RETURN", + sourceGrpId: affector.grpId, + abilityId: undefined, + grpId: affected.grpId, + zone: getZoneName(zone || "ZoneType_None"), + }); } - - const seat = affected.ownerSeatId; - actionLog( - seat, - globalStore.currentMatch.logTime, - `${text} returned ${actionLogGenerateLink( - affected.grpId - )} to ${getZoneName(zone || "ZoneType_None")}`, - affected.grpId - ); } // A card was exiled @@ -334,23 +334,32 @@ const AnnotationType_ZoneTransfer = (ann: Annotations): void => { const affected = instanceIdToObject(ann.affectedIds[0]); const affector = instanceIdToObject(ann.affectorId); - let text = ""; - if (affector.type == "GameObjectType_Ability") { - text = `${actionLogGenerateLink( - affector.objectSourceGrpId - )}'s ${actionLogGenerateAbilityLink(affector.grpId)}`; - } - if (isObjectACard(affector)) { - text = actionLogGenerateLink(affector.grpId); - } + const seat = affector.ownerSeatId || 0; - const seat = affector.ownerSeatId; - actionLog( - seat, - globalStore.currentMatch.logTime, - `${text} exiled ${actionLogGenerateLink(affected.grpId)}`, + if ( + affector.type == "GameObjectType_Ability" && + affector.objectSourceGrpId && affected.grpId - ); + ) { + actionLog({ + seat: seat, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "EXILE", + sourceGrpId: affector.objectSourceGrpId, + abilityId: affector.grpId, + grpId: affected.grpId, + }); + } + if (isObjectACard(affector) && affected.grpId && affector.grpId) { + actionLog({ + seat: seat, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "EXILE", + sourceGrpId: affector.grpId, + abilityId: undefined, + grpId: affected.grpId, + }); + } } // Saw this one when Lava coil exiled a creature (??) @@ -363,40 +372,73 @@ const AnnotationType_ZoneTransfer = (ann: Annotations): void => { const affector = instanceIdToObject(ann.affectorId); const affected = instanceIdToObject(ann.affectedIds[0]); - let text = ""; - if (affector.type == "GameObjectType_Ability") { - text = `${actionLogGenerateLink( - affector.objectSourceGrpId - )}'s ${actionLogGenerateAbilityLink(affector.grpId)}`; + const seat = affector.ownerSeatId || 0; + if ( + affector.type == "GameObjectType_Ability" && + affector.objectSourceGrpId && + affected.grpId + ) { + actionLog({ + seat: seat, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "COUNTERED", + sourceGrpId: affector.objectSourceGrpId, + abilityId: affector.grpId, + grpId: affected.grpId, + }); } - if (isObjectACard(affector)) { - text = actionLogGenerateLink(affector.grpId); + if (isObjectACard(affector) && affected.grpId && affector.grpId) { + actionLog({ + seat: seat, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "COUNTERED", + sourceGrpId: affector.grpId, + abilityId: undefined, + grpId: affected.grpId, + }); } - - const seat = affector.ownerSeatId; - setHeat(seat, 1); - actionLog( - seat, - globalStore.currentMatch.logTime, - `${text} countered ${actionLogGenerateLink(affected.grpId)}`, - affected.grpId - ); } // A spell or ability destroys something if (ann.details.category == "Destroy") { - // + const affector = instanceIdToObject(ann.affectorId); + const affected = instanceIdToObject(ann.affectedIds[0]); + + const seat = affector.ownerSeatId || 0; + if ( + affector.type == "GameObjectType_Ability" && + affector.objectSourceGrpId && + affected.grpId + ) { + actionLog({ + seat: seat, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "DESTROYED", + sourceGrpId: affector.objectSourceGrpId, + abilityId: affector.grpId, + grpId: affected.grpId, + }); + } + if (isObjectACard(affector) && affected.grpId && affector.grpId) { + actionLog({ + seat: seat, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "DESTROYED", + sourceGrpId: affector.grpId, + abilityId: undefined, + grpId: affected.grpId, + }); + } } }; const AnnotationType_AbilityInstanceCreated = (ann: Annotations): void => { + return; if (ann.type !== "AnnotationType_AbilityInstanceCreated") return; - // const affected = ann.affectedIds[0]; + /* + const affected = ann.affectedIds[0]; const affector = instanceIdToObject(ann.affectorId); - setHeat(affector.controllerSeatId, 1); - - /* if (affector) { //currentMatch.gameObjs[affected] const newObj = { @@ -420,57 +462,69 @@ const AnnotationType_ResolutionStart = (ann: Annotations): void => { const affected = instanceIdToObject(ann.affectedIds[0]); const grpId = ann.details.grpid; - if (affected.type == "GameObjectType_Ability") { + if ( + affected.type == "GameObjectType_Ability" && + affected.controllerSeatId && + affected.objectSourceGrpId + ) { // affected.grpId = grpId; - setHeat(affected.controllerSeatId, 1); - actionLog( - affected.controllerSeatId, - globalStore.currentMatch.logTime, - `${actionLogGenerateLink( - affected.objectSourceGrpId - )}'s ${actionLogGenerateAbilityLink(grpId)}`, - grpId - ); + actionLog({ + seat: affected.controllerSeatId, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "ABILITY", + sourceGrpId: affected.objectSourceGrpId, + abilityId: grpId, + }); } }; const AnnotationType_DamageDealt = (ann: Annotations): void => { if (ann.type !== "AnnotationType_DamageDealt") return; - let recipient = ""; + + let targetId = 0; + let targetType = "PERMANENT"; + if (ann.affectedIds[0] < 5) { - recipient = getNameBySeat(ann.affectedIds[0]); + [targetId] = ann.affectedIds; + targetType = "PLAYER"; } else { const affected = instanceIdToObject(ann.affectedIds[0]); - recipient = actionLogGenerateLink(affected.grpId); + targetId = affected.grpId || 0; + targetType = "PERMANENT"; } const affector = instanceIdToObject(ann.affectorId); const dmg = ann.details.damage; - setHeat(affector.controllerSeatId, dmg); + const affectorGrpId = affector.grpId || 0; + if (affector.controllerSeatId == globalStore.currentMatch.playerSeat) { const pstats = globalStore.currentMatch.playerStats; - const prev = pstats.damage[affector.grpId]; - pstats.damage[affector.grpId] = (prev || 0) + dmg; + + const prev = pstats.damage[affectorGrpId]; + pstats.damage[affectorGrpId] = (prev || 0) + dmg; } else { const pstats = globalStore.currentMatch.oppStats; - const prev = pstats.damage[affector.grpId]; - pstats.damage[affector.grpId] = (prev || 0) + dmg; + + const prev = pstats.damage[affectorGrpId]; + pstats.damage[affectorGrpId] = (prev || 0) + dmg; } - actionLog( - affector.controllerSeatId, - globalStore.currentMatch.logTime, - `${actionLogGenerateLink( - affector.grpId - )} dealt ${dmg} damage to ${recipient}`, - affector.grpId - ); + + actionLog({ + seat: affector.controllerSeatId || 0, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "DAMAGE_DEALT", + sourceGrpId: affectorGrpId, + targetId, + targetType, + amount: dmg, + }); }; const AnnotationType_ModifiedLife = (ann: Annotations): void => { if (ann.type !== "AnnotationType_ModifiedLife") return; const affected = ann.affectedIds[0]; const total = getPlayer(affected)?.lifeTotal || 0 + ann.details.life; - const lifeStr = (ann.details.life > 0 ? "+" : "") + ann.details.life; + // const lifeStr = (ann.details.life > 0 ? "+" : "") + ann.details.life; const lifeAbs = Math.abs(ann.details.life); if (affected == globalStore.currentMatch.playerSeat) { @@ -485,51 +539,65 @@ const AnnotationType_ModifiedLife = (ann: Annotations): void => { globalStore.currentMatch.oppStats.lifeTotals.push(Math.max(0, total)); } - actionLog( - affected, - globalStore.currentMatch.logTime, - `${getNameBySeat(affected)} life changed (${lifeStr}) to ${total}` - ); + actionLog({ + seat: affected, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "MODIFIED_LIFE", + delta: ann.details.life, + total, + }); }; const AnnotationType_TargetSpec = (ann: Annotations): void => { if (ann.type !== "AnnotationType_TargetSpec") return; - let target; + + let targetId = 0; + let targetType = "PERMANENT"; + if (ann.affectedIds[0] < 5) { - target = getNameBySeat(ann.affectedIds[0]); + [targetId] = ann.affectedIds; + targetType = "PLAYER"; } else { - const { grpId } = instanceIdToObject(ann.affectedIds[0]); - target = actionLogGenerateLink(grpId); + const affected = instanceIdToObject(ann.affectedIds[0]); + targetId = affected.grpId || 0; + targetType = "PERMANENT"; } const affector = instanceIdToObject(ann.affectorId); - const seat = affector.ownerSeatId; - let text = getNameBySeat(seat); - setHeat(seat, 1); - if (affector.type == "GameObjectType_Ability") { - text = `${actionLogGenerateLink( - affector.objectSourceGrpId - )}'s ${actionLogGenerateAbilityLink(affector.grpId)}`; + const seat = affector.ownerSeatId || 0; + + if (affector.type == "GameObjectType_Ability" && affector.objectSourceGrpId) { + actionLog({ + seat, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "TARGET", + sourceGrpId: affector.objectSourceGrpId, + abilityId: affector.grpId, + targetType, + targetId, + }); } - if (isObjectACard(affector)) { - text = actionLogGenerateLink(affector.grpId); + if (isObjectACard(affector) && affector.grpId) { + actionLog({ + seat, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "TARGET", + sourceGrpId: affector.grpId, + abilityId: undefined, + targetType, + targetId, + }); } - actionLog( - seat, - globalStore.currentMatch.logTime, - `${text} targeted ${target}` - ); }; const AnnotationType_Scry = (ann: Annotations): void => { if (ann.type !== "AnnotationType_Scry") return; // REVIEW SCRY ANNOTATION - let affector = ann.affectorId; - if (affector > 3) { - affector = instanceIdToObject(affector).ownerSeatId; + let affector: GameObjectInfo | undefined; + if (ann.affectorId > 3) { + affector = instanceIdToObject(ann.affectorId); } - const { playerSeat } = globalStore.currentMatch; - const player = getNameBySeat(affector); + // const { playerSeat } = globalStore.currentMatch; const top = ann.details.topIds; const bottom = ann.details.bottomIds; @@ -545,37 +613,40 @@ const AnnotationType_Scry = (ann: Annotations): void => { const xtop = newTop.length; const xbottom = newBottom.length; const scrySize = xtop + xbottom; - setHeat(affector, scrySize); - actionLog( - affector, - globalStore.currentMatch.logTime, - `${player} scry ${scrySize}: ${xtop} top, ${xbottom} bottom` - ); - if (affector == playerSeat) { - if (xtop > 0) { - newTop.forEach((instanceId: number) => { - const { grpId } = instanceIdToObject(instanceId); - actionLog( - affector, - globalStore.currentMatch.logTime, - ` ${actionLogGenerateLink(grpId)} to the top`, - grpId - ); - }); - } - if (xbottom > 0) { - newBottom.forEach((instanceId: number) => { - const { grpId } = instanceIdToObject(instanceId); - actionLog( - affector, - globalStore.currentMatch.logTime, - ` ${actionLogGenerateLink(grpId)} to the bottom`, - grpId - ); - }); - } - } + actionLog({ + seat: affector?.ownerSeatId || ann.affectorId || 0, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "SCRY", + amount: scrySize, + top: newTop, + bottom: newBottom, + }); + + // if (affector == playerSeat) { + // if (xtop > 0) { + // newTop.forEach((instanceId: number) => { + // const { grpId } = instanceIdToObject(instanceId); + // actionLog( + // affector, + // globalStore.currentMatch.logTime, + // ` ${actionLogGenerateLink(grpId)} to the top`, + // grpId + // ); + // }); + // } + // if (xbottom > 0) { + // newBottom.forEach((instanceId: number) => { + // const { grpId } = instanceIdToObject(instanceId); + // actionLog( + // affector, + // globalStore.currentMatch.logTime, + // ` ${actionLogGenerateLink(grpId)} to the bottom`, + // grpId + // ); + // }); + // } + // } }; const AnnotationType_CardRevealed = (ann: Annotations): void => { @@ -587,14 +658,13 @@ const AnnotationType_CardRevealed = (ann: Annotations): void => { const zone = getZone(ann.details.source_zone); const owner = zone.ownerSeatId || 0; - actionLog( - owner, - globalStore.currentMatch.logTime, - `revealed ${actionLogGenerateLink(grpId)} from ${getZoneName( - zone.type || "ZoneType_None" - )}`, - grpId - ); + actionLog({ + seat: owner, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "REVEAL", + grpId, + zone: getZoneName(zone.type || "ZoneType_None"), + }); }); }; @@ -604,7 +674,7 @@ const AnnotationType_ManaPaid = (ann: Annotations): void => { let affector = ann.affectorId; if (affector > 3) { - affector = instanceIdToObject(affector).ownerSeatId; + affector = instanceIdToObject(affector).ownerSeatId || 0; } if (affector == playerSeat) { @@ -806,7 +876,7 @@ function getOppUsedCards(): number[] { if (obj.ownerSeatId == oppSeat && isObjectACard(obj)) { grpId = obj.grpId; // debugLog(zone.type, db.card(grpId).name, obj); - if (grpId !== FACE_DOWN_CARD) cardsUsed.push(grpId); + if (grpId && grpId !== FACE_DOWN_CARD) cardsUsed.push(grpId); } } catch (e) { // @@ -980,8 +1050,8 @@ function checkForStartingLibrary(gameState?: GameStateMessage): boolean { const mull = player.mulliganCount || 0; // If this is the first hand drawn or we made a mulligan if (mull > 0 || currentMatch.handsDrawn.length == 0) { - const drawn = hand.map((n) => getGameObject(n).grpId); - setHandDrawn(mull, drawn); + const drawn = hand.map((n) => getGameObject(n).grpId).filter((n) => n); + setHandDrawn(mull, drawn as number[]); console.log(`Mulligan: ${mull}, ${drawn}`); } } @@ -1006,6 +1076,7 @@ function checkForStartingLibrary(gameState?: GameStateMessage): boolean { } function checkTurnDiff(turnInfo: TurnInfo): void { + // console.log("checkTurnDiff", turnInfo.turnNumber, turnInfo); const { currentMatch } = globalStore; const currentTurnInfo = currentMatch.turnInfo; const { currentPriority } = currentMatch; @@ -1016,7 +1087,59 @@ function checkTurnDiff(turnInfo: TurnInfo): void { ) { setOnThePlay(turnInfo.activePlayer); } - // console.log("checkTurnDiff", currentMatch.logTime); + + if ( + turnInfo.step === "Step_DeclareBlock" && + currentTurnInfo.step !== turnInfo.step + ) { + // Find all attacking creatures and add it to the action log + const attackersByTarget: Record = {}; + if (turnInfo.phase && turnInfo.step === "Step_DeclareBlock") { + Object.values(globalStore.currentMatch.zones) + .filter((z) => z.type === "ZoneType_Battlefield") + .forEach((zone) => { + if (zone.objectInstanceIds) { + zone.objectInstanceIds.forEach((id: number) => { + try { + const obj = getGameObject(id); + if ( + obj.attackState === "AttackState_Attacking" && + obj.grpId && + obj.grpId !== FACE_DOWN_CARD && + obj.attackInfo && + obj.attackInfo.targetId + ) { + const { targetId } = obj.attackInfo; + + if (attackersByTarget[targetId]) { + attackersByTarget[targetId].push(obj.grpId); + } else { + attackersByTarget[targetId] = [obj.grpId]; + } + } + } catch (e) { + // + } + }); + } + }); + } + + console.log("attackersByTarget", attackersByTarget); + Object.entries(attackersByTarget).forEach(([id, attackers]) => { + const targetId = parseInt(id); + const targetType = targetId < 5 ? "PLAYER" : "PERMANENT"; + actionLog({ + type: "ATTACK", + seat: turnInfo.activePlayer || -1, + timestamp: globalStore.currentMatch.logTime.getTime(), + grpIds: attackers, + targetType, + targetId, + }); + }); + } + if (turnInfo.priorityPlayer !== currentPriority) { changePriority( currentPriority, @@ -1026,13 +1149,31 @@ function checkTurnDiff(turnInfo: TurnInfo): void { } if (currentTurnInfo.turnNumber !== turnInfo.turnNumber) { currentMatch.totalTurns += 1; - actionLog( - -1, - currentMatch.logTime, - `${getNameBySeat(turnInfo.activePlayer || 0)}'s turn begins. (#${ - turnInfo.turnNumber - })` - ); + actionLog({ + type: "TURN_INFO", + seat: -1, + timestamp: globalStore.currentMatch.logTime.getTime(), + subType: "BEGIN", + ...turnInfo, + }); + } + if (currentTurnInfo.phase !== turnInfo.phase) { + actionLog({ + type: "TURN_INFO", + seat: -1, + timestamp: globalStore.currentMatch.logTime.getTime(), + subType: "PHASE", + ...turnInfo, + }); + } + if (currentTurnInfo.step !== turnInfo.step) { + actionLog({ + type: "TURN_INFO", + seat: -1, + timestamp: globalStore.currentMatch.logTime.getTime(), + subType: "STEP", + ...turnInfo, + }); } } @@ -1128,19 +1269,27 @@ const GREMessageType_IntermissionReq = (msg: GREToClientMessage): void => { if (msg.intermissionReq?.result) { const result = msg.intermissionReq?.result; setGameWinner(result.winningTeamId || 0); - const winnerName = getNameBySeat(result.winningTeamId || 0); - const loserName = getNameBySeat(result.winningTeamId == 2 ? 1 : 2); + const winner = result.winningTeamId || 0; + const loser = result.winningTeamId == 2 ? 1 : 2; if (result.reason == "ResultReason_Concede") { - actionLog(-1, globalStore.currentMatch.logTime, `${loserName} conceded.`); + actionLog({ + seat: loser, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "CONCEDED", + }); } else if (result.reason == "ResultReason_Timeout") { - actionLog( - -1, - globalStore.currentMatch.logTime, - `${loserName} timed out.` - ); + actionLog({ + seat: loser, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "TIMED_OUT", + }); } - actionLog(-1, globalStore.currentMatch.logTime, `${winnerName} wins!`); + actionLog({ + seat: winner, + timestamp: globalStore.currentMatch.logTime.getTime(), + type: "WIN", + }); } getMatchGameStats(); }; diff --git a/src/background/onLabel/MatchGameRoomStateChangedEvent.ts b/src/background/onLabel/MatchGameRoomStateChangedEvent.ts index 9bd2a673..d88507c7 100644 --- a/src/background/onLabel/MatchGameRoomStateChangedEvent.ts +++ b/src/background/onLabel/MatchGameRoomStateChangedEvent.ts @@ -87,7 +87,13 @@ export default function onLabelMatchGameRoomStateChangedEvent( // Now only when a match begins if (gameRoom.stateType == "MatchGameRoomStateType_Playing") { - actionLog(-99, globalStore.currentMatch.logTime, ""); + globalStore.currentActionLog.lines = []; + globalStore.currentActionLog.players = []; + actionLog({ + seat: -1, + type: "START", + timestamp: globalStore.currentMatch.logTime.getTime(), + }); resetCurrentMatch(); const playerId = getLocalSetting("playerId"); // let oppId = ""; @@ -119,6 +125,12 @@ export default function onLabelMatchGameRoomStateChangedEvent( } gameRoom.gameRoomConfig.reservedPlayers.forEach((player) => { + globalStore.currentActionLog.players.push({ + name: player.playerName, + seat: player.systemSeatId, + userId: player.userId, + }); + if (player.userId == playerId) { setPlayer({ seat: player.systemSeatId, diff --git a/src/background/saveMatch.ts b/src/background/saveMatch.ts index a011506e..fe59b460 100644 --- a/src/background/saveMatch.ts +++ b/src/background/saveMatch.ts @@ -85,7 +85,7 @@ function generateInternalMatch(): InternalMatch { toolRunFromSource: !remote?.app.isPackaged, arenaId: currentMatch.player.name, playerDeckHash: globalStore.currentMatch.originalDeck.getHash(), - actionLog: globalStore.currentActionLog, + actionLog: globalStore.currentActionLog as any, type: "match", }; diff --git a/src/background/store/index.ts b/src/background/store/index.ts index 47dc5bbf..ffe2d802 100644 --- a/src/background/store/index.ts +++ b/src/background/store/index.ts @@ -1,3 +1,4 @@ +import { ActionLogV2 } from "../../components/action-log-v2/types"; import { CombinedRankInfo } from "../onLabel/InEventGetCombinedRankInfo"; import { Course } from "../onLabel/InEventGetCourses"; import { draftStateObject } from "./currentDraftStore"; @@ -8,7 +9,11 @@ import { matchStateObject } from "./currentMatchStore"; const globalStore = { currentMatch: matchStateObject, currentDraft: draftStateObject, - currentActionLog: "", + currentActionLog: { + lines: [], + players: [], + version: 2, + } as ActionLogV2, currentCourses: {} as Record, rank: { constructedSeasonOrdinal: 0, diff --git a/src/background/store/types.ts b/src/background/store/types.ts index 692684f9..df537dfd 100644 --- a/src/background/store/types.ts +++ b/src/background/store/types.ts @@ -2,7 +2,6 @@ import { CardCast, Chances, Deck, - GameObject, Heat, InternalDeck, InternalPlayer, @@ -12,6 +11,7 @@ import { import { AnnotationInfo, GameInfo, + GameObjectInfo, GREToClientMessage, PlayerInfo, TurnInfo, @@ -61,7 +61,7 @@ export interface MatchState { zones: Record; annotations: Record; processedAnnotations: number[]; - gameObjects: Record; + gameObjects: Record; initialLibraryInstanceIds: number[]; instanceToCardIdMap: Record; idChanges: Record; diff --git a/src/broadcastChannel/channelMessages.ts b/src/broadcastChannel/channelMessages.ts index f867671c..caec4871 100644 --- a/src/broadcastChannel/channelMessages.ts +++ b/src/broadcastChannel/channelMessages.ts @@ -8,6 +8,7 @@ import { import { CombinedRankInfo } from "../background/onLabel/InEventGetCombinedRankInfo"; import { OverlayUpdateMatchState } from "../background/store/types"; import { OverlaySettings } from "../common/defaultConfig"; +import { ActionLogV2 } from "../components/action-log-v2/types"; import { DbDraftVote, DbInventoryInfo } from "../types/dbTypes"; import { ClientSceneChange } from "../types/logDecoder"; @@ -66,7 +67,7 @@ export interface StopLogReadingMessage extends ChannelMessageBase { export interface ActionLogMessage extends ChannelMessageBase { type: "ACTION_LOG"; - value: string; + value: ActionLogV2; } export interface LogMessageRecvMessage extends ChannelMessageBase { diff --git a/src/components/action-log-v2/LineAbility.tsx b/src/components/action-log-v2/LineAbility.tsx new file mode 100644 index 00000000..5eb37037 --- /dev/null +++ b/src/components/action-log-v2/LineAbility.tsx @@ -0,0 +1,19 @@ +import LogAbility from "./LogAbility"; +import LogCard from "./LogCard"; +import { ActionLogLineProps } from "./types"; + +export default function LineAbility(props: ActionLogLineProps) { + const { line } = props; + + if (line.type !== "ABILITY") return <>; + + return ( + <> +
+ + 's  + {line.abilityId ? : "ability"} +
+ + ); +} diff --git a/src/components/action-log-v2/LineAttack.tsx b/src/components/action-log-v2/LineAttack.tsx new file mode 100644 index 00000000..af58d326 --- /dev/null +++ b/src/components/action-log-v2/LineAttack.tsx @@ -0,0 +1,32 @@ +import getPlayerBySeat from "./getPlayerBySeat"; +import LogCard from "./LogCard"; +import { ActionLogLineProps } from "./types"; + +export default function LineAttack(props: ActionLogLineProps) { + const { line, players } = props; + + if (line.type !== "ATTACK") return <>; + + return ( +
+ {getPlayerBySeat(line.seat, players)} +  attacked with  + {line.grpIds.map((grpId, index) => { + const len = line.grpIds.length; + return ( + <> + + {index < len - 2 && len > 2 && <>, } + {index === len - 2 && <> and } + + ); + })} +  to  + {line.targetType === "PLAYER" ? ( + getPlayerBySeat(line.targetId, players) + ) : ( + + )} +
+ ); +} diff --git a/src/components/action-log-v2/LineCast.tsx b/src/components/action-log-v2/LineCast.tsx new file mode 100644 index 00000000..7be0bb60 --- /dev/null +++ b/src/components/action-log-v2/LineCast.tsx @@ -0,0 +1,20 @@ +import getPlayerBySeat from "./getPlayerBySeat"; +import LogCard from "./LogCard"; +import { ActionLogLineProps } from "./types"; + +export default function LineCast(props: ActionLogLineProps) { + const { line, players } = props; + + if (line.type !== "CAST") return <>; + + const playerName = getPlayerBySeat(line.seat || 0, players); + + return ( + <> +
+ {playerName} cast  + {line.grpId ? : "a spell"} +
+ + ); +} diff --git a/src/components/action-log-v2/LineConceded.tsx b/src/components/action-log-v2/LineConceded.tsx new file mode 100644 index 00000000..708054dc --- /dev/null +++ b/src/components/action-log-v2/LineConceded.tsx @@ -0,0 +1,18 @@ +import getPlayerBySeat from "./getPlayerBySeat"; +import { ActionLogLineProps } from "./types"; + +export default function LineConceded(props: ActionLogLineProps) { + const { line, players } = props; + + if (line.type !== "CONCEDED") return <>; + + const playerName = getPlayerBySeat(line.seat || 0, players); + + return ( + <> +
+ {playerName} conceded. +
+ + ); +} diff --git a/src/components/action-log-v2/LineCountered.tsx b/src/components/action-log-v2/LineCountered.tsx new file mode 100644 index 00000000..e8528b81 --- /dev/null +++ b/src/components/action-log-v2/LineCountered.tsx @@ -0,0 +1,27 @@ +import LogAbility from "./LogAbility"; +import LogCard from "./LogCard"; +import { ActionLogLineProps } from "./types"; + +export default function LineCountered(props: ActionLogLineProps) { + const { line } = props; + + if (line.type !== "COUNTERED") return <>; + + return ( + <> +
+ {line.abilityId ? ( + <> + + 's  + + + ) : ( + + )} +  countered  + +
+ + ); +} diff --git a/src/components/action-log-v2/LineDamageDealt.tsx b/src/components/action-log-v2/LineDamageDealt.tsx new file mode 100644 index 00000000..8bf2884f --- /dev/null +++ b/src/components/action-log-v2/LineDamageDealt.tsx @@ -0,0 +1,23 @@ +import getPlayerBySeat from "./getPlayerBySeat"; +import LogCard from "./LogCard"; +import { ActionLogLineProps } from "./types"; + +export default function LineDamageDealt(props: ActionLogLineProps) { + const { line, players } = props; + + if (line.type !== "DAMAGE_DEALT") return <>; + + return ( + <> +
+ +  dealt {line.amount} damage to  + {line.targetType === "PLAYER" ? ( + getPlayerBySeat(line.targetId, players) + ) : ( + + )} +
+ + ); +} diff --git a/src/components/action-log-v2/LineDestroyed.tsx b/src/components/action-log-v2/LineDestroyed.tsx new file mode 100644 index 00000000..566ea0a2 --- /dev/null +++ b/src/components/action-log-v2/LineDestroyed.tsx @@ -0,0 +1,27 @@ +import LogAbility from "./LogAbility"; +import LogCard from "./LogCard"; +import { ActionLogLineProps } from "./types"; + +export default function LineDestroyed(props: ActionLogLineProps) { + const { line } = props; + + if (line.type !== "DESTROYED") return <>; + + return ( + <> +
+ {line.abilityId ? ( + <> + + 's  + + + ) : ( + + )} +  destroyed  + +
+ + ); +} diff --git a/src/components/action-log-v2/LineDiscard.tsx b/src/components/action-log-v2/LineDiscard.tsx new file mode 100644 index 00000000..1a2b8cce --- /dev/null +++ b/src/components/action-log-v2/LineDiscard.tsx @@ -0,0 +1,20 @@ +import getPlayerBySeat from "./getPlayerBySeat"; +import LogCard from "./LogCard"; +import { ActionLogLineProps } from "./types"; + +export default function LineDiscard(props: ActionLogLineProps) { + const { line, players } = props; + + if (line.type !== "DISCARD") return <>; + + const playerName = getPlayerBySeat(line.seat || 0, players); + + return ( + <> +
+ {playerName} discarded  + {line.grpId ? : "a card"} +
+ + ); +} diff --git a/src/components/action-log-v2/LineDraw.tsx b/src/components/action-log-v2/LineDraw.tsx new file mode 100644 index 00000000..b587a496 --- /dev/null +++ b/src/components/action-log-v2/LineDraw.tsx @@ -0,0 +1,20 @@ +import getPlayerBySeat from "./getPlayerBySeat"; +import LogCard from "./LogCard"; +import { ActionLogLineProps } from "./types"; + +export default function LineDraw(props: ActionLogLineProps) { + const { line, players } = props; + + if (line.type !== "DRAW") return <>; + + const playerName = getPlayerBySeat(line.seat || 0, players); + + return ( + <> +
+ {playerName} drew  + {line.grpId ? : "a card"} +
+ + ); +} diff --git a/src/components/action-log-v2/LineExile.tsx b/src/components/action-log-v2/LineExile.tsx new file mode 100644 index 00000000..a799c4c5 --- /dev/null +++ b/src/components/action-log-v2/LineExile.tsx @@ -0,0 +1,27 @@ +import LogAbility from "./LogAbility"; +import LogCard from "./LogCard"; +import { ActionLogLineProps } from "./types"; + +export default function LineExile(props: ActionLogLineProps) { + const { line } = props; + + if (line.type !== "EXILE") return <>; + + return ( + <> +
+ {line.abilityId ? ( + <> + + 's  + + + ) : ( + + )} +  exiled  + +
+ + ); +} diff --git a/src/components/action-log-v2/LineModifiedLife.tsx b/src/components/action-log-v2/LineModifiedLife.tsx new file mode 100644 index 00000000..a293e9eb --- /dev/null +++ b/src/components/action-log-v2/LineModifiedLife.tsx @@ -0,0 +1,19 @@ +import getPlayerBySeat from "./getPlayerBySeat"; +import { ActionLogLineProps } from "./types"; + +export default function LineModifiedLife(props: ActionLogLineProps) { + const { line, players } = props; + + if (line.type !== "MODIFIED_LIFE") return <>; + + const playerName = getPlayerBySeat(line.seat || 0, players); + + return ( + <> +
+ {playerName} {line.delta < 0 ? "lost" : "gained"}  + {Math.abs(line.delta)} life ({line.total}). +
+ + ); +} diff --git a/src/components/action-log-v2/LinePlay.tsx b/src/components/action-log-v2/LinePlay.tsx new file mode 100644 index 00000000..5f7855dc --- /dev/null +++ b/src/components/action-log-v2/LinePlay.tsx @@ -0,0 +1,20 @@ +import getPlayerBySeat from "./getPlayerBySeat"; +import LogCard from "./LogCard"; +import { ActionLogLineProps } from "./types"; + +export default function LinePlay(props: ActionLogLineProps) { + const { line, players } = props; + + if (line.type !== "PLAY") return <>; + + const playerName = getPlayerBySeat(line.seat || 0, players); + + return ( + <> +
+ {playerName} played  + {line.grpId ? : "a land"} +
+ + ); +} diff --git a/src/components/action-log-v2/LineScry.tsx b/src/components/action-log-v2/LineScry.tsx new file mode 100644 index 00000000..188ee771 --- /dev/null +++ b/src/components/action-log-v2/LineScry.tsx @@ -0,0 +1,19 @@ +import getPlayerBySeat from "./getPlayerBySeat"; +import { ActionLogLineProps } from "./types"; + +export default function LineScry(props: ActionLogLineProps) { + const { line, players } = props; + + if (line.type !== "SCRY") return <>; + + const playerName = getPlayerBySeat(line.seat || 0, players); + + return ( + <> +
+ {playerName} scry {line.amount}, put {line.top.length} +  on top and {line.bottom.length} on bottom. +
+ + ); +} diff --git a/src/components/action-log-v2/LineTarget.tsx b/src/components/action-log-v2/LineTarget.tsx new file mode 100644 index 00000000..7b283798 --- /dev/null +++ b/src/components/action-log-v2/LineTarget.tsx @@ -0,0 +1,32 @@ +import getPlayerBySeat from "./getPlayerBySeat"; +import LogAbility from "./LogAbility"; +import LogCard from "./LogCard"; +import { ActionLogLineProps } from "./types"; + +export default function LineTarget(props: ActionLogLineProps) { + const { line, players } = props; + + if (line.type !== "TARGET") return <>; + + return ( + <> +
+ {line.abilityId ? ( + <> + + 's  + + + ) : ( + + )} +  targeted  + {line.targetType === "PLAYER" ? ( + getPlayerBySeat(line.targetId, players) + ) : ( + + )} +
+ + ); +} diff --git a/src/components/action-log-v2/LineTimedOut.tsx b/src/components/action-log-v2/LineTimedOut.tsx new file mode 100644 index 00000000..1e71a00d --- /dev/null +++ b/src/components/action-log-v2/LineTimedOut.tsx @@ -0,0 +1,18 @@ +import getPlayerBySeat from "./getPlayerBySeat"; +import { ActionLogLineProps } from "./types"; + +export default function LineTimedOut(props: ActionLogLineProps) { + const { line, players } = props; + + if (line.type !== "TIMED_OUT") return <>; + + const playerName = getPlayerBySeat(line.seat || 0, players); + + return ( + <> +
+ {playerName} timed out. +
+ + ); +} diff --git a/src/components/action-log-v2/LineTurnInfo.tsx b/src/components/action-log-v2/LineTurnInfo.tsx new file mode 100644 index 00000000..dc6ac982 --- /dev/null +++ b/src/components/action-log-v2/LineTurnInfo.tsx @@ -0,0 +1,40 @@ +import { toMMSS } from "../../utils/dateTo"; +import getPlayerBySeat from "./getPlayerBySeat"; +import { ActionLogLineProps } from "./types"; + +const phasesMap: Record = { + Phase_None: "None", + Phase_Beginning: "Beginning Phase", + Phase_Main1: "First Main Phase", + Phase_Combat: "Combat", + Phase_Main2: "Second Main Phase", + Phase_Ending: "End Phase", +}; + +export default function LineTurnInfo(props: ActionLogLineProps) { + const { line, players, timeStart } = props; + + if (line.type !== "TURN_INFO") return <>; + + const timeDiff = toMMSS(Math.round((line.timestamp - timeStart) / 1000)); + + return ( + <> + {line.subType === "BEGIN" && ( +
+
+ Turn {line.turnNumber} + +{timeDiff} +
+
+ {getPlayerBySeat(line.activePlayer || 0, players)} +
+
+ )} + + {line.subType === "PHASE" && line.phase && ( +
{phasesMap[line.phase]}
+ )} + + ); +} diff --git a/src/components/action-log-v2/LineWon.tsx b/src/components/action-log-v2/LineWon.tsx new file mode 100644 index 00000000..2c2880ab --- /dev/null +++ b/src/components/action-log-v2/LineWon.tsx @@ -0,0 +1,18 @@ +import getPlayerBySeat from "./getPlayerBySeat"; +import { ActionLogLineProps } from "./types"; + +export default function LineWin(props: ActionLogLineProps) { + const { line, players } = props; + + if (line.type !== "WIN") return <>; + + const playerName = getPlayerBySeat(line.seat || 0, players); + + return ( + <> +
+ {playerName} won! +
+ + ); +} diff --git a/src/components/action-log-v2/LineZonePut.tsx b/src/components/action-log-v2/LineZonePut.tsx new file mode 100644 index 00000000..99527c66 --- /dev/null +++ b/src/components/action-log-v2/LineZonePut.tsx @@ -0,0 +1,28 @@ +import LogAbility from "./LogAbility"; +import LogCard from "./LogCard"; +import { ActionLogLineProps } from "./types"; + +export default function LineZonePut(props: ActionLogLineProps) { + const { line } = props; + + if (line.type !== "ZONE_PUT") return <>; + + return ( + <> +
+ {line.abilityId ? ( + <> + + 's  + + + ) : ( + + )} +  put  + +  in {line.zone} +
+ + ); +} diff --git a/src/components/action-log-v2/LineZoneReturn.tsx b/src/components/action-log-v2/LineZoneReturn.tsx new file mode 100644 index 00000000..b094165b --- /dev/null +++ b/src/components/action-log-v2/LineZoneReturn.tsx @@ -0,0 +1,28 @@ +import LogAbility from "./LogAbility"; +import LogCard from "./LogCard"; +import { ActionLogLineProps } from "./types"; + +export default function LineZoneReturn(props: ActionLogLineProps) { + const { line } = props; + + if (line.type !== "ZONE_RETURN") return <>; + + return ( + <> +
+ {line.abilityId ? ( + <> + + 's  + + + ) : ( + + )} +  returned  + +  to {line.zone} +
+ + ); +} diff --git a/src/components/action-log-v2/LogAbility.tsx b/src/components/action-log-v2/LogAbility.tsx new file mode 100644 index 00000000..fda21f90 --- /dev/null +++ b/src/components/action-log-v2/LogAbility.tsx @@ -0,0 +1,16 @@ +import { database } from "mtgatool-shared"; + +interface LogAbilityProps { + abId: number; +} + +export default function LogAbility(props: LogAbilityProps): JSX.Element { + const { abId } = props; + const desc = database.ability(abId); + + return ( + + ability + + ); +} diff --git a/src/components/action-log-v2/LogCard.tsx b/src/components/action-log-v2/LogCard.tsx new file mode 100644 index 00000000..ad11a261 --- /dev/null +++ b/src/components/action-log-v2/LogCard.tsx @@ -0,0 +1,21 @@ +import { database } from "mtgatool-shared"; + +import useHoverCard from "../../hooks/useHoverCard"; + +interface LogCardProps { + grpId: number; +} + +export default function LogCard(props: LogCardProps): JSX.Element { + const { grpId } = props; + const cardObj = database.card(grpId); + const cardName = cardObj?.Name; + + const [hoverIn, hoverOut] = useHoverCard(grpId); + + return ( + + {cardName} + + ); +} diff --git a/src/components/action-log-v2/getPlayerBySeat.ts b/src/components/action-log-v2/getPlayerBySeat.ts new file mode 100644 index 00000000..1cfc7e32 --- /dev/null +++ b/src/components/action-log-v2/getPlayerBySeat.ts @@ -0,0 +1,11 @@ +import getPlayerNameWithoutSuffix from "../../utils/getPlayerNameWithoutSuffix"; +import { ActionLogPlayer } from "./types"; + +export default function getPlayerBySeat( + seat: number, + players: ActionLogPlayer[] +): string { + const playerName = + players.filter((player) => player.seat === seat)[0]?.name || ""; + return getPlayerNameWithoutSuffix(playerName); +} diff --git a/src/components/action-log-v2/index.tsx b/src/components/action-log-v2/index.tsx new file mode 100644 index 00000000..63bc9274 --- /dev/null +++ b/src/components/action-log-v2/index.tsx @@ -0,0 +1,132 @@ +import LineAbility from "./LineAbility"; +import LineAttack from "./LineAttack"; +import LineCast from "./LineCast"; +import LineConceded from "./LineConceded"; +import LineCountered from "./LineCountered"; +import LineDamageDealt from "./LineDamageDealt"; +import LineDestroyed from "./LineDestroyed"; +import LineDiscard from "./LineDiscard"; +import LineDraw from "./LineDraw"; +import LineExile from "./LineExile"; +import LineModifiedLife from "./LineModifiedLife"; +import LinePlay from "./LinePlay"; +import LineScry from "./LineScry"; +import LineTarget from "./LineTarget"; +import LineTimedOut from "./LineTimedOut"; +import LineTurnInfo from "./LineTurnInfo"; +import LineWin from "./LineWon"; +import LineZonePut from "./LineZonePut"; +import LineZoneReturn from "./LineZoneReturn"; +import { ActionLogLineProps, ActionLogLineType, ActionLogV2 } from "./types"; + +const DefaultLineComponent = (_props: ActionLogLineProps): JSX.Element => { + return <>; +}; + +function getLineComponent(type: ActionLogLineType) { + let lineComponent = DefaultLineComponent; + + switch (type) { + case "WIN": + lineComponent = LineWin; + break; + case "CONCEDED": + lineComponent = LineConceded; + break; + case "TIMED_OUT": + lineComponent = LineTimedOut; + break; + case "DRAW": + lineComponent = LineDraw; + break; + case "SCRY": + lineComponent = LineScry; + break; + case "CAST": + lineComponent = LineCast; + break; + case "PLAY": + lineComponent = LinePlay; + break; + case "DISCARD": + lineComponent = LineDiscard; + break; + case "ZONE_PUT": + lineComponent = LineZonePut; + break; + case "ZONE_RETURN": + lineComponent = LineZoneReturn; + break; + case "COUNTERED": + lineComponent = LineCountered; + break; + case "EXILE": + lineComponent = LineExile; + break; + case "DESTROYED": + lineComponent = LineDestroyed; + break; + case "TARGET": + lineComponent = LineTarget; + break; + case "TURN_INFO": + lineComponent = LineTurnInfo; + break; + case "ABILITY": + lineComponent = LineAbility; + break; + case "MODIFIED_LIFE": + lineComponent = LineModifiedLife; + break; + case "DAMAGE_DEALT": + lineComponent = LineDamageDealt; + break; + case "ATTACK": + lineComponent = LineAttack; + break; + default: + break; + } + + return lineComponent; +} + +interface ActionLogProps { + actionLog: ActionLogV2; +} + +export default function ActionLog(props: ActionLogProps): JSX.Element { + const { actionLog } = props; + + // const logLength = actionLog.lines.length; + + return ( +
+ {actionLog.lines.map((line, index) => { + const LineComponent = getLineComponent(line.type); + + // const nextLine = actionLog.lines[i + 1]; + + // if ( + // line.type === "TURN_INFO" && + // line.subType !== "BEGIN" && + // logLength - 1 !== i && + // nextLine.type === "TURN_INFO" && + // nextLine.subType === line.subType + // ) { + // return <>; + // } + + return ( + + ); + })} +
+ ); +} diff --git a/src/components/action-log-v2/types.ts b/src/components/action-log-v2/types.ts new file mode 100644 index 00000000..33ae3c5e --- /dev/null +++ b/src/components/action-log-v2/types.ts @@ -0,0 +1,203 @@ +import { TurnInfo } from "mtgatool-shared/dist/types/greTypes"; + +export type ActionLogLineType = + | "START" + | "END" + | "CONCEDED" + | "TIMED_OUT" + | "WIN" + | "TURN_INFO" + | "DRAW" + | "CAST" + | "DISCARD" + | "PLAY" + | "ZONE_PUT" + | "ZONE_RETURN" + | "EXILE" + | "COUNTERED" + | "DESTROYED" + | "ABILITY" + | "ATTACK" + | "DAMAGE_DEALT" + | "MODIFIED_LIFE" + | "TARGET" + | "SCRY" + | "REVEAL"; + +export interface ActionLogLineBase { + timestamp: number; + seat: number; + type: ActionLogLineType; +} + +export interface ActionLogLineStart extends ActionLogLineBase { + type: "START"; +} + +export interface ActionLogLineEnd extends ActionLogLineBase { + type: "END"; +} + +export interface ActionLogLineConceded extends ActionLogLineBase { + type: "CONCEDED"; +} + +export interface ActionLogLineTimedOut extends ActionLogLineBase { + type: "TIMED_OUT"; +} + +export interface ActionLogLineWin extends ActionLogLineBase { + type: "WIN"; +} + +export interface ActionLogLineTurnInfo extends ActionLogLineBase, TurnInfo { + type: "TURN_INFO"; + subType: "BEGIN" | "STEP" | "PHASE" | "PRIORITY"; +} + +export interface ActionLogLineDraw extends ActionLogLineBase { + type: "DRAW"; + grpId: number | null; +} + +export interface ActionLogLineCast extends ActionLogLineBase { + type: "CAST"; + grpId: number; +} + +export interface ActionLogLineDiscard extends ActionLogLineBase { + type: "DISCARD"; + grpId: number; +} + +export interface ActionLogLinePlay extends ActionLogLineBase { + type: "PLAY"; + grpId: number; +} + +export interface ActionLogLineZonePut extends ActionLogLineBase { + type: "ZONE_PUT"; + sourceGrpId: number; + abilityId: number | undefined; + grpId: number; + zone: string; +} + +export interface ActionLogLineZoneReturn extends ActionLogLineBase { + type: "ZONE_RETURN"; + sourceGrpId: number; + abilityId: number | undefined; + grpId: number; + zone: string; +} + +export interface ActionLogLineExile extends ActionLogLineBase { + type: "EXILE"; + sourceGrpId: number; + abilityId: number | undefined; + grpId: number; +} + +export interface ActionLogLineCountered extends ActionLogLineBase { + type: "COUNTERED"; + sourceGrpId: number; + abilityId: number | undefined; + grpId: number; +} + +export interface ActionLogLineDestroyed extends ActionLogLineBase { + type: "DESTROYED"; + sourceGrpId: number; + abilityId: number | undefined; + grpId: number; +} + +export interface ActionLogLineAbility extends ActionLogLineBase { + type: "ABILITY"; + sourceGrpId: number; + abilityId: number; +} + +export interface ActionLogLineAttack extends ActionLogLineBase { + type: "ATTACK"; + grpIds: number[]; + targetType: string; + targetId: number; +} + +export interface ActionLogLineDamageDealt extends ActionLogLineBase { + type: "DAMAGE_DEALT"; + sourceGrpId: number; + amount: number; + targetType: string; + targetId: number; +} + +export interface ActionLogLineModifiedLife extends ActionLogLineBase { + type: "MODIFIED_LIFE"; + delta: number; + total: number; +} + +export interface ActionLogLineTarget extends ActionLogLineBase { + type: "TARGET"; + sourceGrpId: number; + abilityId: number | undefined; + targetType: string; + targetId: number; +} + +export interface ActionLogLineScry extends ActionLogLineBase { + type: "SCRY"; + amount: number; + top: number[]; + bottom: number[]; +} + +export interface ActionLogLineReveal extends ActionLogLineBase { + type: "REVEAL"; + grpId: number; + zone: string; +} + +export type ActionLogLine = + | ActionLogLineStart + | ActionLogLineEnd + | ActionLogLineConceded + | ActionLogLineTimedOut + | ActionLogLineWin + | ActionLogLineTurnInfo + | ActionLogLineDraw + | ActionLogLineCast + | ActionLogLineDiscard + | ActionLogLinePlay + | ActionLogLineZonePut + | ActionLogLineZoneReturn + | ActionLogLineExile + | ActionLogLineCountered + | ActionLogLineDestroyed + | ActionLogLineAbility + | ActionLogLineAttack + | ActionLogLineDamageDealt + | ActionLogLineModifiedLife + | ActionLogLineTarget + | ActionLogLineScry + | ActionLogLineReveal; + +export interface ActionLogPlayer { + name: string; + seat: number; + userId: string; +} + +export type ActionLogV2 = { + version: number; + players: ActionLogPlayer[]; + lines: ActionLogLine[]; +}; + +export interface ActionLogLineProps { + line: ActionLogLine; + players: ActionLogPlayer[]; + timeStart: number; +} diff --git a/src/components/views/history/MatchView.tsx b/src/components/views/history/MatchView.tsx index 1ed5931d..b797ba5e 100644 --- a/src/components/views/history/MatchView.tsx +++ b/src/components/views/history/MatchView.tsx @@ -27,6 +27,7 @@ import { getCardArtCrop, getCardImage } from "../../../utils/getCardArtCrop"; import getEventPrettyName from "../../../utils/getEventPrettyName"; import getPlayerNameWithoutSuffix from "../../../utils/getPlayerNameWithoutSuffix"; import isLimitedEventId from "../../../utils/isLimitedEventId"; +import ActionLogV2 from "../../action-log-v2"; import ActionLog from "../../ActionLog"; import CardList from "../../CardList"; import DeckColorsBar from "../../DeckColorsBar"; @@ -166,9 +167,9 @@ export default function MatchView(): JSX.Element { : undefined; const logExists = matchData ? !!matchData.internalMatch.actionLog : undefined; - const actionLogDataString = matchData + const actionLogData = matchData ? matchData.internalMatch.actionLog ?? "" - : undefined; + : ""; const goBack = (): void => { history.push("/history"); @@ -435,7 +436,7 @@ export default function MatchView(): JSX.Element {
{view == VIEW_LOG ? ( - + typeof actionLogData === "string" ? ( + + ) : ( + + ) ) : arrayGameStats[gameSeen] ? ( ) : ( diff --git a/src/info.json b/src/info.json index 7e62c393..451cf8c3 100644 --- a/src/info.json +++ b/src/info.json @@ -1 +1 @@ -{"version":"6.5.3","branch":"dev","timestamp":1704328553701} \ No newline at end of file +{"version":"6.5.3","branch":"action-logs-v2","timestamp":1704578558609} \ No newline at end of file diff --git a/src/overlay/index.tsx b/src/overlay/index.tsx index cd6b701b..c46c1f09 100644 --- a/src/overlay/index.tsx +++ b/src/overlay/index.tsx @@ -13,7 +13,8 @@ import { ChannelMessage } from "../broadcastChannel/channelMessages"; import postChannelMessage from "../broadcastChannel/postChannelMessage"; import { OverlaySettings, Settings } from "../common/defaultConfig"; import { overlayTitleToId } from "../common/maps"; -import ActionLog from "../components/ActionLog"; +import ActionLog from "../components/action-log-v2"; +import { ActionLogV2 } from "../components/action-log-v2/types"; import OverlayDeckList from "../components/OverlayDeckList"; import TopBar from "../components/TopBar"; import useDebounce from "../hooks/useDebounce"; @@ -36,7 +37,7 @@ export default function Overlay() { const [matchState, setMatchState] = useState(); const [draftState, setDraftState] = useState(); const [draftVotes, setDraftVotes] = useState>({}); - const [actionLog, setActionLog] = useState(""); + const [actionLog, setActionLog] = useState(null); const [odds, setOdds] = useState(); const heightDivAdjustRef = useRef(null); @@ -201,8 +202,8 @@ export default function Overlay() { }} /> )} - {settings && settings.mode === OVERLAY_LOG && ( - + {settings && settings.mode === OVERLAY_LOG && actionLog && ( + )} {settings && !!settings.clock && diff --git a/src/scss/actionLog.scss b/src/scss/actionLog.scss index b943554d..c0a555cc 100644 --- a/src/scss/actionLog.scss +++ b/src/scss/actionLog.scss @@ -73,3 +73,81 @@ cursor: pointer; margin-right: 3px; } + +.action-log-v2 { + font-size: 14px; + color: var(--color-text); + font-style: italic; + + .log-line { + margin-top: 8px; + display: flex; + font-family: var(--main-font-name-it); + flex-wrap: wrap; + + &.seat-1 { + &::before { + margin-right: 8px; + content: ""; + width: 5px; + background-color: var(--color-g); + border-radius: 0 3px 3px 0; + } + } + + &.seat-2 { + &::before { + margin-right: 8px; + content: ""; + width: 5px; + background-color: var(--color-r); + border-radius: 0 3px 3px 0; + } + } + } + + .result { + font-weight: 600; + margin-left: 0px; + color: var(--color-text-dark); + } + + .winner { + font-weight: 600; + margin-left: 0px; + margin-top: 16px; + color: var(--color-text-dark); + } + + .card, .ability { + color: var(--color-link); + text-decoration: underline; + } + + .turn-info { + margin-top: 12px; + display: flex; + justify-content: space-between; + font-family: var(--sub-font-name-it); + + .time { + color: var(--color-text-dark); + margin-left: 8px; + } + + .name { + color: var(--color-text-dark); + } + + &.begin { + margin-top: 16px; + padding-bottom: 4px; + border-bottom: var(--color-link) 1px solid; + } + + &.phase { + font-weight: 600; + color: var(--color-text-dark); + } + } +}