import Victor from 'victor' ; import entityData from './defaultentities' ; import Blueprint from './index' ; type PositionGrid = { [ location ] : Entity ; } ; type Side = 1 | 2 | 'in' | 'out' ; type Color = 'red' | 'green' ; type Priority = 'left' | 'right' ; type DirectionType = 'input' | 'output' ; interface Connection { entity : Entity ; color : Color ; side : Side ; id ? : string ; } interface CombinatorData { left ? : string ; right ? : string ; operator ? : string ; out ? : string ; controlEnable ? : boolean ; readContents ? : boolean ; readMode ? : string ; countFromInput ? : boolean ; } interface Constant { name : string ; count : number ; } interface AlertParameters { showAlert ? : boolean ; showOnMap ? : boolean ; icon ? : string | { type : string ; name : string ; } ; message ? : string ; } export default class Entity { id : number ; bp : Blueprint ; name : string ; position : Victor ; direction : number ; rawConnections : any ; connections : Connection [ ] ; rawNeighbours ? : number [ ] ; neighbours : Entity [ ] ; circuitParameters : any ; condition : CombinatorData ; constants ? : { [ position ] : Constant ; } ; constantEnabled : boolean ; parameters : any ; alertParameters : AlertParameters ; filters : { [ position ] : string ; } ; requestFilters : { [ position ] : Constant ; } ; directionType : DirectionType ; recipe ? : string ; bar : number ; modules : any ; splitterFilter ? : string ; inputPriority : Priority | undefined ; outputPriority : Priority | undefined ; size : Victor ; HAS_DIRECTION_TYPE : boolean ; CAN_HAVE_RECIPE : boolean ; CAN_HAVE_MODULES : number ; INVENTORY_SIZE : number ; constructor ( data , bp , center ) { if ( ! entityData [ bp . checkName ( data . name ) ] ) entityData [ bp . checkName ( data . name ) ] = { } ; let myData = entityData [ bp . checkName ( data . name ) ] ; this . id = - 1 ; this . bp = bp ; this . name = this . bp . checkName ( data . name ) ; this . position = Victor . fromObject ( data . position ) ; this . direction = 0 ; this . rawConnections = data . connections ; this . connections = [ ] ; this . rawNeighbours = data . neighbors ; this . neighbours = [ ] ; this . circuitParameters = data . circuit_parameters || null ; this . condition = this . parseCondition ( data ) ; this . constants = this . parseConstants ( data ) ; this . constantEnabled = data . control_behavior && data . control_behavior . is_on !== undefined ? data . control_behavior . is_on : true ; this . parameters = data . paramaters || ( myData . parameters ? { } : null ) ; this . alertParameters = data . alert_parameters || ( myData . alertParameters ? { } : null ) ; this . filters = { } ; this . requestFilters = { } ; this . directionType = data . type || 'input' ; this . recipe = data . recipe ? this . bp . checkName ( data . recipe ) : undefined ; this . bar = data . bar || - 1 ; this . modules = data . items ? Object . keys ( data . items ) . reduce ( ( obj , key ) => { obj [ this . bp . checkName ( key ) ] = data . items [ key ] ; return obj ; } , { } ) : { } ; this . splitterFilter = data . filter ? this . bp . checkName ( data . filter ) : undefined ; this . inputPriority = data . input_priority || undefined ; this . outputPriority = data . output_priority || undefined ; this . size = myData ? new Victor ( myData . width || 0 , myData . height || 0 ) : entityData [ this . name ] ? new Victor ( entityData [ this . name ] . width || 0 , entityData [ this . name ] . height || 0 ) : new Victor ( 1 , 1 ) ; this . HAS_DIRECTION_TYPE = myData . directionType || false ; this . CAN_HAVE_RECIPE = myData . recipe || false ; this . CAN_HAVE_MODULES = myData . modules || 0 ; this . INVENTORY_SIZE = myData . inventorySize || 0 ; this . setDirection ( data . direction || 0 ) ; this . parseFilters ( data . filters ) ; this . parseRequestFilters ( data . request_filters ) ; if ( center ) { this . position . add ( new Victor ( 0.5 , 0.5 ) ) . subtract ( this . size . clone ( ) . divide ( new Victor ( 2 , 2 ) ) ) ; } this . position = new Victor ( Math . round ( this . position . x * 100 ) / 100 , Math . round ( this . position . y * 100 ) / 100 ) ; } parseConnections ( entityList ) { const conns = this . rawConnections ; if ( conns ) { for ( let side in conns ) { if ( side != '1' && side != '2' ) return ; for ( let color in conns [ side ] ) { for ( let i = 0 ; i < conns [ side ] [ color ] . length ; i ++ ) { const id = conns [ side ] [ color ] [ i ] [ 'entity_id' ] ; const connection = { entity : entityList [ id - 1 ] , color : color == 'red' ? 'red' : 'green' , side : parseInt ( side ) == 1 ? 1 : 2 , id : conns [ side ] [ color ] [ i ] [ 'circuit_id' ] , } ; this . connections . push ( connection ) ; } } } } if ( this . rawNeighbours ) { this . neighbours = this . rawNeighbours . map ( id => entityList [ id - 1 ] ) ; } } parseFilters ( filters ) { if ( ! filters ) return [ ] ; for ( let i = 0 ; i < filters . length ; i ++ ) { const name = this . bp . checkName ( filters [ i ] . name ) ; const final_position = filters [ i ] . index - 1 ; const final_name = name ; this . setFilter ( final_position , final_name ) ; } } parseRequestFilters ( request_filters ) { if ( ! request_filters ) return [ ] ; for ( let i = 0 ; i < request_filters . length ; i ++ ) { request_filters [ i ] . name = this . bp . checkName ( request_filters [ i ] . name ) ; this . setRequestFilter ( request_filters [ i ] . index - 1 , request_filters [ i ] . name , request_filters [ i ] . count ) ; } } parseCondition ( data ) { const controlBehavior = data . control_behavior ; const condition = ( controlBehavior && ( controlBehavior . decider_conditions || controlBehavior . arithmetic_conditions || controlBehavior . circuit_condition ) ) || { } ; if ( ! controlBehavior ) return { } ; if ( condition . first_signal ) condition . first_signal . name = this . bp . checkName ( condition . first_signal . name ) ; if ( condition . second_signal ) condition . second_signal . name = this . bp . checkName ( condition . second_signal . name ) ; if ( condition . output_signal ) condition . output_signal . name = this . bp . checkName ( condition . output_signal . name ) ; const out = { left : condition . first_signal ? condition . first_signal . name : undefined , right : condition . second_signal ? condition . second_signal . name : condition . constant ? parseInt ( condition . constant ) : undefined , out : condition . output_signal ? condition . output_signal . name : undefined , operator : undefined , controlEnable : controlBehavior . circuit_enable_disable , readContents : controlBehavior . circuit_read_hand_contents , readMode : controlBehavior . circuit_contents_read_mode != undefined ? condition . circuit_contents_read_mode == 0 ? 'pulse' : 'hold' : undefined , countFromInput : undefined , } ; [ condition . first_signal , condition . second_signal , condition . output_signal , ] . forEach ( signal => { if ( signal && ! entityData [ signal . name ] ) entityData [ signal . name ] = { type : signal . type } ; } ) ; if ( this . name == 'decider_combinator' ) { out . countFromInput = condition . copy_count_from_input == 'true' ; } if ( condition . comparator ) out . operator = condition . comparator == ':' ? '=' : condition . comparator ; else out . operator = condition . operation ; return out ; } parseConstants ( data ) { if ( this . name != 'constant_combinator' ) return undefined ; else if ( ! data . control_behavior || ! data . control_behavior . filters ) return { } ; const constants = { } ; data . control_behavior . filters . forEach ( ( filter ) => { if ( ! entityData [ this . bp . checkName ( filter . signal . name ) ] ) { entityData [ this . bp . checkName ( filter . signal . name ) ] = { type : filter . signal . type } ; } constants [ parseInt ( filter . index ) - 1 ] = { name : this . bp . checkName ( filter . signal . name ) , count : filter . count || 0 , } ; } ) ; return constants ; } place ( positionGrid , entityList ) { this . setTileData ( positionGrid ) ; this . parseConnections ( entityList ) ; return this ; } remove ( ) { return this . bp . removeEntity ( this ) ; } removeCleanup ( positionGrid ) { this . removeTileData ( positionGrid ) ; return this ; } topLeft ( ) { return this . position . clone ( ) ; } topRight ( ) { return this . position . clone ( ) . add ( this . size . clone ( ) . multiply ( new Victor ( 1 , 0 ) ) ) ; } bottomRight ( ) { return this . position . clone ( ) . add ( this . size ) ; } bottomLeft ( ) { return this . position . clone ( ) . add ( this . size . clone ( ) . multiply ( new Victor ( 0 , 1 ) ) ) ; } center ( ) { return this . position . clone ( ) . add ( this . size . clone ( ) . divide ( new Victor ( 2 , 2 ) ) ) ; } setTileData ( positionGrid ) { this . tileDataAction ( positionGrid , ( x , y ) => ( positionGrid [ x + ',' + y ] = this ) ) ; return this ; } removeTileData ( positionGrid ) { this . tileDataAction ( positionGrid , ( x , y ) => delete positionGrid [ x + ',' + y ] ) ; return this ; } checkNoOverlap ( positionGrid ) { const ent = this . getOverlap ( positionGrid ) ; if ( ! ent ) return true ; if ( ( this . name == 'gate' && ent . name == 'straight_rail' ) || ( ent . name == 'gate' && this . name == 'straight_rail' ) ) return true ; return false ; } getOverlap ( positionGrid ) { let item = null ; this . tileDataAction ( positionGrid , ( x , y ) => { item = positionGrid [ x + ',' + y ] || item ; } ) ; return item ; } tileDataAction ( positionGrid , fn ) { if ( ! positionGrid ) return ; const topLeft = this . topLeft ( ) ; const bottomRight = this . bottomRight ( ) . subtract ( new Victor ( 0.9 , 0.9 ) ) ; for ( let x = Math . floor ( topLeft . x ) ; x < bottomRight . x ; x ++ ) { for ( let y = Math . floor ( topLeft . y ) ; y < bottomRight . y ; y ++ ) { fn ( x , y ) ; } } } connect ( ent , mySide = 1 , theirSide = 1 , color = 'red' ) { mySide = convertSide ( mySide , this ) ; theirSide = convertSide ( theirSide , ent ) ; const checkCombinator = ( name ) => { return name == 'decider_combinator' || name == 'arithmetic_combinator' ; } ; color = color == 'green' ? color : 'red' ; this . connections . push ( { entity : ent , color : color , side : mySide , id : checkCombinator ( ent . name ) ? theirSide . toString ( ) : undefined , } ) ; ent . connections . push ( { entity : this , color : color , side : theirSide , id : checkCombinator ( this . name ) ? mySide . toString ( ) : undefined , } ) ; return this ; } removeConnection ( ent , mySide = 1 , theirSide = 1 , color = 'red' ) { mySide = convertSide ( mySide , this ) ; theirSide = convertSide ( theirSide , ent ) ; color = color || 'red' ; for ( let i = 0 ; i < this . connections . length ; i ++ ) { if ( this . connections [ i ] . entity == ent && this . connections [ i ] . side == mySide && this . connections [ i ] . color == color ) { this . connections . splice ( i , 1 ) ; break ; } } for ( let i = 0 ; i < ent . connections . length ; i ++ ) { if ( ent . connections [ i ] . entity == this && ent . connections [ i ] . side == theirSide && ent . connections [ i ] . color == color ) { ent . connections . splice ( i , 1 ) ; break ; } } return this ; } removeConnectionsWithEntity ( ent , color ) { for ( let i = this . connections . length - 1 ; i >= 0 ; i -- ) { if ( this . connections [ i ] . entity == ent && ( ! color || this . connections [ i ] . color == color ) ) this . connections . splice ( i , 1 ) ; } for ( let i = ent . connections . length - 1 ; i >= 0 ; i -- ) { if ( ent . connections [ i ] . entity == this && ( ! color || ent . connections [ i ] . color == color ) ) ent . connections . splice ( i , 1 ) ; } return this ; } removeAllConnections ( ) { for ( let i = 0 ; i < this . connections . length ; i ++ ) { let ent = this . connections [ i ] . entity ; for ( let j = 0 ; j < ent . connections . length ; j ++ ) { if ( ent . connections [ j ] . entity == this ) { ent . connections . splice ( j , 1 ) ; break ; } } } this . connections = [ ] ; return this ; } setFilter ( pos , name ) { if ( pos < 0 ) throw new Error ( 'Filter index cannot be less than 0!' ) ; name = this . bp . checkName ( name ) ; if ( name == null ) delete this . filters [ pos ] ; else this . filters [ pos ] = name ; return this ; } setRequestFilter ( pos , name , count ) { if ( pos < 0 ) throw new Error ( 'Filter index cannot be less than 0!' ) ; name = this . bp . checkName ( name ) ; if ( name == null ) delete this . requestFilters [ pos ] ; else this . requestFilters [ pos ] = { name , count , } ; return this ; } removeAllFilters ( ) { this . filters = { } ; return this ; } removeAllRequestFilters ( ) { this . requestFilters = { } ; return this ; } setCondition ( opt ) { if ( opt . countFromInput != undefined && this . name != 'decider_combinator' ) throw new Error ( 'Cannot set countFromInput for ' + this . name ) ; else if ( opt . readMode && opt . readMode != 'pulse' && opt . readMode != 'hold' ) throw new Error ( 'readMode in a condition must be "pulse" or "hold"!' ) ; else if ( this . name == 'arithmetic_combinator' && ( opt . left == 'signal_everything' || opt . out == 'signal_everything' || opt . left == 'signal_anything' || opt . out == 'signal_anything' ) ) throw new Error ( 'Only comparitive conditions can contain signal_everything or signal_anything. Instead use signal_each' ) ; else if ( opt . out == 'signal_each' && opt . left != 'signal_each' ) throw new Error ( 'Left condition must be signal_each for output to be signal_each.' + ( this . name != 'arithmetic_combinator' ? ' Use signal_everything for the output instead' : '' ) ) ; if ( opt . left ) opt . left = this . bp . checkName ( opt . left ) ; if ( typeof opt . right == 'string' ) opt . right = this . bp . checkName ( opt . right ) ; if ( opt . out ) opt . out = this . bp . checkName ( opt . out ) ; if ( ! this . condition ) this . condition = { } ; this . condition = { left : this . condition . left || opt . left , right : this . condition . right || opt . right , operator : this . condition . operator || opt . operator , countFromInput : this . condition . countFromInput || opt . countFromInput , out : this . condition . out || opt . out , controlEnable : this . condition . controlEnable || opt . controlEnable , readContents : this . condition . readContents || opt . readContents , readMode : this . condition . readMode || opt . readMode , } ; return this ; } setDirection ( dir ) { this . size = new Victor ( dir % 4 == this . direction % 4 ? this . size . x : this . size . y , dir % 4 == this . direction % 4 ? this . size . y : this . size . x ) ; this . direction = dir ; return this ; } setDirectionType ( type ) { if ( ! this . HAS_DIRECTION_TYPE ) throw new Error ( 'This type of item does not have a directionType! Usually only underground belts have these.' ) ; this . directionType = type ; return this ; } setRecipe ( recipe ) { if ( ! this . CAN_HAVE_RECIPE ) throw new Error ( 'This entity cannot have a recipe.' ) ; this . recipe = this . bp . checkName ( recipe ) ; return this ; } setBar ( num ) { if ( ! this . INVENTORY_SIZE ) throw new Error ( 'Only entities with inventories can have bars!' ) ; else if ( typeof num == 'number' && num < 0 ) throw new Error ( 'You must provide a positive value to setBar()' ) ; this . bar = typeof num != 'number' || num >= this . INVENTORY_SIZE ? - 1 : num ; return this ; } setCircuitParameters ( obj ) { if ( ! this . circuitParameters ) this . circuitParameters = { } ; Object . keys ( obj ) . forEach ( key => ( this . circuitParameters [ key ] = obj [ key ] ) ) ; return this ; } setParameters ( obj ) { if ( ! this . parameters ) this . parameters = { } ; Object . keys ( obj ) . forEach ( key => ( this . parameters [ key ] = obj [ key ] ) ) ; return this ; } setAlertParameters ( opt ) { if ( ! this . alertParameters ) this . alertParameters = { } ; Object . keys ( opt ) . forEach ( ( key ) => ( this . alertParameters [ key ] = opt [ key ] ) ) ; return this ; } setConstant ( pos , name , count ) { if ( this . name != 'constant_combinator' ) throw new Error ( 'Can only set constants for constant combinators!' ) ; else if ( pos < 0 || pos >= 18 ) throw new Error ( pos + ' is an invalid position (must be between 0 and 17 inclusive)' ) ; if ( ! this . constants ) { this . constants = { } ; } if ( ! name ) delete this . constants [ pos ] ; else this . constants [ pos ] = { name : this . bp . checkName ( name ) , count : count == undefined ? 0 : count , } ; return this ; } setSplitterFilter ( name ) { this . splitterFilter = name ; return this ; } setInputPriority ( priority ? ) { this . inputPriority = priority ; return this ; } setOutputPriority ( priority ? ) { this . outputPriority = priority ; return this ; } getData ( ) { const useValueOrDefault = ( val , def ) => val != undefined ? val : def ; const getOptionData = ( append = { } ) => { if ( ! this . condition ) return append ; append . circuit_enable_disable = this . condition . controlEnable ; append . circuit_read_hand_contents = this . condition . readContents ; append . circuit_contents_read_mode = this . condition . readMode != undefined ? this . condition . readMode == 'pulse' ? 0 : 1 : undefined ; return append ; } ; const getCondition = ( ) => { const out = { } ; out . first_signal = this . condition . left ? { type : entityData [ this . condition . left ] . type , name : this . condition . left . replace ( /_/g , '-' ) , } : undefined ; out . second_signal = typeof this . condition . right == 'string' ? { type : entityData [ this . condition . right ] . type , name : this . condition . right . replace ( /_/g , '-' ) , } : undefined ; out . constant = typeof this . condition . right == 'number' ? this . condition . right : undefined ; out . operation = undefined ; out . comparator = undefined ; out . output_signal = this . condition . out ? { type : entityData [ this . condition . out ] . type , name : this . condition . out . replace ( /_/g , '-' ) , } : undefined ; if ( this . name != 'arithmetic_combinator' ) { out . comparator = this . condition . operator ; out . copy_count_from_input = this . condition . countFromInput != undefined ? ( ! ! this . condition . countFromInput ) . toString ( ) : undefined ; } else { out . operation = this . condition . operator ; } return out ; } ; const getAlertParameters = ( { showAlert = false , showOnMap = true , message = '' , icon = undefined , } ) => { if ( icon ) { if ( typeof icon === 'string' ) { icon = { type : entityData [ icon ] . type || '' , name : icon . replace ( /_/g , '-' ) , } ; } else { icon = { type : icon . type . replace ( /_/g , '-' ) , name : icon . name . replace ( /_/g , '-' ) , } ; } } return { show_alert : showAlert , show_on_map : showOnMap , alert_message : message , icon_signal_id : icon , } ; } ; return { name : this . bp . fixName ( this . name ) , position : this . center ( ) . subtract ( new Victor ( 0.5 , 0.5 ) ) as { x : number ; y : number ; } , direction : this . direction || 0 , entity_number : - 1 , type : this . directionType ? this . directionType : undefined , recipe : this . recipe ? this . bp . fixName ( this . recipe ) : undefined , bar : this . bar != - 1 ? this . bar : undefined , filter : this . splitterFilter ? this . bp . fixName ( this . splitterFilter ) : undefined , input_priority : this . inputPriority || undefined , output_priority : this . outputPriority || undefined , items : this . modules && Object . keys ( this . modules ) . length ? Object . keys ( this . modules ) . reduce ( ( obj , key ) => { obj [ this . bp . fixName ( key ) ] = this . modules [ key ] ; return obj ; } , { } ) : undefined , filters : makeEmptyArrayUndefined ( Object . keys ( this . filters ) . map ( ( filterPosition ) => { return { index : parseInt ( filterPosition ) + 1 , name : this . bp . fixName ( this . filters [ parseInt ( filterPosition ) ] ) , } ; } ) ) , request_filters : makeEmptyArrayUndefined ( Object . keys ( this . requestFilters ) . map ( ( index ) => { const rFilter = this . requestFilters [ parseInt ( index ) ] ; return { name : this . bp . fixName ( rFilter . name ) , count : rFilter . count , index : parseInt ( index ) + 1 , } ; } ) ) , connections : this . connections . length || this . condition || Object . keys ( this . condition ) . length || Object . keys ( this . circuitParameters ) . length ? this . connections . reduce ( ( obj , connection ) => { let side = connection . side ; let color = connection . color ; if ( ! obj [ side ] ) obj [ side ] = { } ; if ( ! obj [ side ] [ color ] ) obj [ side ] [ color ] = [ ] ; obj [ side ] [ color ] . push ( { entity_id : connection . entity . id , circuit_id : connection . id , } ) ; return obj ; } , { } ) : undefined , neighbours : this . neighbours . map ( ent => ent . id ) , parameters : this . parameters ? { playback_volume : useValueOrDefault ( this . parameters . volume , 1.0 ) , playback_globally : useValueOrDefault ( this . parameters . playGlobally , false ) , allow_polyphony : useValueOrDefault ( this . parameters . allowPolyphony , true ) , } : undefined , alert_parameters : this . alertParameters ? getAlertParameters ( this . alertParameters ) : undefined , control_behavior : this . constants || this . condition || this . name == 'decider_combinator' || this . name == 'arithmetic_combinator' ? getOptionData ( { filters : this . constants && Object . keys ( this . constants ) . length ? Object . keys ( this . constants ) . map ( ( key , i ) => { const data = this . constants [ key ] ; return { signal : { name : this . bp . fixName ( data . name ) , type : entityData [ data . name ] . type , } , count : data . count != undefined ? data . count : 0 , index : parseInt ( key ) + 1 , } ; } ) : undefined , decider_conditions : this . name == 'decider_combinator' ? getCondition ( ) : undefined , arithmetic_conditions : this . name == 'arithmetic_combinator' ? getCondition ( ) : undefined , circuit_condition : ! this . name . includes ( 'combinator' ) && this . condition . left ? getCondition ( ) : undefined , is_on : this . name == 'constant_combinator' && ! this . constantEnabled ? this . constantEnabled : undefined , circuit_parameters : this . circuitParameters ? { signal_value_is_pitch : useValueOrDefault ( this . circuitParameters . signalIsPitch , false ) , instrument_id : useValueOrDefault ( this . circuitParameters . instrument , 0 ) , note_id : useValueOrDefault ( this . circuitParameters . note , 0 ) , } : undefined , } ) : undefined , } ; } } function convertSide ( side , ent ) { if ( ! side ) return 1 ; if ( side == 1 || side == 2 ) return side ; else if ( side == 'in' || side == 'out' ) { if ( ent && ent . name != 'arithmetic_combinator' && ent . name != 'decider_combinator' ) return 1 ; else return side == 'in' ? 1 : 2 ; } else throw new Error ( 'Invalid side' ) ; } function makeEmptyArrayUndefined ( arr ) { return arr . length ? arr : undefined ; }