import { CreatureType , PieceModel , CombatPieceModel , getRelativeDirection , TileCoordinates , Directions , getDistance , clonePieceCombatState } from "@creature-chess/models" ; import { BoardSelectors , BoardSlice , BoardState } from "@shoki/board" ; import { isOvercomeBy , isGeneratedBy } from "./utils/get-type-attack-bonus" ; import { inAttackRange } from "./utils/inAttackRange" ; import { findTargetId } from "./utils/findTargetId" ; import { getNextPiecePosition } from "./pathfinding" ; const DYING_DURATION = 10 ; const ATTACK_TURN_DURATION = 2 ; const MOVE_TURN_DURATION = 2 ; const getCooldownForSpeed = ( speed ) => ( 180 - speed ) / 24 ; const STRONG_ATTACK_MODIFIER = 1.7 ; const WEAK_ATTACK_MODIFIER = 0.3 ; const getStats = ( piece ) => piece . definition . stages [ piece . stage ] ; export const simulateTurn = ( currentTurn , board , boardSlice ) => { const pieceEntries = Object . entries ( board . pieces ) ; pieceEntries . sort ( ( [ , aPiece ] , [ , bPiece ] ) => { const aStats = getStats ( aPiece ) ; const bStats = getStats ( bPiece ) ; return bStats . speed - aStats . speed ; } ) ; return pieceEntries . reduce ( ( b , [ pieceId ] ) => takePieceTurn ( currentTurn , pieceId , b , boardSlice ) , board ) ; } ; const takePieceTurn = ( currentTurn , pieceId , board , boardSlice ) : BoardState < PieceModel > => { const originalPiece = BoardSelectors . getPiece ( board , pieceId ) ; if ( ! originalPiece ) { return board ; } const attackerCombatState = clonePieceCombatState ( originalPiece . combat ) ; const attacker = { ... originalPiece , attacking : null , hit : null , combat : attackerCombatState } ; const attackerPosition = BoardSelectors . getPiecePosition ( board , pieceId ) ; if ( ! attackerPosition ) { return board ; } const attackerTargetId = attackerCombatState . targetId ; const attackerBoardState = attackerCombatState . board ; const attackerStats = getStats ( attacker ) ; if ( attackerBoardState . removeFromBoardAtTurn === currentTurn ) { return boardSlice . boardReducer ( board , boardSlice . commands . removeBoardPiecesCommand ( [ pieceId ] ) ) ; } if ( attacker . currentHealth === 0 ) { if ( attackerBoardState . removeFromBoardAtTurn ) { return board ; } attackerBoardState . removeFromBoardAtTurn = currentTurn + DYING_DURATION ; return boardSlice . boardReducer ( board , boardSlice . commands . updateBoardPiecesCommand ( [ attacker ] ) ) ; } const cooldown = getCooldownForSpeed ( attackerStats . speed ) ; if ( attackerBoardState . canMoveAtTurn === null ) { attackerBoardState . canMoveAtTurn = currentTurn + cooldown ; } if ( attackerBoardState . canAttackAtTurn === null ) { attackerBoardState . canAttackAtTurn = currentTurn + cooldown ; } if ( ! attackerTargetId ) { attacker . combat . targetId = findTargetId ( attacker , board ) ; return boardSlice . boardReducer ( board , boardSlice . commands . updateBoardPiecesCommand ( [ attacker ] ) ) ; } const target = BoardSelectors . getPiece ( board , attackerTargetId ) as CombatPieceModel ; if ( ! target || attackerBoardState . canAttackAtTurn > currentTurn ) { return boardSlice . boardReducer ( board , boardSlice . commands . updateBoardPiecesCommand ( [ attacker ] ) ) ; } if ( target . combat . board . canBeAttackedAtTurn > currentTurn ) { return boardSlice . boardReducer ( board , boardSlice . commands . updateBoardPiecesCommand ( [ attacker ] ) ) ; } const targetPosition = BoardSelectors . getPiecePosition ( board , attackerTargetId ) ; const targetAlive = target . currentHealth > 0 ; if ( ! targetAlive || ! targetPosition ) { attacker . combat . targetId = null ; return boardSlice . boardReducer ( board , boardSlice . commands . updateBoardPiecesCommand ( [ attacker ] ) ) ; } const inRange = inAttackRange ( attackerPosition , targetPosition , attackerStats . attackType ) ; if ( inRange ) { const damage = getAttackDamage ( attacker , target ) ; const newDefenderHealth = Math . max ( target . currentHealth - damage , 0 ) ; const attackerDirection = getRelativeDirection ( attackerPosition , targetPosition ) ; const attackerDistance = getDistance ( attackerPosition , targetPosition ) ; const attackerFacingAway = getNewAttackerFacingAway ( attacker . facingAway , attackerDirection ) ; const newAttacker = { ... attacker , combat : { ... attacker . combat , board : { ... attacker . combat . board , canAttackAtTurn : currentTurn + ATTACK_TURN_DURATION + getCooldownForSpeed ( attackerStats . speed ) } } , attacking : { attackType : attackerStats . attackType , distance : attackerDistance , direction : attackerDirection , damage } , facingAway : attackerFacingAway } ; const defender = { ... target , currentHealth : newDefenderHealth , hit : { direction : getRelativeDirection ( targetPosition , attackerPosition ) , damage } } ; return boardSlice . boardReducer ( board , boardSlice . commands . updateBoardPiecesCommand ( [ newAttacker , defender ] ) ) ; } else { if ( attackerBoardState . canMoveAtTurn > currentTurn ) { return boardSlice . boardReducer ( board , boardSlice . commands . updateBoardPiecesCommand ( [ attacker ] ) ) ; } const nextPosition = getNextPiecePosition ( attackerPosition , attackerStats , targetPosition , board ) ; if ( ! nextPosition ) { return boardSlice . boardReducer ( board , boardSlice . commands . updateBoardPiecesCommand ( [ attacker ] ) ) ; } const attackerDirection = getRelativeDirection ( attackerPosition , targetPosition ) ; attacker . facingAway = getNewAttackerFacingAway ( attacker . facingAway , attackerDirection ) ; attackerBoardState . canMoveAtTurn = currentTurn + MOVE_TURN_DURATION + getCooldownForSpeed ( attackerStats . speed ) ; attackerBoardState . canBeAttackedAtTurn = currentTurn + MOVE_TURN_DURATION + 2 ; attackerBoardState . canAttackAtTurn = currentTurn + MOVE_TURN_DURATION + 2 ; return boardSlice . boardReducer ( boardSlice . boardReducer ( board , boardSlice . commands . moveBoardPieceCommand ( { pieceId , from : attackerPosition , to : nextPosition } ) ) , boardSlice . commands . updateBoardPiecesCommand ( [ attacker ] ) ) ; } } ; const getNewAttackerFacingAway = ( oldFacingAway , direction ) => { if ( direction === Directions . LEFT || direction === Directions . RIGHT ) { return oldFacingAway ; } if ( direction === Directions . UP ) { return true ; } return false ; } ; const getAttackBonus = ( attacker , defender ) => { const isDefenderOvercome = isOvercomeBy ( defender , attacker ) ; if ( isDefenderOvercome ) { return STRONG_ATTACK_MODIFIER ; } const isDefenderGenerated = isGeneratedBy ( defender , attacker ) ; const isAttackerOvercome = isOvercomeBy ( attacker , defender ) ; if ( isDefenderGenerated || isAttackerOvercome ) { return WEAK_ATTACK_MODIFIER ; } return 1 ; } ; const getAttackDamage = ( attacker , defender ) : number => { const attackerStats = getStats ( attacker ) ; const defenderStats = getStats ( defender ) ; const attackBonus = getAttackBonus ( attacker . definition . type , defender . definition . type ) ; return ( attackerStats . attack / defenderStats . defense ) * attackBonus * 8 ; } ;