using System.Text; namespace Ink.Parsed { public class Choice : Parsed.Object, IWeavePoint, INamedContent { public ContentList startContent { get; protected set; } public ContentList choiceOnlyContent { get; protected set; } public ContentList innerContent { get; protected set; } public string name { get { return identifier?.name; } } public Identifier identifier { get; set; } public Expression condition { get { return _condition; } set { _condition = value; if( _condition ) AddContent (_condition); } } public bool onceOnly { get; set; } public bool isInvisibleDefault { get; set; } public int indentationDepth { get; set; }// = 1; public bool hasWeaveStyleInlineBrackets { get; set; } // Required for IWeavePoint interface // Choice's target container. Used by weave to append any extra // nested weave content into. public Runtime.Container runtimeContainer { get { return _innerContentContainer; } } public Runtime.Container innerContentContainer { get { return _innerContentContainer; } } public override Runtime.Container containerForCounting { get { return _innerContentContainer; } } // Override runtimePath to point to the Choice's target content (after it's chosen), // as opposed to the default implementation which would point to the choice itself // (or it's outer container), which is what runtimeObject is. public override Runtime.Path runtimePath { get { return _innerContentContainer.path; } } public Choice (ContentList startContent, ContentList choiceOnlyContent, ContentList innerContent) { this.startContent = startContent; this.choiceOnlyContent = choiceOnlyContent; this.innerContent = innerContent; this.indentationDepth = 1; if (startContent) AddContent (this.startContent); if (choiceOnlyContent) AddContent (this.choiceOnlyContent); if( innerContent ) AddContent (this.innerContent); this.onceOnly = true; // default } public override Runtime.Object GenerateRuntimeObject () { _outerContainer = new Runtime.Container (); // Content names for different types of choice: // * start content [choice only content] inner content // * start content -> divert // * start content // * [choice only content] // Hmm, this structure has become slightly insane! // // [ // EvalStart // assign $r = $r1 -- return target = return label 1 // BeginString // -> s // [(r1)] -- return label 1 (after start content) // EndString // BeginString // ... choice only content // EndEval // Condition expression // choice: -> "c-0" // (s) = [ // start content // -> r -- goto return label 1 or 2 // ] // ] // // in parent's container: (the inner content for the choice) // // (c-0) = [ // EvalStart // assign $r = $r2 -- return target = return label 2 // EndEval // -> s // [(r2)] -- return label 1 (after start content) // inner content // ] // _runtimeChoice = new Runtime.ChoicePoint (onceOnly); _runtimeChoice.isInvisibleDefault = this.isInvisibleDefault; if (startContent || choiceOnlyContent || condition) { _outerContainer.AddContent (Runtime.ControlCommand.EvalStart ()); } // Start content is put into a named container that's referenced both // when displaying the choice initially, and when generating the text // when the choice is chosen. if (startContent) { // Generate start content and return // - We can't use a function since it uses a call stack element, which would // put temporary values out of scope. Instead we manually divert around. // - $r is a variable divert target contains the return point _returnToR1 = new Runtime.DivertTargetValue (); _outerContainer.AddContent (_returnToR1); var varAssign = new Runtime.VariableAssignment ("$r", true); _outerContainer.AddContent (varAssign); // Mark the start of the choice text generation, so that the runtime // knows where to rewind to to extract the content from the output stream. _outerContainer.AddContent (Runtime.ControlCommand.BeginString ()); _divertToStartContentOuter = new Runtime.Divert (); _outerContainer.AddContent (_divertToStartContentOuter); // Start content itself in a named container _startContentRuntimeContainer = startContent.GenerateRuntimeObject () as Runtime.Container; _startContentRuntimeContainer.name = "s"; // Effectively, the "return" statement - return to the point specified by $r var varDivert = new Runtime.Divert (); varDivert.variableDivertName = "$r"; _startContentRuntimeContainer.AddContent (varDivert); // Add the container _outerContainer.AddToNamedContentOnly (_startContentRuntimeContainer); // This is the label to return to _r1Label = new Runtime.Container (); _r1Label.name = "$r1"; _outerContainer.AddContent (_r1Label); _outerContainer.AddContent (Runtime.ControlCommand.EndString ()); _runtimeChoice.hasStartContent = true; } // Choice only content - mark the start, then generate it directly into the outer container if (choiceOnlyContent) { _outerContainer.AddContent (Runtime.ControlCommand.BeginString ()); var choiceOnlyRuntimeContent = choiceOnlyContent.GenerateRuntimeObject () as Runtime.Container; _outerContainer.AddContentsOfContainer (choiceOnlyRuntimeContent); _outerContainer.AddContent (Runtime.ControlCommand.EndString ()); _runtimeChoice.hasChoiceOnlyContent = true; } // Generate any condition for this choice if (condition) { condition.GenerateIntoContainer (_outerContainer); _runtimeChoice.hasCondition = true; } if (startContent || choiceOnlyContent || condition) { _outerContainer.AddContent (Runtime.ControlCommand.EvalEnd ()); } // Add choice itself _outerContainer.AddContent (_runtimeChoice); // Container that choice points to for when it's chosen _innerContentContainer = new Runtime.Container (); // Repeat start content by diverting to its container if (startContent) { // Set the return point when jumping back into the start content // - In this case, it's the $r2 point, within the choice content "c". _returnToR2 = new Runtime.DivertTargetValue (); _innerContentContainer.AddContent (Runtime.ControlCommand.EvalStart ()); _innerContentContainer.AddContent (_returnToR2); _innerContentContainer.AddContent (Runtime.ControlCommand.EvalEnd ()); var varAssign = new Runtime.VariableAssignment ("$r", true); _innerContentContainer.AddContent (varAssign); // Main divert into start content _divertToStartContentInner = new Runtime.Divert (); _innerContentContainer.AddContent (_divertToStartContentInner); // Define label to return to _r2Label = new Runtime.Container (); _r2Label.name = "$r2"; _innerContentContainer.AddContent (_r2Label); } // Choice's own inner content if (innerContent) { var innerChoiceOnlyContent = innerContent.GenerateRuntimeObject () as Runtime.Container; _innerContentContainer.AddContentsOfContainer (innerChoiceOnlyContent); } if (this.story.countAllVisits) { _innerContentContainer.visitsShouldBeCounted = true; } _innerContentContainer.countingAtStartOnly = true; return _outerContainer; } public override void ResolveReferences(Story context) { // Weave style choice - target own content container if (_innerContentContainer) { _runtimeChoice.pathOnChoice = _innerContentContainer.path; if (onceOnly) _innerContentContainer.visitsShouldBeCounted = true; } if (_returnToR1) _returnToR1.targetPath = _r1Label.path; if (_returnToR2) _returnToR2.targetPath = _r2Label.path; if( _divertToStartContentOuter ) _divertToStartContentOuter.targetPath = _startContentRuntimeContainer.path; if( _divertToStartContentInner ) _divertToStartContentInner.targetPath = _startContentRuntimeContainer.path; base.ResolveReferences (context); if( identifier != null && identifier.name.Length > 0 ) context.CheckForNamingCollisions (this, identifier, Story.SymbolType.SubFlowAndWeave); } public override string ToString () { if (choiceOnlyContent != null) { return string.Format ("* {0}[{1}]...", startContent, choiceOnlyContent); } else { return string.Format ("* {0}...", startContent); } } Runtime.ChoicePoint _runtimeChoice; Runtime.Container _innerContentContainer; Runtime.Container _outerContainer; Runtime.Container _startContentRuntimeContainer; Runtime.Divert _divertToStartContentOuter; Runtime.Divert _divertToStartContentInner; Runtime.Container _r1Label; Runtime.Container _r2Label; Runtime.DivertTargetValue _returnToR1; Runtime.DivertTargetValue _returnToR2; Expression _condition; } }