diff --git a/src/classes/alpha-strike-unit.ts b/src/classes/alpha-strike-unit.ts index 6538e402..bda7c786 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,42 @@ 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; + // 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; + } + } + } + } // 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 +1617,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.scss b/src/ui/components/svg/alpha-strike-unit-svg.scss new file mode 100644 index 00000000..9bf91277 --- /dev/null +++ b/src/ui/components/svg/alpha-strike-unit-svg.scss @@ -0,0 +1,37 @@ +.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); + } + &.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 bef960d0..16c4555f 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-svg.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,141 @@ 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(740,70,280,'high',false) + ) + options.push( + this._movementCounter(270,340,280,'extreme',false) + ) + options.push( + this._movementCounter(590,340,280,'grounded',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 +479,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 +489,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 +518,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 +589,9 @@ export default class AlphaStrikeUnitSVG extends React.Component ) : null} + {/* Heat Scale Box */} - {/* Heat Scale Box */} OV: {this.props.asUnit.overheat} HEAT SCALE @@ -864,6 +1021,12 @@ export default class AlphaStrikeUnitSVG extends React.Component + {this._movementOptions()}; + + ) : null} + {this.props.asUnit.isWrecked() ? ( <> WRECKED @@ -906,5 +1069,5 @@ interface IAlphaStrikeUnitSVGProps { interface IAlphaStrikeUnitSVGState { showTakeDamage: boolean; - + showMovementOptions: boolean; } \ No newline at end of file diff --git a/src/ui/pages/alpha-strike/roster/in-play.tsx b/src/ui/pages/alpha-strike/roster/in-play.tsx index e6eb2323..5043a598 100644 --- a/src/ui/pages/alpha-strike/roster/in-play.tsx +++ b/src/ui/pages/alpha-strike/roster/in-play.tsx @@ -112,6 +112,28 @@ 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 = ''; + } + + } + } + + this.props.appGlobals.saveCurrentASForce( this.props.appGlobals.currentASForce ); + + } + } + render = (): JSX.Element => { if(!this.props.appGlobals.currentASForce) { @@ -162,6 +184,8 @@ export default class AlphaStrikeRosterInPlay extends React.Component +
  • +