import Button from 'app/components/base/Button' ; import Callout from 'app/components/base/Callout' ; import Card from 'app/components/base/Card' ; import { Choice , RoleplayElement } from 'app/reducers/QuestTypes' ; import { CardThemeType , SettingsType } from 'app/reducers/StateTypes' ; import * as React from 'react' ; import { generateIconElements } from '../Render' ; import { ParserNode , TemplateContext } from '../TemplateTypes' ; import { REGEX } from 'shared/Regex' ; export interface StateProps { node : ParserNode ; questID : string ; onReturn ? : ( ) => any ; prevNode ? : ParserNode ; settings : SettingsType ; } export interface DispatchProps { onChoice : ( settings : SettingsType , node : ParserNode , index : number ) => void ; onRetry : ( ) => void ; } export interface Props extends StateProps , DispatchProps { } export interface RoleplayResult { choices : Choice [ ] ; content : RoleplayElement [ ] ; ctx : TemplateContext ; icon : string ; title : string | JSX . Element ; } export function loadRoleplayNode ( node , theme = 'light' ) { const choices = [ ] ; let choiceCount = - 1 ; const content = [ ] ; node . loopChildren ( ( tag , c ) => { let text = '' ; c = c . clone ( ) ; if ( tag === 'choice' ) { choiceCount ++ ; if ( ! c . attr ( 'text' ) ) { throw new Error ( '<choice> inside <roleplay> must have "text" attribute' ) ; } text = c . attr ( 'text' ) ; choices . push ( { jsx : generateIconElements ( text , theme ) , idx : choiceCount } ) ; return ; } if ( tag === 'event' ) { const on = ( ( c [ 0 ] . attribs || { } ) . on || '' ) . toLowerCase ( ) ; console . warn ( 'Warning: <roleplay> cannot contain event ' + on ) ; return ; } const element = { jsx : < span > < /span>, type : 'text' , } ; if ( tag === 'instruction' ) { element . type = 'instruction' ; text = c . html ( ) || '' ; let icon = 'adventurer' ; const matches = text . match ( REGEX . ICON ) ; if ( matches && text . trim ( ) . indexOf ( '<p>' + matches [ 0 ] ) === 0 ) { text = text . replace ( matches [ 0 ] , '' ) ; icon = matches [ 0 ] . replace ( /:/g , '' ) ; } element . jsx = < Callout icon = { icon } > { generateIconElements ( text , theme ) } < /Callout>; } else { text = c . toString ( ) ; element . jsx = generateIconElements ( text , theme ) ; } if ( text !== '' ) { content . push ( element ) ; } } ) ; if ( choices . length === 0 ) { const nextNode = node . getNext ( ) ; let buttonText = < span > Next < /span>; if ( nextNode && nextNode . getTag ( ) === 'trigger' ) { const triggerText = nextNode . elem . text ( ) . toLowerCase ( ) . split ( ' ' ) [ 0 ] . trim ( ) ; switch ( triggerText ) { case 'end' : buttonText = < span > The End < /span>; break ; case 'goto' : case 'win' : case 'lose' : break ; default : throw new Error ( 'Unknown trigger with text ' + triggerText ) ; } } choices . push ( { jsx : buttonText , idx : 0 } ) ; } return { choices , content , ctx : node . ctx , icon : node . elem . attr ( 'icon' ) , title : generateIconElements ( node . elem . attr ( 'title' ) , theme ) , } ; } const Roleplay = ( props , theme ) : JSX . Element => { const resolvedTheme = ( typeof ( theme ) !== 'string' ) ? 'light' : theme ; if ( props . node . getTag ( ) !== 'roleplay' ) { console . log ( 'Roleplay constructor called with non-roleplay node.' ) ; return < span > < /span>; } const rpResult = loadRoleplayNode ( props . node , resolvedTheme ) ; const renderedContent = rpResult . content . map ( ( element , idx ) : JSX . Element => { return < span key = { idx } > { element . jsx } < /span>; } ) ; const buttons = rpResult . choices . map ( ( choice ) : JSX . Element => { return ( < Button key = { choice . idx } id = { `${ props . questID }-${ props . node . elem . attr ( 'data-line' ) || '' }-${ choice . idx }` } onClick = { ( ) => props . onChoice ( props . settings , props . node , choice . idx ) } > { choice . jsx } < /Button>); } ) ; const prevNodeCombatAdventurers = props . prevNode && props . prevNode . getTag ( ) === 'combat' && props . prevNode . ctx . templates . combat && props . prevNode . ctx . templates . combat . numAliveAdventurers ; const nextNode = props . node . getNext ( ) ; if ( prevNodeCombatAdventurers === 0 && rpResult . choices . length === 1 && nextNode && nextNode . isEnd ( ) ) { buttons . unshift ( < Button key = { - 1 } onClick = { ( ) => props . onRetry ( ) } > Retry combat < /Button>); } return ( < Card title = { rpResult . title } icon = { rpResult . icon } inQuest = { true } theme = { resolvedTheme } onReturn = { props . onReturn } > { renderedContent } { buttons } < /Card>); } ; export default Roleplay ;