import * as _ from "lodash" ; import keyPathify from "./keyPathify" ; import * as Actions from "./gameActions" ; import { Node } from './node' ; import { State } from './state' ; import { Story } from './story' ; import * as Parser from 'storyboard-lang' ; import { Dispatch } from './dispatch' ; export { default as keyPathify } from './keyPathify' ; export type OutputCallback = ( ( content : string , passageId : Parser . PassageId , track : String ) => void ) ; export type ObserverCallback = ( ( content : any ) => void ) ; export class Game { observers : { [ keypath ] : ObserverCallback [ ] ; } ; valid : boolean = true ; constructor ( storyData , initialState ? ) { let story ; if ( typeof storyData === "string" ) { story = Parser . parseString ( storyData ) ! ; if ( ! story ) { this . valid = false ; console . log ( "Throwing invalid story" ) ; throw ( "Invalid story" ) ; } } else { story = storyData ; } let dispatch = _ . bind ( this . receiveDispatch , this ) as Dispatch ; this . story = new Story ( story , dispatch ) ; this . state = new State ( initialState ) ; this . started = false ; this . outputs = { } ; this . observers = { } ; var that = this ; this . addOutput ( "wait" , ( timeout , passageId ) => { setTimeout ( ( ) => { this . completePassage ( passageId ) ; } , parseInt ( timeout ) ) ; } ) ; this . addOutput ( '' , ( _ , passageId ) => { this . completePassage ( passageId ) ; } ) ; } readonly story : Story ; state : State ; outputs : { [ name ] : OutputCallback [ ] ; } ; started : boolean ; stateListener ? : ( ( state : State ) => void ) ; jsonStateListener ? : ( ( serializedState : string ) => void ) ; receiveDispatch ( action , data ) { if ( action === Actions . OUTPUT ) { let outputs = this . outputs [ data . type ] ; if ( ! outputs ) return ; for ( let outputCallback of outputs ) { const string = data . content . replace ( /\{(.+?)\}/g , ( match , keyPath ) => _ ( this . state ) . get ( keyPath ) ) ; outputCallback ( string , data . passageId , data . track ) ; } } else if ( action === Actions . MAKE_GRAPH_CHOICE ) { if ( ! this . state . graph . currentNodeId ) return ; this . state . graph . previousNodeId = this . state . graph . currentNodeId ; this . state . graph . nodeHistory . unshift ( this . state . graph . currentNodeId ) ; this . state . graph . previousChoice = ( < any > Object ) . assign ( { } , data ) ; this . state . graph . choiceHistory . unshift ( ( < any > Object ) . assign ( { } , data ) ) ; this . story . bag . checkNodes ( this . state ) ; this . receiveDispatch ( Actions . CHANGE_GRAPH_NODE , data . nodeId ) ; } else if ( action === Actions . CHANGE_GRAPH_NODE ) { this . state . graph . currentNodeId = data ; this . state . graph . nodeComplete = false ; this . state . graph . currentPassageIndex = - 1 ; if ( this . story . graph ) { this . story . graph . startNextPassage ( this . state ) ; } this . story . bag . checkNodes ( this . state ) ; } else if ( action === Actions . CHANGE_GRAPH_PASSAGE ) { this . state . graph . currentPassageIndex = data ; if ( this . story . graph ) { this . story . graph . playCurrentPassage ( this . state ) ; } } else if ( action === Actions . COMPLETE_GRAPH_NODE ) { this . state . graph . nodeComplete = true ; if ( this . story . graph ) { this . story . graph . checkChoiceTransitions ( this . state ) ; } this . story . bag . checkNodes ( this . state ) ; } else if ( action === Actions . TRIGGERED_BAG_NODES ) { _ . forEach ( data , ( node , track ) => { const nodeId = node . nodeId ; this . state . bag . activePassageIndexes [ nodeId ] = 0 ; this . state . bag . activeTracks [ node . track ] = nodeId ; this . story . bag . playCurrentPassage ( nodeId , this . state ) ; } ) ; } else if ( action === Actions . CHANGE_BAG_PASSAGE ) { let [ nodeId , passageIndex ] = data ; this . state . bag . activePassageIndexes [ nodeId ] = passageIndex ; this . story . bag . playCurrentPassage ( nodeId , this . state ) ; } else if ( action === Actions . COMPLETE_BAG_NODE ) { const nodeId = data ; delete this . state . bag . activePassageIndexes [ nodeId ] ; const node = this . story . bag . nodes [ nodeId ] ; delete this . state . bag . activeTracks [ node . track ! ] ; if ( ! this . state . bag . nodeHistory [ nodeId ] ) { this . state . bag . nodeHistory [ nodeId ] = 0 ; } this . state . bag . nodeHistory [ nodeId ] ++ ; this . story . bag . checkNodes ( this . state ) ; } else if ( action === Actions . RECEIVE_INPUT ) { let newState = _ . assign ( { } , this . state ) ; _ . forIn ( data , ( value , key ) => { _ . set ( newState , key , keyPathify ( value , this . state , true ) ) ; } ) ; this . state = { ... this . state , ... newState } ; _ . forIn ( data , ( value , key ) => { if ( this . observers [ key ] ) { _ . forEach ( this . observers [ key ] , ( fn ) => fn ( value ) ) ; } } ) ; if ( this . started ) { if ( this . story . graph ) { this . story . graph . checkChoiceTransitions ( this . state ) ; } this . story . bag . checkNodes ( this . state ) ; } } else if ( action === Actions . SET_VARIABLES ) { let newState = _ . assign ( { } , this . state ) ; _ . forIn ( data , ( value , key ) => { _ . set ( newState , key , keyPathify ( value , this . state , true ) ) ; } ) ; _ . assign ( this . state , newState ) ; _ . forIn ( data , ( value , key ) => { if ( this . observers [ key ] ) { _ . forEach ( this . observers [ key ] , ( fn ) => fn ( value ) ) ; } } ) ; if ( this . started ) { if ( this . story . graph ) { this . story . graph . checkChoiceTransitions ( this . state ) ; } this . story . bag . checkNodes ( this . state ) ; } } else if ( action === Actions . COMPLETE_PASSAGE ) { this . completePassage ( data ) ; } this . emitState ( ) ; } start ( ) { this . started = true ; if ( this . story . graph && this . story . graph . start ) { this . receiveDispatch ( Actions . CHANGE_GRAPH_NODE , this . story . graph . start ) ; } else { this . story . bag . checkNodes ( this . state ) ; } } addOutput ( type , callback ) { if ( ! this . outputs [ type ] ) { this . outputs [ type ] = [ ] ; } this . outputs [ type ] . push ( callback ) ; } addObserver ( type , callback ) { if ( ! this . observers [ type ] ) { this . observers [ type ] = [ ] ; } this . observers [ type ] . push ( callback ) ; } removeObserver ( type , callback ) { this . observers [ type ] = _ . without ( this . observers [ type ] , callback ) ; } receiveInput ( type , value ) { let obj = { } ; obj [ type ] = value ; this . receiveDispatch ( Actions . RECEIVE_INPUT , obj ) ; } receiveMomentaryInput ( type , value ? ) { let trueObj = { } ; trueObj [ type ] = value || true ; this . receiveDispatch ( Actions . RECEIVE_INPUT , trueObj ) ; let falseObj = { } ; falseObj [ type ] = undefined ; this . receiveDispatch ( Actions . RECEIVE_INPUT , falseObj ) ; } completePassage ( passageId ) { if ( this . story . graph ) { this . story . graph . completePassage ( passageId , this . state ) ; } this . story . bag . completePassage ( passageId , this . state ) ; } emitState ( ) { if ( this . stateListener ) { this . stateListener ( { ... this . state } ) ; } if ( this . jsonStateListener ) { const json = JSON . stringify ( this . state , null , 2 ) ; this . jsonStateListener ( json ) ; } } }