import GameState from "../../../../GameState" ; import House from "../../../game-data-structure/House" ; import Player from "../../../Player" ; import { ClientMessage } from "../../../../../messages/ClientMessage" ; import Game from "../../../game-data-structure/Game" ; import unitTypes from "../../../game-data-structure/unitTypes" ; import Region from "../../../game-data-structure/Region" ; import UnitType from "../../../game-data-structure/UnitType" ; import Unit from "../../../game-data-structure/Unit" ; import unitMusteringRules , { getCostOfMusteringRule } from "../../../game-data-structure/unitMusteringRules" ; import BetterMap from "../../../../../utils/BetterMap" ; import * as _ from "lodash" ; import EntireGame from "../../../../EntireGame" ; import ResolveConsolidatePowerGameState from "../../../action-game-state/resolve-consolidate-power-game-state/ResolveConsolidatePowerGameState" ; import { ServerMessage } from "../../../../../messages/ServerMessage" ; import RegionKind from "../../../game-data-structure/RegionKind" ; import IngameGameState from "../../../IngameGameState" ; import User from "../../../../../server/User" ; import { observable } from "mobx" ; type MusteringRule = ( Mustering & { cost : number ; } ) ; export type Mustering = { from : Unit | null ; region : Region ; to : UnitType ; affectedUnit ? : Unit ; } ; export enum PlayerMusteringType { MUSTERING_WESTEROS_CARD = 0 , STARRED_CONSOLIDATE_POWER = 1 , THE_HORDE_DESCENDS_WILDLING_CARD = 2 , DEFENSE_MUSTER_ORDER = 3 } interface ParentGameState extends GameState < any , any > { game : Game ; entireGame : EntireGame ; ingame : IngameGameState ; onPlayerMusteringEnd ( house , regions ) : void ; } /**
 * This GameState handles mustering for the Westeros card "Mustering", a mustering from a consolidated power
 * token, a mustering from defense/muster token and a mustering from the Wildling card "The Horde Descends".
 */ export default class PlayerMusteringGameState extends GameState < ParentGameState > { house : House ; @ observable type : PlayerMusteringType ; get game ( ) : Game { return this . parentGameState . game ; } get entireGame ( ) : EntireGame { return this . parentGameState . entireGame ; } get resolveConsolidatePowerGameState ( ) : ResolveConsolidatePowerGameState { if ( ! this . isStarredConsolidatePowerMusteringType && this . type != PlayerMusteringType . DEFENSE_MUSTER_ORDER ) { throw new Error ( "ResolveConsolidatePowerGameState requested but type != STARRED_CONSOLIDATE_POWER and type != DEFENSE_MUSTER_ORDER" ) ; } return this . parentGameState as ResolveConsolidatePowerGameState ; } get ingame ( ) : IngameGameState { return this . parentGameState . ingame ; } get isStarredConsolidatePowerMusteringType ( ) : boolean { return this . type == PlayerMusteringType . STARRED_CONSOLIDATE_POWER ; } get regions ( ) : Region [ ] { switch ( this . type ) { case PlayerMusteringType . MUSTERING_WESTEROS_CARD : case PlayerMusteringType . THE_HORDE_DESCENDS_WILDLING_CARD : const regionsWithCastles = this . ingame . world . getControlledRegions ( this . house ) . filter ( r => r . castleLevel > 0 ) ; return this . ingame . isVassalHouse ( this . house ) ? regionsWithCastles . filter ( r => r . superControlPowerToken == this . house ) : regionsWithCastles ; case PlayerMusteringType . DEFENSE_MUSTER_ORDER : return this . resolveConsolidatePowerGameState . actionGameState . getRegionsWithDefenseMusterOrderOfHouse ( this . house ) . map ( ( [ r , _ot ] ) => r ) ; case PlayerMusteringType . STARRED_CONSOLIDATE_POWER : return this . resolveConsolidatePowerGameState . actionGameState . getRegionsWithConsolidatePowerOrderOfHouse ( this . house ) . filter ( ( [ _r , ot ] ) => ot . starred ) . map ( ( [ r , _ot ] ) => r ) ; default : throw new Error ( ) ; } } firstStart ( house , type ) { this . house = house ; this . type = type ; if ( ! this . anyUsablePointsLeft ( new BetterMap ( ) ) ) { if ( type == PlayerMusteringType . STARRED_CONSOLIDATE_POWER ) { this . resolveConsolidatePowerGameState . resolveConsolidatePowerOrderForPt ( this . regions [ 0 ] , this . house , true ) ; } else { this . parentGameState . ingame . log ( { type : "player-mustered" , house : house . id , musterings : [ ] } , true ) ; } this . proceedToParentGameState ( ) ; } } onServerMessage ( _ ) { } onPlayerMessage ( player , message ) { if ( message . type == "muster" ) { if ( this . parentGameState . ingame . getControllerOfHouse ( this . house ) != player ) { return ; } const musterings = new BetterMap ( message . units . map ( ( [ regionId , recruitements ] ) => { return [ this . game . world . regions . get ( regionId ) , recruitements . map ( ( { from , to , region } ) => { const musteringRegion = this . game . world . regions . get ( region ) ; return { from : from ? musteringRegion . units . get ( from ) : null , region : musteringRegion , to : unitTypes . get ( to ) } ; } ) ] as [ Region , Mustering [ ] ] ; } ) ) ; if ( this . type != PlayerMusteringType . MUSTERING_WESTEROS_CARD ) { if ( musterings . size > 1 ) { return ; } } if ( this . type == PlayerMusteringType . MUSTERING_WESTEROS_CARD && this . ingame . isVassalHouse ( this . house ) ) { if ( musterings . keys . some ( r => r . superControlPowerToken != this . house ) ) { return ; } } if ( ! this . isMusteringValid ( musterings ) ) { return ; } for ( const [ originatingRegion , recruitements ] of musterings . entries ) { if ( this . ingame . getControllerOfHouse ( originatingRegion . getController ( ) as House ) != player ) { return ; } const transformedUnits = recruitements . filter ( ( { from } ) => from ) . map ( ( { from } ) => from ) ; if ( _ . uniq ( transformedUnits ) . length != transformedUnits . length ) { return ; } } musterings . entries . forEach ( ( [ _ , recruitments ] ) => { recruitments . forEach ( ( { from , to , region } ) => { if ( from ) { this . ingame . transformUnits ( region , [ from ] , to ) ; } else { const unit = this . game . createUnit ( region , to , this . house ) ; region . units . set ( unit . id , unit ) ; this . entireGame . broadcastToClients ( { type : "add-units" , units : [ [ region . id , [ unit . serializeToClient ( ) ] ] ] } ) ; } } ) ; } ) ; if ( this . isStarredConsolidatePowerMusteringType && musterings . size == 0 && this . regions . length > 0 ) { this . resolveConsolidatePowerGameState . resolveConsolidatePowerOrderForPt ( this . regions [ 0 ] , this . house ) ; } else { this . parentGameState . ingame . log ( { type : "player-mustered" , house : this . house . id , musterings : musterings . map ( ( region , musterings ) => [ region . id , musterings . map ( m => ( { region : m . region . id , from : m . from ? m . from . type . id : null , to : m . to . id } ) ) ] ) } ) ; } this . proceedToParentGameState ( ) ; } } proceedToParentGameState ( ) { if ( this . parentGameState instanceof ResolveConsolidatePowerGameState ) { this . parentGameState . onPlayerMusteringEnd ( this . house , this . regions ) ; return ; } this . parentGameState . onPlayerMusteringEnd ( this . house , [ ] ) ; } muster ( musterings ) { this . entireGame . sendMessageToServer ( { type : "muster" , units : musterings . entries . map ( ( [ region , recruitements ] ) => [ region . id , recruitements . map ( ( { from , to , region } ) => ( { from : from ? from . id : null , to : to . id , region : region . id } ) ) ] ) } ) ; } getWaitedUsers ( ) { return [ this . parentGameState . ingame . getControllerOfHouse ( this . house ) . user ] ; } isMusteringValid ( musterings ) { return musterings . entries . every ( ( [ originatingRegion , recruitements ] , mi ) => { return recruitements . every ( ( recruitement , ri ) => { const previousMusterings = new BetterMap ( musterings . entries . slice ( 0 , mi ) . concat ( [ [ originatingRegion , recruitements . slice ( 0 , ri ) ] ] ) ) ; const validMusteringRules = this . getValidMusteringRules ( originatingRegion , previousMusterings ) ; return validMusteringRules . find ( r => r . region == recruitement . region && r . rules . find ( r => r . from == recruitement . from && r . to == recruitement . to ) != null ) != null ; } ) ; } ) ; } getValidMusteringRules ( originatingRegion , musterings ) { const possibleRecruitementRegions = [ originatingRegion ] . concat ( this . game . world . getNeighbouringRegions ( originatingRegion ) . filter ( r => r . type . kind == RegionKind . SEA ) ) . filter ( r => r . getController ( ) == null || r . getController ( ) == this . house ) . filter ( r => r . garrison > 0 ? r . getController ( ) == this . house : true ) ; const pointsLeft = this . getPointsLeft ( originatingRegion , musterings ) ; return possibleRecruitementRegions . map ( recruitmentRegion => { return { region : recruitmentRegion , rules : unitMusteringRules . filter ( rule => pointsLeft >= rule . cost ) . filter ( rule => rule . to . walksOn == recruitmentRegion . type . kind ) . filter ( rule => rule . from ? this . getPotentialUnitForUpgrading ( musterings , recruitmentRegion , rule . from ) != null : true ) . filter ( rule => { const flattenedMusterings = _ . flatMap ( musterings . values ) ; const alreadyMusteredUnits = flattenedMusterings . filter ( m => m . to == rule . to ) . length ; const freedUnits = flattenedMusterings . filter ( m => m . from && m . from . type == rule . to ) . length ; return this . game . getAvailableUnitsOfType ( this . house , rule . to ) + freedUnits - alreadyMusteredUnits ; } ) . filter ( ( { from , to } ) => { if ( from != null ) { return true ; } const addedUnits = this . getAddedUnitsOfMustering ( musterings ) ; addedUnits . set ( recruitmentRegion , addedUnits . tryGet ( recruitmentRegion , [ ] as UnitType [ ] ) . concat ( [ to ] ) ) ; return ! this . game . hasTooMuchArmies ( this . house , addedUnits ) ; } ) . map ( rule => { return { region : recruitmentRegion , from : rule . from ? this . getPotentialUnitForUpgrading ( musterings , recruitmentRegion , rule . from ) as Unit : null , to : rule . to , cost : rule . cost } ; } ) } ; } ) ; } getValidMusteringRulesForRegion ( region , musterings ) { return _ . flatMap ( this . getValidMusteringRules ( region , musterings ) . map ( ( { rules } ) => rules ) ) ; } getAddedUnitsOfMustering ( musterings ) { const addedUnits = new BetterMap < Region , UnitType [ ] > ( ) ; musterings . entries . forEach ( ( [ _ , recruitements ] ) => recruitements . forEach ( ( { region , from , to } ) => { if ( from != null ) { return ; } addedUnits . set ( region , addedUnits . tryGet ( region , [ ] as UnitType [ ] ) . concat ( [ to ] ) ) ; } ) ) ; return addedUnits ; } getPointsLeft ( region , musterings ) { return region . castleLevel - this . getUsedPointsForRegion ( region , musterings ) ; } anyUsablePointsLeft ( musterings ) { switch ( this . type ) { case PlayerMusteringType . MUSTERING_WESTEROS_CARD : case PlayerMusteringType . STARRED_CONSOLIDATE_POWER : case PlayerMusteringType . DEFENSE_MUSTER_ORDER : return _ . flatMap ( this . regions . map ( c => this . getValidMusteringRulesForRegion ( c , musterings ) ) ) . length > 0 ; case PlayerMusteringType . THE_HORDE_DESCENDS_WILDLING_CARD : if ( musterings . size == 0 ) { return _ . flatMap ( this . regions . map ( c => this . getValidMusteringRulesForRegion ( c , musterings ) ) ) . length > 0 ; } else { return this . getValidMusteringRulesForRegion ( musterings . keys [ 0 ] , musterings ) . length > 0 ; } } } getUsedPointsForRegion ( region , musterings ) { return this . getUsedPoints ( musterings . tryGet ( region , [ ] ) ) ; } getUsedPoints ( musterings ) { return _ . sum ( musterings . map ( m => getCostOfMusteringRule ( m . from ? m . from . type : null , m . to ) ) ) ; } getPotentialUnitForUpgrading ( musterings , region , fromType ) { const units = this . getUnitsLeftToUpgrade ( musterings , region ) ; const potentialUnit = units . find ( u => u . type == fromType ) ; return potentialUnit ? potentialUnit : null ; } getUnitsLeftToUpgrade ( musterings , region ) { const recruitments = musterings . tryGet ( region , [ ] ) ; return _ . difference ( region . units . values , recruitments . map ( ( { from } ) => from ) . filter ( f => f ) as Unit [ ] ) ; } serializeToClient ( _ , __ ) { return { type : "player-mustering" , house : this . house . id , musteringType : this . type } ; } static deserializeFromServer ( parent , data ) { const playerMustering = new PlayerMusteringGameState ( parent ) ; playerMustering . house = parent . game . houses . get ( data . house ) ; playerMustering . type = data . musteringType ; return playerMustering ; } } export interface SerializedPlayerMusteringGameState { type : "player-mustering" ; house : string ; musteringType : PlayerMusteringType ; }