From 104f37c4f01367b9cc54fdbc3ce50b6b8e5c27ea Mon Sep 17 00:00:00 2001 From: James Mingardi-Elliott Date: Tue, 24 Dec 2024 11:47:48 -0500 Subject: [PATCH 1/4] Added movement tokens for Alpha Strike in play mode This adds a small hex token next to pilot skill with a . Clicking on it will show the appropriate movement tokens for the unit. Selecting the movement type will now show the letter and coloring of the movement token in the small hex. TTM is updated to reflect movement. It should respect JMPS# and JMPW#. Movement affects to firing should be respected as well. Standstill reduces by 1, jumping increases by 2, jumping jack SPA is considered as well as BA having no jump penalty. --- .github/workflows/webpack.yml | 28 +++ package.json | 2 +- src/classes/alpha-strike-unit.ts | 62 ++++++- .../components/svg/alpha-strike-unit-svg.tsx | 164 +++++++++++++++++- src/ui/components/svg/alpha-strike-unit.scss | 35 ++++ src/ui/pages/alpha-strike/roster/in-play.tsx | 26 +++ 6 files changed, 306 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/webpack.yml create mode 100644 src/ui/components/svg/alpha-strike-unit.scss diff --git a/.github/workflows/webpack.yml b/.github/workflows/webpack.yml new file mode 100644 index 00000000..b67bac23 --- /dev/null +++ b/.github/workflows/webpack.yml @@ -0,0 +1,28 @@ +name: NodeJS with Webpack + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x, 20.x, 22.x] + + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Build + run: | + npm install + npx webpack diff --git a/package.json b/package.json index 8b9da001..aaabd6d6 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "@types/react-router-dom": "^5.3.3", "bootstrap": "^5.3.3", "fast-xml-parser": "^4.3.5", - "gh-pages": "^6.1.1", "react": "^18.2.0", "react-bootstrap": "^2.10.1", "react-dom": "^18.2.0", @@ -54,6 +53,7 @@ ] }, "devDependencies": { + "gh-pages": "^6.2.0", "sass": "^1.75.0" } } diff --git a/src/classes/alpha-strike-unit.ts b/src/classes/alpha-strike-unit.ts index 6538e402..3376461f 100644 --- a/src/classes/alpha-strike-unit.ts +++ b/src/classes/alpha-strike-unit.ts @@ -124,6 +124,7 @@ export interface IAlphaStrikeUnitExport { imageURL: string; move: IMoveNumber[]; + movementType: string; jumpMove: number; structure: number; armor: number; @@ -177,6 +178,7 @@ export class AlphaStrikeUnit { public currentMoveSprint: string = ""; public currentMoveHexesSprint: string = ""; public currentTMM: string = ""; + public movementType: string = ""; public armor: number = 0; public structure: number = 1; @@ -799,6 +801,7 @@ export class AlphaStrikeUnit { this.vehicleMotive910 = []; this.vehicleMotive11 = []; this.vehicleMotive12 = false; + this.movementType = ""; this.calcCurrentValues(); } @@ -1059,9 +1062,21 @@ export class AlphaStrikeUnit { extremeMinimal: this.damage.extremeMinimal ? true : false, }; + // Consider Speed-Demon from pilot skills + let speedDemon = false; + for ( let ability = 0; ability < this._pilot.alphaStrikeAbilities.length; ability++) { + if (this._pilot.alphaStrikeAbilities[ability] === 46) { + speedDemon = true; + } + } for( let moveC = 0; moveC < this.move.length; moveC++ ) { this.move[moveC].currentMove = this.move[moveC].move; + + if (this.move[moveC].type === '' && speedDemon) { + this.move[moveC].currentMove = this.move[moveC].move + 2; + } + } // Calculate Critical Movement @@ -1224,7 +1239,11 @@ export class AlphaStrikeUnit { } - this.currentMoveSprint = "" + (+this.move[0].currentMove * 1.5 ) + "\""; + // Consider Speed Demon + let sprintBase = !speedDemon? this.move[0].currentMove : this.move[0].currentMove - 2; + let sprintBonus = speedDemon? 4 : 0; + + this.currentMoveSprint = "" + (sprintBase * 1.5 + sprintBonus) + "\""; this.currentMoveHexesSprint = "" + ( Math.ceil(( +this.move[0].currentMove / 2) * 1.5) )+ "⬣"; this.currentMove += this.move[moveC].currentMove.toString() + "\"" + this.move[moveC].type; @@ -1299,6 +1318,7 @@ export class AlphaStrikeUnit { // shut down units have a tmm of -4 (ASC pg. 53) if( this.currentHeat === 4 ){ tmpTMM = -4; + this.move[moveC].currentMove = 0; this.immobile = true; } else { // -1 TMM at OV2 or OV3 (ASC pg. 52) @@ -1317,8 +1337,9 @@ export class AlphaStrikeUnit { // } } - if( this.move[moveC].currentMove > 0 ) - this.immobile = false; + if( this.move[moveC].currentMove > 0) { + this.immobile = false; + } if( tmpTMM.toString() + "/" !== this.currentTMM ) this.currentTMM += tmpTMM.toString() + this.move[moveC].type.toLowerCase(); @@ -1331,15 +1352,39 @@ export class AlphaStrikeUnit { this.currentMoveHexes += "/"; } + if(this.movementType.toLowerCase() === "standstill" && !this.immobile) { + this.currentTMM = "0"; + } + + if(this.movementType.toLowerCase() === "jump" && !this.immobile) { + tmpTMM++; + this.currentTMM = tmpTMM.toString() + "j"; + } + + } - if( this.currentTMM.endsWith("/")) + if( this.currentTMM.endsWith("/")) { this.currentTMM = this.currentTMM.substring( 0, this.currentTMM.length - 1); + } + + // Update To-Hit with movement + let movementToHit = 0; + if (this.movementType === "standstill") { + movementToHit = -1; + } else if (this.movementType === "jump" && this.type && this.type.toLowerCase() !== "ba") { + movementToHit = 2; + for ( let ability = 0; ability < this._pilot.alphaStrikeAbilities.length; ability++) { + if (this._pilot.alphaStrikeAbilities[ability] === 26) { + movementToHit = 1; + } + } + } // Calculate To-Hits with Criticals - this.currentToHitShort = this.currentSkill + this.currentHeat + currentFCHits * 2; // + currentEngineHits; - this.currentToHitMedium = this.currentSkill + 2 + this.currentHeat + currentFCHits * 2; // + currentEngineHits; - this.currentToHitLong = this.currentSkill + 4 + this.currentHeat + currentFCHits * 2; // + currentEngineHits; - this.currentToHitExtreme = this.currentSkill + 6 + this.currentHeat + currentFCHits * 2; // + currentEngineHits; + this.currentToHitShort = this.currentSkill + this.currentHeat + currentFCHits * 2 + movementToHit; // + currentEngineHits; + this.currentToHitMedium = this.currentSkill + 2 + this.currentHeat + currentFCHits * 2 + movementToHit; // + currentEngineHits; + this.currentToHitLong = this.currentSkill + 4 + this.currentHeat + currentFCHits * 2 + movementToHit; // + currentEngineHits; + this.currentToHitExtreme = this.currentSkill + 6 + this.currentHeat + currentFCHits * 2 + movementToHit; // + currentEngineHits; this.currentHeat = this.currentHeat / 1; @@ -1569,6 +1614,7 @@ export class AlphaStrikeUnit { basePoints: this.basePoints, currentSkill: this.currentSkill, uuid: this.uuid, + movementType: this.movementType, }; return rv; diff --git a/src/ui/components/svg/alpha-strike-unit-svg.tsx b/src/ui/components/svg/alpha-strike-unit-svg.tsx index bef960d0..3842af7d 100644 --- a/src/ui/components/svg/alpha-strike-unit-svg.tsx +++ b/src/ui/components/svg/alpha-strike-unit-svg.tsx @@ -4,6 +4,7 @@ import { IASPilotAbility } from '../../../data/alpha-strike-pilot-abilities'; import { IASSpecialAbility } from '../../../data/alpha-strike-special-abilities'; import { IAppGlobals } from '../../app-router'; import BattleTechLogo from '../battletech-logo'; +import './alpha-strike-unit.scss'; export default class AlphaStrikeUnitSVG extends React.Component { height: string = "100%"; @@ -19,6 +20,7 @@ export default class AlphaStrikeUnitSVG extends React.Component { + this.setState({ + showMovementOptions: !this.state.showMovementOptions, + }) + } + private _takeDamage = ( damageTaken: number ): void => { if( this.props.inPlay && this.props.asUnit ) { this.props.asUnit.takeDamage( damageTaken ); @@ -232,6 +240,138 @@ export default class AlphaStrikeUnitSVG extends React.Component { + + // Plot out the hexagon's points relative to the starting position and size. + let cx2 = cx1 + cSize*.5; + let cy2 = cy1 + 0; + let cx3 = cx1 + cSize*.75; + let cy3 = cy1 + cSize*.4; + let cx4 = cx1 + cSize*.5; + let cy4 = cy1 + cSize*.8; + let cx5 = cx1 + 0; + let cy5 = cy1 + cSize*.8; + let cx6 = cx1 - cSize*.25; + let cy6 = cy1 + cSize*.4; + + // Concatenate into a string for the SVG + let points = cx1 + "," + cy1 + " " + cx2 + "," + cy2 + " " + cx3 + "," + cy3 + " " + cx4 + "," + cy4 + " " + cx5 + "," + cy5 + " " + cx6 + "," + cy6; + + // Calculate the size and placement for text + let text = "?"; + let textX = cx1 + cSize*.25; + let textY = cy1 + cSize*.5 + cSize*.1; + let textSize = cSize * .6; + let classes = "cursor-pointer move-token"; + + + // Separate concerns, is this a toggle? is this set already? + if (toggle && this.props && this.props.asUnit && this.props.asUnit.movementType) { + text = this.props.asUnit.movementType.charAt(0).toUpperCase(); + key = this.props.asUnit.movementType.toLowerCase(); + } else if (!toggle) { + text = key.charAt(0).toUpperCase() + key.slice(1);; + textY = textY - 34; + } + + classes = classes + " " + key; + classes = (toggle) ? classes : classes + " big"; + + let fragment = (toggle) ? + + this._toggleMovementOptions()} points={points} /> + this._toggleMovementOptions()} x={textX} y={textY} textAnchor="middle" width="150" fontFamily="sans-serif" fontSize={textSize}>{text} + + : + + this._setMovement(key)} points={points} /> + this._setMovement(key)} x={textX} y={textY} textAnchor="middle" width="150" fontFamily="sans-serif" fontSize={54}>{text} + + ; + + if (this.props.asUnit?.immobile) { + fragment = + + I + + } + + + return fragment; + } + + private _movementOptions = ( + + ): JSX.Element[] => { + let options: JSX.Element[] = [] + + let moveOptions = this.props.inPlay && this.props.asUnit ? this.props.asUnit.move : []; + if (this.props.asUnit?.isAerospace === false) { + + options.push( + this._movementCounter(120,70,280,'standstill',false) + ) + if (moveOptions[0].currentMove > 0) { + options.push( + this._movementCounter(430,70,280,'ground',false) + ) + options.push( + this._movementCounter(750,70,280,'sprint',false) + ) + } + + for( let currentMove = 0; moveOptions.length > currentMove; currentMove++ ) { + + // Add a jump option where applicable + if (moveOptions[currentMove].type === 'j') { + options.push( + this._movementCounter(430,340,280,'jump',false) + ) + } + } + } else { + // Add the available heights for flight. + options.push( + this._movementCounter(120,70,280,'low',false) + ) + options.push( + this._movementCounter(430,70,280,'middle',false) + ) + options.push( + this._movementCounter(750,70,280,'high',false) + ) + options.push( + this._movementCounter(430,340,280,'extreme',false) + ) + } + + return options; + } + + private _setMovement = (type: string): void => { + if( this.props.inPlay && this.props.asUnit ) { + this.props.asUnit.movementType = type; + this.props.asUnit.calcCurrentValues(); + this.props.appGlobals.saveCurrentASForce( this.props.appGlobals.currentASForce ); + } + this.setState({ + showMovementOptions: false, + }) + } + private _splitAbilities = ( val: string ): string[][] => { val = val.trim(); let words = val.split(","); @@ -336,6 +476,8 @@ export default class AlphaStrikeUnitSVG extends React.Component ) : null} + + // Point value box PV: {this.props.asUnit.currentPoints} {this.props.asUnit.currentPoints !== this.props.asUnit.basePoints ? ( @@ -344,6 +486,10 @@ export default class AlphaStrikeUnitSVG extends React.ComponentSPA Total: {this.props.asUnit.getTotalPilotAbilityPoints()} ) : null} + + + + // Info panel with Type, Movement, Role, Skill TP: {this.props.asUnit.type} @@ -369,9 +515,17 @@ export default class AlphaStrikeUnitSVG extends React.Component ) : null} + // Movement Token + <> + {this.props.inPlay ? this._movementCounter() : null} + + + ROLE: {this.props.asUnit.role.toUpperCase()} SKILL: {this.props.asUnit.currentSkill} + + // Attack Panel DAMAGE @@ -432,9 +586,9 @@ export default class AlphaStrikeUnitSVG extends React.Component ) : null} + // Heat Scale Box - {/* Heat Scale Box */} OV: {this.props.asUnit.overheat} HEAT SCALE @@ -864,6 +1018,12 @@ export default class AlphaStrikeUnitSVG extends React.Component + {this._movementOptions()}; + + ) : null} + {this.props.asUnit.isWrecked() ? ( <> WRECKED @@ -906,5 +1066,5 @@ interface IAlphaStrikeUnitSVGProps { interface IAlphaStrikeUnitSVGState { showTakeDamage: boolean; - + showMovementOptions: boolean; } \ No newline at end of file diff --git a/src/ui/components/svg/alpha-strike-unit.scss b/src/ui/components/svg/alpha-strike-unit.scss new file mode 100644 index 00000000..5e8de7b1 --- /dev/null +++ b/src/ui/components/svg/alpha-strike-unit.scss @@ -0,0 +1,35 @@ +.move-token { + fill: rgb(0,0,0); + &.standstill, + &.low, + &.jump, + &.extreme { + fill: rgb(255,255,255); + } + &:not(text) { + fill: rgba(255,255,255,.2); + stroke: rgb(0,0,0); + stroke-width: 3; + filter: drop-shadow(rgba(0,0,0,.5) 0 .15rem .3rem); + &.ground, + &.middle { + fill: rgb(255,255,255); + } + &.standstill, + &.low { + fill: rgb(0,0,0); + } + &.sprint, + &.high { + fill: rgb(204,187,0); + } + &.jump, + &.extreme { + fill: rgb(200,0,0); + } + &.big { + filter: drop-shadow(black 0 .5rem 1rem); + } + } + +} diff --git a/src/ui/pages/alpha-strike/roster/in-play.tsx b/src/ui/pages/alpha-strike/roster/in-play.tsx index e6eb2323..417ef2fb 100644 --- a/src/ui/pages/alpha-strike/roster/in-play.tsx +++ b/src/ui/pages/alpha-strike/roster/in-play.tsx @@ -112,6 +112,30 @@ export default class AlphaStrikeRosterInPlay extends React.Component + ): void => { + if( e && e.preventDefault ) e.preventDefault(); + + if (this.props.appGlobals.currentASForce) { + for (let group of this.props.appGlobals.currentASForce.groups) { + for( let unit of group.members ) { + + // Reset movement + if( unit && unit.movementType ) { + unit.movementType = ''; + } + + // TODO: Figure out how to apply temp damage + + } + } + + this.props.appGlobals.saveCurrentASForce( this.props.appGlobals.currentASForce ); + + } + } + render = (): JSX.Element => { if(!this.props.appGlobals.currentASForce) { @@ -162,6 +186,8 @@ export default class AlphaStrikeRosterInPlay extends React.Component +
  • +
  • Date: Thu, 16 Jan 2025 09:40:32 -0500 Subject: [PATCH 2/4] Fix to-hit for BA and Infantry BA and infantry are unaffected by attacker movement modifiers. Updated the calculations to skip modifying the to-hit for these unit types. --- src/classes/alpha-strike-unit.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/classes/alpha-strike-unit.ts b/src/classes/alpha-strike-unit.ts index 3376461f..bda7c786 100644 --- a/src/classes/alpha-strike-unit.ts +++ b/src/classes/alpha-strike-unit.ts @@ -1369,13 +1369,16 @@ export class AlphaStrikeUnit { // Update To-Hit with movement let movementToHit = 0; - if (this.movementType === "standstill") { - movementToHit = -1; - } else if (this.movementType === "jump" && this.type && this.type.toLowerCase() !== "ba") { - movementToHit = 2; - for ( let ability = 0; ability < this._pilot.alphaStrikeAbilities.length; ability++) { - if (this._pilot.alphaStrikeAbilities[ability] === 26) { - movementToHit = 1; + // Battle Armor and Infantry are not affected by attacker movement modifiers. AS:CE pg 42. + if (this.type && this.type.toLowerCase() !== "ba" && this.type.toLowerCase() !== "ci") { + if (this.movementType === "standstill") { + movementToHit = -1; + } else if (this.movementType === "jump") { + movementToHit = 2; + for ( let ability = 0; ability < this._pilot.alphaStrikeAbilities.length; ability++) { + if (this._pilot.alphaStrikeAbilities[ability] === 26) { + movementToHit = 1; + } } } } From f8baeac3cdcb3b6e7bf69a3e09d97a1236f7a663 Mon Sep 17 00:00:00 2001 From: James Mingardi-Elliott Date: Thu, 16 Jan 2025 13:54:37 -0500 Subject: [PATCH 3/4] Removing some cruft Additional cleanup --- .github/workflows/webpack.yml | 28 -------------------- src/ui/pages/alpha-strike/roster/in-play.tsx | 2 +- 2 files changed, 1 insertion(+), 29 deletions(-) delete mode 100644 .github/workflows/webpack.yml diff --git a/.github/workflows/webpack.yml b/.github/workflows/webpack.yml deleted file mode 100644 index b67bac23..00000000 --- a/.github/workflows/webpack.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: NodeJS with Webpack - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [18.x, 20.x, 22.x] - - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - - - name: Build - run: | - npm install - npx webpack diff --git a/src/ui/pages/alpha-strike/roster/in-play.tsx b/src/ui/pages/alpha-strike/roster/in-play.tsx index 417ef2fb..b51056c2 100644 --- a/src/ui/pages/alpha-strike/roster/in-play.tsx +++ b/src/ui/pages/alpha-strike/roster/in-play.tsx @@ -186,7 +186,7 @@ export default class AlphaStrikeRosterInPlay extends React.Component
  • -
  • +
  • Date: Tue, 4 Feb 2025 16:29:40 -0500 Subject: [PATCH 4/4] Updates and clean up Removed some extraneous changes I let slip into this PR. Added a "grounded" option for Aerospace to indicate that they are moving along the ground on the map as per https://discord.com/channels/1307019240867364937/1335474984063078551/1335474984063078551 --- package.json | 2 +- ...e-unit.scss => alpha-strike-unit-svg.scss} | 4 +++- .../components/svg/alpha-strike-unit-svg.tsx | 19 +++++++++++-------- src/ui/pages/alpha-strike/roster/in-play.tsx | 2 -- 4 files changed, 15 insertions(+), 12 deletions(-) rename src/ui/components/svg/{alpha-strike-unit.scss => alpha-strike-unit-svg.scss} (91%) diff --git a/package.json b/package.json index aaabd6d6..8b9da001 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@types/react-router-dom": "^5.3.3", "bootstrap": "^5.3.3", "fast-xml-parser": "^4.3.5", + "gh-pages": "^6.1.1", "react": "^18.2.0", "react-bootstrap": "^2.10.1", "react-dom": "^18.2.0", @@ -53,7 +54,6 @@ ] }, "devDependencies": { - "gh-pages": "^6.2.0", "sass": "^1.75.0" } } diff --git a/src/ui/components/svg/alpha-strike-unit.scss b/src/ui/components/svg/alpha-strike-unit-svg.scss similarity index 91% rename from src/ui/components/svg/alpha-strike-unit.scss rename to src/ui/components/svg/alpha-strike-unit-svg.scss index 5e8de7b1..9bf91277 100644 --- a/src/ui/components/svg/alpha-strike-unit.scss +++ b/src/ui/components/svg/alpha-strike-unit-svg.scss @@ -27,9 +27,11 @@ &.extreme { fill: rgb(200,0,0); } + &.grounded { + fill: rgb(210,210,210); + } &.big { filter: drop-shadow(black 0 .5rem 1rem); } } - } diff --git a/src/ui/components/svg/alpha-strike-unit-svg.tsx b/src/ui/components/svg/alpha-strike-unit-svg.tsx index 3842af7d..16c4555f 100644 --- a/src/ui/components/svg/alpha-strike-unit-svg.tsx +++ b/src/ui/components/svg/alpha-strike-unit-svg.tsx @@ -4,7 +4,7 @@ import { IASPilotAbility } from '../../../data/alpha-strike-pilot-abilities'; import { IASSpecialAbility } from '../../../data/alpha-strike-special-abilities'; import { IAppGlobals } from '../../app-router'; import BattleTechLogo from '../battletech-logo'; -import './alpha-strike-unit.scss'; +import './alpha-strike-unit-svg.scss'; export default class AlphaStrikeUnitSVG extends React.Component { height: string = "100%"; @@ -351,10 +351,13 @@ export default class AlphaStrikeUnitSVG extends React.Component PV: {this.props.asUnit.currentPoints} {this.props.asUnit.currentPoints !== this.props.asUnit.basePoints ? ( @@ -489,7 +492,7 @@ export default class AlphaStrikeUnitSVG extends React.Component TP: {this.props.asUnit.type} @@ -515,7 +518,7 @@ export default class AlphaStrikeUnitSVG extends React.Component ) : null} - // Movement Token + {/* Movement Token */} <> {this.props.inPlay ? this._movementCounter() : null} @@ -525,7 +528,7 @@ export default class AlphaStrikeUnitSVG extends React.ComponentROLE: {this.props.asUnit.role.toUpperCase()} SKILL: {this.props.asUnit.currentSkill} - // Attack Panel + {/* Attack Panel */} DAMAGE @@ -586,7 +589,7 @@ export default class AlphaStrikeUnitSVG extends React.Component ) : null} - // Heat Scale Box + {/* Heat Scale Box */} diff --git a/src/ui/pages/alpha-strike/roster/in-play.tsx b/src/ui/pages/alpha-strike/roster/in-play.tsx index b51056c2..5043a598 100644 --- a/src/ui/pages/alpha-strike/roster/in-play.tsx +++ b/src/ui/pages/alpha-strike/roster/in-play.tsx @@ -126,8 +126,6 @@ export default class AlphaStrikeRosterInPlay extends React.Component