diff --git a/InkpotDemo/Config/DefaultEngine.ini b/InkpotDemo/Config/DefaultEngine.ini
index 12aacad..fc269df 100644
--- a/InkpotDemo/Config/DefaultEngine.ini
+++ b/InkpotDemo/Config/DefaultEngine.ini
@@ -56,6 +56,7 @@ CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet'
bAuthorizeAutomaticWidgetVariableCreation=False
FontDPIPreset=Standard
FontDPI=72
+UIScaleRule=ScaleToFit
[/Script/Engine.Engine]
+ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/InkpotDemo")
diff --git a/InkpotDemo/Content/InkpotDemo/Maps/Demo/Demo.umap b/InkpotDemo/Content/InkpotDemo/Maps/Demo/Demo.umap
index 8971eff..6c31df1 100644
Binary files a/InkpotDemo/Content/InkpotDemo/Maps/Demo/Demo.umap and b/InkpotDemo/Content/InkpotDemo/Maps/Demo/Demo.umap differ
diff --git a/InkpotDemo/Content/InkpotDemo/Stories/Demo.uasset b/InkpotDemo/Content/InkpotDemo/Stories/Demo.uasset
index eb2ca63..7f72f9c 100644
Binary files a/InkpotDemo/Content/InkpotDemo/Stories/Demo.uasset and b/InkpotDemo/Content/InkpotDemo/Stories/Demo.uasset differ
diff --git a/InkpotDemo/Content/InkpotDemo/Stories/HelloInk.uasset b/InkpotDemo/Content/InkpotDemo/Stories/HelloInk.uasset
index b51121f..7d75734 100644
Binary files a/InkpotDemo/Content/InkpotDemo/Stories/HelloInk.uasset and b/InkpotDemo/Content/InkpotDemo/Stories/HelloInk.uasset differ
diff --git a/InkpotDemo/Content/InkpotDemo/UI/WBP_Display.uasset b/InkpotDemo/Content/InkpotDemo/UI/WBP_Display.uasset
index 8a2d733..0f8713e 100644
Binary files a/InkpotDemo/Content/InkpotDemo/UI/WBP_Display.uasset and b/InkpotDemo/Content/InkpotDemo/UI/WBP_Display.uasset differ
diff --git a/InkpotDemo/Plugins/Inkpot/Inkpot.uplugin b/InkpotDemo/Plugins/Inkpot/Inkpot.uplugin
index 47a9457..6e8e29a 100644
--- a/InkpotDemo/Plugins/Inkpot/Inkpot.uplugin
+++ b/InkpotDemo/Plugins/Inkpot/Inkpot.uplugin
@@ -1,15 +1,15 @@
{
"FileVersion": 1,
"Version": 1,
- "VersionName": "0.4.20",
+ "VersionName": "1.00.21",
"FriendlyName": "Inkpot",
"Description": "A container for Ink in Unreal.",
"Category": "Scripting",
"CreatedBy": "The Chinese Room",
- "CreatedByURL": "",
- "DocsURL": "",
+ "CreatedByURL": "https://www.thechineseroom.co.uk/",
+ "DocsURL": "https://github.com/the-chinese-room",
"MarketplaceURL": "",
- "SupportURL": "",
+ "SupportURL": "https://github.com/the-chinese-room",
"EnabledByDefault": false,
"CanContainContent": true,
"IsBetaVersion": false,
diff --git a/InkpotDemo/Plugins/Inkpot/README.md b/InkpotDemo/Plugins/Inkpot/README.md
index 858daa3..489edf0 100644
--- a/InkpotDemo/Plugins/Inkpot/README.md
+++ b/InkpotDemo/Plugins/Inkpot/README.md
@@ -1,28 +1,31 @@
# Inkpot
**Inkpot** - An container for **Ink** within the Unreal Engine developed by [The Chinese Room](https://www.thechineseroom.co.uk/).
This is a plugin for Unreal Engine 5.3 or later.
-This is version 0.4.20 of the plugin.
+This is version 1.00.21 of the plugin.
Inkpot is a wrapper for the wonderful narrative scripting language **Ink** developed by [Inkle Studios](https://www.inklestudios.com/ink/).
+### Changes from 0.4.20
+Inkplusplus port now matches version 21 of the Ink runtime engine, Ink version 1.1.1.
+.net version updated to 5.0
+
### Changes from 0.3.20
-Settings backed by CVars added.
-First pass on auto reload and replay of ink source.
+Settings backed by CVars added.
+First pass on auto reload and replay of ink source.
### Changes from 0.2.20
-JSON serialisation now functional.
+JSON serialisation now functional.
### Changes from 0.1.20
-External functions are now implemented, along with functional tests.
+External functions are now implemented, along with functional tests.
## Requirements
Inkpot works with version 5.3 of Unreal.
-Inkpot includes a C++ port of Ink-engine-runtime version 20, which can be found in the InkPlusPlus module.
-As Inkpot compiles the Ink source directly on import, it should be compatible with any Ink editor version as long as you only use features from Ink version 20.
+Inkpot includes a C++ port of Ink-engine-runtime version 21, which can be found in the InkPlusPlus module.
-### .Net framework 3.1
-InkleCate requires the .net framework 3.1 which you can download from here.
-https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-3.1.32-windows-x64-installer
+### .Net framework 5.0
+InkleCate requires the .net framework 5.0 which you can download from here.
+https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-5.0.17-windows-x64-installer
(Without this you will not be able to import Ink source)
### Visual Studio 2022
@@ -32,10 +35,10 @@ https://visualstudio.microsoft.com/vs/
## Installing the plugin
-Make sure you have **.Net framework 3.1** and **Visual Studio 2022** installed.
+Make sure you have **.Net framework 5.0** and **Visual Studio 2022** installed.
In your **project folder**, create a folder named **Plugins** and copy the Inkpot plugin there.
-Make sure the inkpot folder is called simply '**Inkpot**'.
+Make sure the inkpot folder is called simply '**Inkpot**'.
LIke this:
@@ -48,8 +51,8 @@ https://github.com/The-Chinese-Room/InkpotDemo
### My Ink files will not import.
-* Make sure you have **.Net framework 3.1** installed.
-Inkpot uses Inklecate to compile the Ink files and it needs .net 3.1.
+* Make sure you have **.Net framework 5.0** installed.
+Inkpot uses Inklecate to compile the Ink files and it needs .net 5.0.
Without this the Ink files will not import.
* Inkpot has only been tested as an application plugin and not an engine plugin.
@@ -77,8 +80,7 @@ InkPlusPlus is the name of the module that contains the C++ port of the Ink Engi
https://github.com/inkle/ink/tree/master/ink-engine-runtime
When porting the code from C# to C++ we came to the conclusion that we should keep the source as close to the original C# as possible to make updates easier.
-That said we took the code in the summer of 2021 and have not had a need to update as it works for our purposes.
-The Ink version we are running is therefore behind that of the offical Ink release being at Inkversion 20.
+This has now been updated to Inkversion 21, Ink v1.1.1.
### Inkpot
A set of wrapper classes for InkPlusPlus to simplify Blueprint coding.
@@ -94,7 +96,7 @@ There area couple of supporting folders that make up the Inkpot distribution.
-Currently 175 tests.
+Currently 180 tests.
### Third Party
This contains a copy of InkleCate and is used by the script importer to compile the scripts.
diff --git a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/Choice.cpp b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/Choice.cpp
index 1309284..e56b15f 100644
--- a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/Choice.cpp
+++ b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/Choice.cpp
@@ -81,3 +81,13 @@ void Ink::FChoice::SetThreadAtGeneration(TSharedPtr InThreadAtGene
{
ThreadAtGeneration = InThreadAtGeneration;
}
+
+const TArray Ink::FChoice::GetTags() const
+{
+ return Tags;
+}
+
+void Ink::FChoice::SetTags(const TArray& InTags)
+{
+ Tags = InTags;
+}
diff --git a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/ControlCommand.cpp b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/ControlCommand.cpp
index d42934b..da26602 100644
--- a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/ControlCommand.cpp
+++ b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/ControlCommand.cpp
@@ -43,6 +43,8 @@ FString Ink::FControlCommand::ToString()
else if (CommandType == ECommandType::ListFromInt) outputString = TEXT("ListFromInt");
else if (CommandType == ECommandType::ListRange) outputString = TEXT("ListRange");
else if (CommandType == ECommandType::ListRandom) outputString = TEXT("ListRandom");
+ else if (CommandType == ECommandType::BeginTag) outputString = TEXT("BeginTag");
+ else if (CommandType == ECommandType::EndTag) outputString = TEXT("EndTag");
else if (CommandType == ECommandType::TOTAL_VALUES) outputString = TEXT("TOTAL_VALUES");
else outputString = TEXT("UNKNOWN_TYPE");
diff --git a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/InkList.cpp b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/InkList.cpp
index b480947..b76ccc4 100644
--- a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/InkList.cpp
+++ b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/InkList.cpp
@@ -13,7 +13,10 @@ Ink::FInkList::FInkList()
Ink::FInkList::FInkList(const Ink::FInkList& InOtherInkList)
: TMap(InOtherInkList)
{
- _originNames = InOtherInkList.GetOriginNames();
+ TSharedPtr> otherOriginNames = InOtherInkList.GetOriginNames();
+ if(otherOriginNames.IsValid())
+ _originNames = MakeShared>(*otherOriginNames);
+
if (InOtherInkList.Origins.IsValid())
Origins = MakeShared>>(*InOtherInkList.Origins);
}
@@ -249,6 +252,16 @@ Ink::FInkList Ink::FInkList::Intersect(const Ink::FInkList& InOtherList) const
return list;
}
+bool Ink::FInkList::HasIntersection(const Ink::FInkList& InOtherList) const
+{
+ for (const TPair& itemPair : *this)
+ {
+ if (InOtherList.Contains(itemPair.Key))
+ return true;
+ }
+ return false;
+}
+
Ink::FInkList Ink::FInkList::Without(const Ink::FInkList& InListToRemove) const
{
FInkList list = *this;
@@ -261,6 +274,9 @@ Ink::FInkList Ink::FInkList::Without(const Ink::FInkList& InListToRemove) const
bool Ink::FInkList::ContainsList(const Ink::FInkList& InOtherList) const
{
+ if( (InOtherList.Num() == 0 ) || Num() == 0 )
+ return false;
+
for (const TPair& itemPair : InOtherList)
{
if (!Contains(itemPair.Key))
@@ -269,6 +285,16 @@ bool Ink::FInkList::ContainsList(const Ink::FInkList& InOtherList) const
return true;
}
+bool Ink::FInkList::ContainsItem(const FString& InListItemName) const
+{
+ for (const TPair& itemPair : *this)
+ {
+ if (itemPair.Key.ItemName.Equals(InListItemName))
+ return true;
+ }
+ return false;
+}
+
bool Ink::FInkList::GreaterThan(const Ink::FInkList& InOtherList) const
{
if (TMap::Num() == 0) return false;
diff --git a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/JsonSerialisation.cpp b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/JsonSerialisation.cpp
index b5b8a61..5a55aa5 100644
--- a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/JsonSerialisation.cpp
+++ b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/JsonSerialisation.cpp
@@ -43,7 +43,10 @@ TArray Ink::FJsonSerialisation::ControlCommandNames = { TEXT("ev"),
TEXT("end"),
TEXT("listInt"),
TEXT("range"),
- TEXT("lrnd") };
+ TEXT("lrnd"),
+ TEXT("#"),
+ TEXT("/#")
+};
TArray> Ink::FJsonSerialisation::JsonArrayToRuntimeObjectList(const TArray>& InJSONArray, bool InSkipLast)
@@ -455,7 +458,7 @@ void Ink::FJsonSerialisation::WriteRuntimeObject(TJsonWriter<>* InJSONWriter, TS
return;
}
- // Tag
+ // Legacy Tag
TSharedPtr tag = FObject::DynamicCastTo(InObject);
if (tag.IsValid())
{
@@ -762,7 +765,7 @@ TSharedPtr Ink::FJsonSerialisation::JsonTokenToRuntimeObject(const
return variableAssignment;
}
- // Tag
+ // Legacy Tag with text
if (Ink::FJsonExtension::TryGetField(TEXT("#"), *objectValue, propertyValue))
return MakeShared(propertyValue->AsString());
diff --git a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/Story.cpp b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/Story.cpp
index 63663d9..0c514fc 100644
--- a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/Story.cpp
+++ b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/Story.cpp
@@ -22,6 +22,8 @@
Ink::FStory::FStory(const FString& jsonString)
{
+ UE_LOG(InkPlusPlus, Log, TEXT("FStory::FStory %p"), this);
+
const TSharedRef> reader = TJsonReaderFactory<>::Create(jsonString);
TSharedPtr rootObject = MakeShared();
@@ -77,6 +79,11 @@ Ink::FStory::FStory(const FString& jsonString)
ResetState();
}
+Ink::FStory::~FStory()
+{
+ UE_LOG(InkPlusPlus, Log, TEXT("FStory::~FStory %p"), this);
+}
+
FString Ink::FStory::ToJson() const
{
FString jsonString;
@@ -497,7 +504,7 @@ Ink::FStory::EOutputStateChange Ink::FStory::CalculateNewlineOutputStateChange(c
{
// Simple case: nothing's changed, and we still have a newline
// at the end of the current content
- const bool newlineStillExists = currText.Len() >= prevText.Len() && currText[prevText.Len() - 1] == '\n';
+ const bool newlineStillExists = currText.Len() >= prevText.Len() && prevText.Len() > 0 && currText[prevText.Len() - 1] == '\n';
if (prevTagCount == currTagCount && prevText.Len() == currText.Len()
&& newlineStillExists)
return EOutputStateChange::NoChange;
@@ -652,15 +659,14 @@ TSharedPtr Ink::FStory::ProcessChoice(FChoicePoint* choicePoint)
FString startText = "";
FString choiceOnlyText = "";
+ TArray tags;
if (choicePoint->HasChoiceOnlyContent()) {
- auto choiceOnlyStrVal = DynamicCastTo(State->PopEvaluationStack());
- choiceOnlyText = choiceOnlyStrVal->GetValue();
+ choiceOnlyText = PopChoiceStringAndTags(tags);
}
if (choicePoint->HasStartContent()) {
- auto startStrVal = DynamicCastTo(State->PopEvaluationStack());
- startText = startStrVal->GetValue();
+ startText = PopChoiceStringAndTags(tags);
}
// Don't create choice if player has already read this content
@@ -682,6 +688,7 @@ TSharedPtr Ink::FStory::ProcessChoice(FChoicePoint* choicePoint)
choice->SetTargetPath(choicePoint->GetPathOnChoice());
choice->SetSourcePath(choicePoint->GetPath()->ToString());
choice->SetIsInvisibleDefault(choicePoint->IsInvisibleDefault());
+ choice->SetTags(tags);
// We need to capture the state of the callstack at the point where
// the choice was generated, since after the generation of this choice
@@ -1001,13 +1008,102 @@ bool Ink::FStory::PerformLogicAndFlowControl(TSharedPtr contentObj
break;
}
+ // Leave it to story.currentText and story.currentTags to sort out the text from the tags
+ // This is mostly because we can't always rely on the existence of EndTag, and we don't want
+ // to try and flatten dynamic tags to strings every time \n is pushed to output
+ case Ink::ECommandType::BeginTag:
+ State->PushToOutputStream( evalCommand );
+ break;
+
+ case Ink::ECommandType::EndTag:
+ {
+ // EndTag has 2 modes:
+ // - When in string evaluation (for choices)
+ // - Normal
+ //
+ // The only way you could have an EndTag in the middle of
+ // string evaluation is if we're currently generating text for a
+ // choice, such as:
+ //
+ // + choice # tag
+ //
+ // In the above case, the ink will be run twice:
+ // - First, to generate the choice text. String evaluation
+ // will be on, and the final string will be pushed to the
+ // evaluation stack, ready to be popped to make a Choice
+ // object.
+ // - Second, when ink generates text after choosing the choice.
+ // On this ocassion, it's not in string evaluation mode.
+ //
+ // On the writing side, we disallow manually putting tags within
+ // strings like this:
+ //
+ // {"hello # world"}
+ //
+ // So we know that the tag must be being generated as part of
+ // choice content. Therefore, when the tag has been generated,
+ // we push it onto the evaluation stack in the exact same way
+ // as the string for the choice content.
+ if (State->InStringEvaluation())
+ {
+ TArray> contentStackForTag;
+ int outputCountConsumed = 0;
+
+ for (int i = State->OutputStream().Num() - 1; i >= 0; --i) {
+ TSharedPtr obj = State->OutputStream()[i];
+
+ outputCountConsumed++;
+
+ TSharedPtr command = DynamicCastTo( obj );
+ if (command != nullptr) {
+ if (command->GetCommandType() == Ink::ECommandType::BeginTag) {
+ break;
+ }
+ else {
+ Error("Unexpected ControlCommand while extracting tag from choice");
+ break;
+ }
+
+ }
+
+ if ( obj->CanCastTo( Ink::EInkObjectClass::FValueString) )
+ contentStackForTag.Push(obj);
+ }
+
+ // Consume the content that was produced for this string
+ State->PopFromOutputStream(outputCountConsumed);
+
+ FString sb;
+ for( TSharedPtr &obj : contentStackForTag ) {
+ TSharedPtr strVal = DynamicCastTo( obj );
+ if(strVal)
+ sb.Append( strVal->GetValue() );
+ }
+
+ TSharedPtr choiceTag = MakeShared( State->CleanOutputWhitespace( sb ) );
+
+ // Pushing to the evaluation stack means it gets picked up
+ // when a Choice is generated from the next Choice Point.
+ State->PushEvaluationStack( choiceTag );
+ }
+
+ // Otherwise! Simply push EndTag, so that in the output stream we
+ // have a structure of: [BeginTag, "the tag content", EndTag]
+ else {
+ State->PushToOutputStream( evalCommand );
+ }
+ break;
+ }
+
+ // Dynamic strings and tags are built in the same way
case Ink::ECommandType::EndString:
{
// Since we're iterating backward through the content,
// build a stack so that when we build the string,
// it's in the right order
- TArray> contentStackForString;
+ TArray> contentStackForString;
+ TArray> contentToRetain;
int outputCountConsumed = 0;
for (int i = State->OutputStream().Num() - 1; i >= 0; --i) {
@@ -1020,14 +1116,23 @@ bool Ink::FStory::PerformLogicAndFlowControl(TSharedPtr contentObj
break;
}
- TSharedPtr stringValue = DynamicCastTo(obj);
- if (stringValue.IsValid())
- contentStackForString.Push(stringValue);
+ if(obj->CanCastTo(EInkObjectClass::FTag))
+ contentToRetain.Push(obj);
+
+ if(obj->CanCastTo(Ink::EInkObjectClass::FValueString))
+ contentStackForString.Push(obj);
}
// Consume the content that was produced for this string
State->PopFromOutputStream(outputCountConsumed);
+ // Rescue the tags that we want actually to keep on the output stack
+ // rather than consume as part of the string we're building.
+ // At the time of writing, this only applies to Tag objects generated
+ // by choices, which are pushed to the stack during string generation.
+ for(TSharedPtr &rescuedTag : contentToRetain)
+ State->PushToOutputStream( rescuedTag );
+
// Build string out of the content we collected
FString contentString;
for (int i = contentStackForString.Num() - 1; i >= 0; --i)
@@ -1429,6 +1534,16 @@ FString Ink::FStory::GetCurrentFlowName() const
return State->GetCurrentFlowName();
}
+bool Ink::FStory::CurrentFlowIsDefaultFlow() const
+{
+ return State->CurrentFlowIsDefaultFlow();
+}
+
+TArray Ink::FStory::AliveFlowNames() const
+{
+ return State->GetAliveFlowNames();
+}
+
bool Ink::FStory::HasError() const
{
return State->HasError();
@@ -1985,16 +2100,34 @@ TArray Ink::FStory::TagsAtStartOfFlowContainerWithPathString(const FStr
flowContainer = firstContent;
}
- // Any initial tag objects count as the "main tags" associated with that story/knot/stitch
+ bool inTag = false;
TArray tags;
for(const TSharedPtr& c : (*flowContainer->GetContent())) {
- TSharedPtr tag = DynamicCastTo(c);
- if (tag.IsValid()) {
- tags.Add(tag->GetText());
- }
- else break;
- }
+ auto command = DynamicCastTo( c );
+ if( command != nullptr ) {
+ if( command->GetCommandType() == Ink::ECommandType::BeginTag ) {
+ inTag = true;
+ } else if( command->GetCommandType() == Ink::ECommandType::EndTag ) {
+ inTag = false;
+ }
+ }
+
+ else if( inTag ) {
+ auto str = DynamicCastTo( c );
+ if( str != nullptr ) {
+ tags.Add(str->GetValue());
+ } else {
+ Error("Tag contained non-text content. Only plain text is allowed when using globalTags or TagsAtContentPath. If you want to evaluate dynamic content, you need to use story.Continue().");
+ }
+ }
+
+ // Any other content - we're done
+ // We only recognise initial text-only tags
+ else {
+ break;
+ }
+ }
return tags;
}
@@ -2086,6 +2219,17 @@ void Ink::FStory::VisitChangedContainersDueToDivert()
}
}
+FString Ink::FStory::PopChoiceStringAndTags(TArray &OutTags)
+{
+ auto choiceOnlyStrVal = DynamicCastTo(State->PopEvaluationStack());
+ while ( (State->GetEvaluationStack().Num() > 0) && (State->PeekEvaluationStack()->CanCastTo(EInkObjectClass::FTag) ) )
+ {
+ auto tag = DynamicCastTo( State->PopEvaluationStack() );
+ OutTags.EmplaceAt( 0, tag->GetText() ); // popped in reverse order
+ }
+ return choiceOnlyStrVal->GetValue();
+}
+
void Ink::FStory::OnVariableStateDidChangeEvent(const FString& VariableName, TSharedPtr NewValueObj)
{
const TSharedPtr value = DynamicCastTo(NewValueObj);
diff --git a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/StoryState.cpp b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/StoryState.cpp
index 0e67251..853f2d1 100644
--- a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/StoryState.cpp
+++ b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Private/Ink/StoryState.cpp
@@ -20,10 +20,7 @@
Ink::FStoryState::FStoryState(Ink::FStory* InStory)
- : InkSaveStateVersion(9)
- , MinCompatibleLoadVersion(8)
- , DefaultFlowName("DEFAULT_FLOW")
- , DivertedPointer(Ink::FPointer())
+ : DivertedPointer(Ink::FPointer())
, CurrentTurnIndex(-1)
, StorySeed(FMath::RandRange(0, 100))
, PreviousRandom(0)
@@ -33,6 +30,7 @@ Ink::FStoryState::FStoryState(Ink::FStory* InStory)
, OutputStreamTagsDirty(true)
, CurrentFlow(MakeShared(DefaultFlowName, InStory))
, VariableState(MakeShared(CallStack(), Story->GetListDefinitions()))
+ , bAliveFlowNamesDirty(true)
{
SetOutputStreamDirty();
GoToStart();
@@ -139,6 +137,7 @@ void Ink::FStoryState::LoadJSONObj(const FJsonObject& InJSONObj)
*/
SetOutputStreamDirty();
+ bAliveFlowNamesDirty = true;
const TSharedPtr* variableStateObjectField = nullptr;
if (InJSONObj.TryGetObjectField(TEXT("variablesState"), variableStateObjectField))
@@ -406,16 +405,25 @@ FString Ink::FStoryState::GetCurrentText()
if (OutputStreamTextDirty)
{
FString newCurrentText;
+ bool inTag = false;
for (TSharedPtr outputObj : OutputStream())
{
if (!outputObj.IsValid())
continue;
TSharedPtr textObject = FObject::DynamicCastTo(outputObj);
- if (textObject.IsValid())
- {
+ if (!inTag && textObject.IsValid()) {
newCurrentText.Append(textObject->GetValue());
- }
+ } else {
+ TSharedPtr controlCommand = FObject::DynamicCastTo(outputObj);
+ if( controlCommand != nullptr ) {
+ if( controlCommand->GetCommandType() == Ink::ECommandType::BeginTag ) {
+ inTag = true;
+ } else if( controlCommand->GetCommandType() == Ink::ECommandType::EndTag ) {
+ inTag = false;
+ }
+ }
+ }
}
CurrentText = CleanOutputWhitespace(newCurrentText);
@@ -465,14 +473,57 @@ TArray& Ink::FStoryState::GetCurrentTags()
if (OutputStreamTagsDirty)
{
CurrentTags.Empty();
- for (TSharedPtr< Ink::FObject> outputObj : OutputStream())
- {
- TSharedPtr tag = FObject::DynamicCastTo(outputObj);
- if (tag.IsValid())
- {
- CurrentTags.Add(tag->GetText());
- }
+
+ bool inTag = false;
+ FString sb;
+
+ for (TSharedPtr< Ink::FObject> outputObj : OutputStream()) {
+ TSharedPtr controlCommand = Ink::FObject::DynamicCastTo(outputObj);
+
+ if( controlCommand != nullptr ) {
+ if( controlCommand->GetCommandType() == Ink::ECommandType::BeginTag ) {
+ if( inTag && sb.Len() > 0 ) {
+ FString txt = CleanOutputWhitespace(sb);
+ CurrentTags.Add(txt);
+ sb.Empty();
+ }
+ inTag = true;
+ }
+
+ else if( controlCommand->GetCommandType() == Ink::ECommandType::EndTag ) {
+ if( sb.Len() > 0 ) {
+ FString txt = CleanOutputWhitespace(sb);
+ CurrentTags.Add(txt);
+ sb.Empty();
+ }
+ inTag = false;
+ }
+ }
+
+ else if( inTag ) {
+ TSharedPtr strVal = Ink::FObject::DynamicCastTo( outputObj );
+ if( strVal != nullptr ) {
+ sb.Append(strVal->GetValue());
+ }
+ }
+
+ else {
+ TSharedPtr tag = FObject::DynamicCastTo( outputObj );
+ if ( tag != nullptr )
+ {
+ FString tagStr = tag->GetText();
+ if(tagStr.Len() > 0)
+ CurrentTags.Add( tag->GetText() ); // tag.text has whitespae already cleaned
+ }
+ }
}
+
+ if( sb.Len() > 0 ) {
+ FString txt = CleanOutputWhitespace(sb);
+ CurrentTags.Add(txt);
+ sb.Empty();
+ }
+
OutputStreamTagsDirty = false;
}
@@ -484,11 +535,38 @@ FString Ink::FStoryState::GetCurrentFlowName() const
return CurrentFlow->GetName();
}
+bool Ink::FStoryState::CurrentFlowIsDefaultFlow() const
+{
+ return CurrentFlow->GetName().Equals( DefaultFlowName );
+}
+
TSharedPtr>> Ink::FStoryState::GetNamedFlows()
{
return NamedFlows;
}
+const TArray &Ink::FStoryState::GetAliveFlowNames()
+{
+ if( bAliveFlowNamesDirty ) {
+ AliveFlowNames.Empty();
+
+ if (NamedFlows != nullptr)
+ {
+ TArray keys;
+ NamedFlows->GetKeys(keys);
+ for( const FString &flowName : keys ){
+ if (!flowName.Equals(DefaultFlowName)) {
+ AliveFlowNames.Add(flowName);
+ }
+ }
+ }
+
+ bAliveFlowNamesDirty = false;
+ }
+
+ return AliveFlowNames;
+}
+
bool Ink::FStoryState::GetInExpressionEvaluation() const
{
return CallStack()->CurrentElement()->GetInExpressionEvaluation();
@@ -516,6 +594,7 @@ void Ink::FStoryState::SwitchFlow_Internal(const FString& FlowName)
{
flow = MakeShared(FlowName, Story);
NamedFlows->Add(FlowName, flow);
+ bAliveFlowNamesDirty = true;
}
else
{
@@ -546,6 +625,7 @@ void Ink::FStoryState::RemoveFlow_Internal(const FString& FlowName)
}
NamedFlows->Remove(FlowName);
+ bAliveFlowNamesDirty = true;
}
TSharedPtr Ink::FStoryState::CopyAndStartPatching()
@@ -574,6 +654,7 @@ TSharedPtr Ink::FStoryState::CopyAndStartPatching()
copy->NamedFlows->Add(flow.Key, flow.Value);
}
copy->NamedFlows->Add(CurrentFlow->GetName(), copy->CurrentFlow);
+ bAliveFlowNamesDirty = true;
}
if (HasError()) {
diff --git a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/Choice.h b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/Choice.h
index 81ee0e0..78085ba 100644
--- a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/Choice.h
+++ b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/Choice.h
@@ -30,6 +30,9 @@ namespace Ink
bool IsInvisibleDefault() const;
void SetIsInvisibleDefault(bool InIsInvisibleDefault);
+ const TArray GetTags() const;
+ void SetTags(const TArray &InTags);
+
FString GetPathStringOnChoice() const;
void SetPathStringOnChoice(const FString& InPathStringOnChoice);
@@ -47,5 +50,7 @@ namespace Ink
int32 OriginalThreadIndex;
bool bIsInvisibleDefault;
+
+ TArray Tags;
};
}
\ No newline at end of file
diff --git a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/ControlCommand.h b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/ControlCommand.h
index 99d9f17..924edab 100644
--- a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/ControlCommand.h
+++ b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/ControlCommand.h
@@ -31,6 +31,8 @@ namespace Ink
ListFromInt,
ListRange,
ListRandom,
+ BeginTag,
+ EndTag,
// ----
TOTAL_VALUES
};
diff --git a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/InkList.h b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/InkList.h
index e17c039..fe15848 100644
--- a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/InkList.h
+++ b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/InkList.h
@@ -76,6 +76,9 @@ namespace Ink
// list that's passed in - i.e. a list of the items that are shared between the
// two other lists. Equivalent to calling (list1 ^ list2) in ink.
FInkList Intersect(const Ink::FInkList& InOtherList) const;
+
+ /// Fast test for the existence of any intersection between the current list and another
+ bool HasIntersection(const Ink::FInkList& InOtherList) const;
// Returns a new list that's the same as the current one, except with the given items
// removed that are in the passed in list. Equivalent to calling (list1 - list2) in ink.
@@ -85,6 +88,9 @@ namespace Ink
// is passed in. Equivalent to calling (list1 ? list2) in ink.
bool ContainsList(const Ink::FInkList& InOtherList) const;
+ /// Returns true if the current list contains an item matching the given name.
+ bool ContainsItem(const FString &InListItemName) const;
+
// Returns true if all the item values in the current list are greater than all the
// item values in the passed in list. Equivalent to calling (list1 > list2) in ink.
bool GreaterThan(const Ink::FInkList& InOtherList) const;
diff --git a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/Story.h b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/Story.h
index 44d011b..a6df290 100644
--- a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/Story.h
+++ b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/Story.h
@@ -60,6 +60,8 @@ namespace Ink
FChoosePathStringEvent& OnChoosePathString() { return OnChoosePathStringEvent; }
FStory(const FString& jsonString);
+ virtual ~FStory();
+
FString ToJson() const;
void ToJson(FArchive* const Stream) const;
void ToJson(TJsonWriter<>* InJSONWriter) const;
@@ -149,6 +151,10 @@ namespace Ink
FString GetCurrentFlowName() const;
+ bool CurrentFlowIsDefaultFlow() const;
+
+ TArray AliveFlowNames() const;
+
/* Whether the currentErrors list contains any errors.
/// THIS MAY BE REMOVED - you should be setting an error handler directly
using Story.onError. */
@@ -165,7 +171,7 @@ namespace Ink
public:
// The current version of the ink story file format.
- const int32 inkVersionCurrent = 20;
+ const int32 inkVersionCurrent = 21;
/* Version numbers are for engine itself and story file, rather
than the story state save format
@@ -192,6 +198,7 @@ namespace Ink
void IfAsyncWeCant(const FString& ActivityStr) const;
void VisitContainer(TSharedPtr Container, bool AtStart);
void VisitChangedContainersDueToDivert();
+ FString PopChoiceStringAndTags(TArray& OutTags);
TSharedPtr GetCurrentDebugMetadata() const;
void AddError(const FString& InMessage, bool InIsWarning = false, bool InUseEndLineNumber = false) const;
void OnVariableStateDidChangeEvent(const FString& VariableName, TSharedPtr NewValueObj);
diff --git a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/StoryState.h b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/StoryState.h
index 2fc6505..d06413e 100644
--- a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/StoryState.h
+++ b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/StoryState.h
@@ -72,7 +72,10 @@ namespace Ink
TArray& GetCurrentTags();
FString GetCurrentFlowName() const;
+ bool CurrentFlowIsDefaultFlow() const;
+
TSharedPtr>> GetNamedFlows();
+ const TArray& GetAliveFlowNames();
bool GetInExpressionEvaluation() const;
void SetInExpressionEvaluation(bool InExpressionEvaluation);
@@ -129,9 +132,13 @@ namespace Ink
void SetOutputStreamDirty();
private:
- const int32 InkSaveStateVersion;
- const int32 MinCompatibleLoadVersion;
- const FString DefaultFlowName;
+ // Backward compatible changes since v8:
+ // v10: dynamic tags
+ // v9: multi-flows
+ const int32 InkSaveStateVersion{10};
+ const int32 MinCompatibleLoadVersion{8};
+ const FString DefaultFlowName{TEXT("DEFAULT_FLOW")};
+
FString CurrentText;
// Callback for when a state is loaded
@@ -161,5 +168,8 @@ namespace Ink
TSharedPtr CurrentFlow;
TSharedPtr VariableState;
TSharedPtr>> NamedFlows;
+
+ TArray AliveFlowNames;
+ bool bAliveFlowNamesDirty;
};
}
diff --git a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/Tag.h b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/Tag.h
index 3c20edd..fbdd1ce 100644
--- a/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/Tag.h
+++ b/InkpotDemo/Plugins/Inkpot/Source/InkPlusPlus/Public/Ink/Tag.h
@@ -6,6 +6,14 @@
namespace Ink
{
+ // New version of tags is dynamic - it constructs the tags
+ // at runtime based on BeginTag and EndTag control commands.
+ // Plain text that's in the output stream is turned into tags
+ // when you do story.currentTags.
+ // The only place this is used is when flattening tags down
+ // to string in advance, during dynamic string generation if
+ // there's a tag embedded in it. See how ControlCommand.EndString
+ // is implemented in Story.cs for more details + comment
class INKPLUSPLUS_API FTag : public Ink::FObject
{
public:
diff --git a/InkpotDemo/Plugins/Inkpot/Source/Inkpot/Private/Inkpot/InkpotStory.cpp b/InkpotDemo/Plugins/Inkpot/Source/Inkpot/Private/Inkpot/InkpotStory.cpp
index 3256724..7398e9f 100644
--- a/InkpotDemo/Plugins/Inkpot/Source/Inkpot/Private/Inkpot/InkpotStory.cpp
+++ b/InkpotDemo/Plugins/Inkpot/Source/Inkpot/Private/Inkpot/InkpotStory.cpp
@@ -183,15 +183,10 @@ bool UInkpotStory::IsFlowAlive( const FString &InFlowName )
return false;
}
-TArray UInkpotStory::GetAliveFlowNames()
+const TArray &UInkpotStory::GetAliveFlowNames()
{
TSharedPtr state = StoryInternal->GetStoryState();
- TSharedPtr>> namedFlows = state->GetNamedFlows();
-
- TArray flowNames;
- if( namedFlows.IsValid() )
- namedFlows->GetKeys( flowNames );
- return flowNames;
+ return state->GetAliveFlowNames();
}
int32 UInkpotStory::GetAliveFlowCount()
diff --git a/InkpotDemo/Plugins/Inkpot/Source/Inkpot/Public/Inkpot/InkpotStory.h b/InkpotDemo/Plugins/Inkpot/Source/Inkpot/Public/Inkpot/InkpotStory.h
index 9b0d87d..05d0b3f 100644
--- a/InkpotDemo/Plugins/Inkpot/Source/Inkpot/Public/Inkpot/InkpotStory.h
+++ b/InkpotDemo/Plugins/Inkpot/Source/Inkpot/Public/Inkpot/InkpotStory.h
@@ -69,7 +69,7 @@ class INKPOT_API UInkpotStory : public UObject
bool IsFlowAlive( const FString &FlowName );
UFUNCTION(BlueprintPure, Category="Inkpot|Story")
- TArray GetAliveFlowNames();
+ const TArray &GetAliveFlowNames();
UFUNCTION(BlueprintPure, Category="Inkpot|Story")
int32 GetAliveFlowCount();
diff --git a/InkpotDemo/Plugins/Inkpot/Source/InkpotEditor/Private/Test/InkPlusPlusTest.cpp b/InkpotDemo/Plugins/Inkpot/Source/InkpotEditor/Private/Test/InkPlusPlusTest.cpp
index f201ad0..16e4a8b 100644
--- a/InkpotDemo/Plugins/Inkpot/Source/InkpotEditor/Private/Test/InkPlusPlusTest.cpp
+++ b/InkpotDemo/Plugins/Inkpot/Source/InkpotEditor/Private/Test/InkPlusPlusTest.cpp
@@ -44,6 +44,7 @@
#define TEST_CURRENT_TAGS TEXT("TEST_CURRENT_TAGS")
#define TEST_CURRENT_TAG_COUNT TEXT("TEST_CURRENT_TAG_COUNT")
#define TEST_STORY_GLOBAL_TAGS TEXT("TEST_STORY_GLOBAL_TAGS")
+#define TEST_CHOICE_TAGS TEXT("TEST_CHOICE_TAGS")
#define TEST_TAG_FOR_PATH TEXT("TEST_TAG_FOR_PATH")
#define TEST_STORY_EVALUATION_STACK_COUNT TEXT("TEST_STORY_EVALUATION_STACK_COUNT")
@@ -178,6 +179,7 @@ static bool IsStoryInstruction(const FString& Instructions)
TEST_CURRENT_TAGS,
TEST_CURRENT_TAG_COUNT,
TEST_STORY_GLOBAL_TAGS,
+ TEST_CHOICE_TAGS,
TEST_TAG_FOR_PATH,
TEST_STORY_EVALUATION_STACK_COUNT,
@@ -796,6 +798,50 @@ bool FInkTests::RunTest(const FString& InkTestName)
return false;
}
}
+ else if (testInstruction->HasField(TEST_CHOICE_TAGS))
+ {
+ const TSharedPtr* tagForChoiceObject;
+ if (testInstruction->TryGetObjectField(TEST_CHOICE_TAGS, tagForChoiceObject))
+ {
+ if ( (*tagForChoiceObject)->TryGetNumberField( TEXT("CHOICE"), jsonParsedInt ) )
+ {
+ int32 choiceIndex = jsonParsedInt;
+ const TArray &actualTags = story->GetStoryInternal()->GetCurrentChoices()[choiceIndex]->GetTags();
+ TArray expectedTags;
+ if(!(*tagForChoiceObject)->TryGetStringArrayField(TEXT("TAGS"), expectedTags) )
+ {
+ INKPOT_ERROR( "%s : TEST_CHOICE_TAGS. Could not find TAGS in JSON object.\n", *InkTestName );
+ return false;
+ }
+
+ if (expectedTags.Num() != actualTags.Num())
+ {
+ INKPOT_ERROR("%s : TEST_CHOICE_TAGS Tag count not equal: \nExpected: %d\nActual__: %d\n", *InkTestName, expectedTags.Num(), actualTags.Num());
+ return false;
+ }
+
+ for (int32 i = 0; i < expectedTags.Num(); i++)
+ {
+ const bool success = actualTags[i].Equals(expectedTags[i]);
+ if (!success)
+ {
+ INKPOT_ERROR("%s : TEST_CHOICE_TAGS tag string did not match. \nExpected: %s\nActual__: %s\n", *InkTestName, *expectedTags[i], *actualTags[i]);
+ return false;
+ }
+ }
+ }
+ else
+ {
+ INKPOT_ERROR( "%s : TEST_CHOICE_TAGS. Could not find CHOICE in JSON object.\n", *InkTestName );
+ return false;
+ }
+ }
+ else
+ {
+ INKPOT_ERROR("%s : TEST_CHOICE_TAGS. Could not Deserialize to object \n", *InkTestName);
+ return false;
+ }
+ }
else if (testInstruction->HasField(TEST_RANDOM_LIST_CONTINUE))
{
TArray expectedRandomOutputs;
diff --git a/InkpotDemo/Plugins/Inkpot/TestInkSource/TestContainsEmptyListAlwaysFalse.ink b/InkpotDemo/Plugins/Inkpot/TestInkSource/TestContainsEmptyListAlwaysFalse.ink
new file mode 100644
index 0000000..8096910
--- /dev/null
+++ b/InkpotDemo/Plugins/Inkpot/TestInkSource/TestContainsEmptyListAlwaysFalse.ink
@@ -0,0 +1,14 @@
+LIST list = (a), b
+{list ? ()}
+{() ? ()}
+{() ? list}
+
+/*
+INK_TEST_STORY_START
+[
+ {"TEST_ERROR_EQUAL": 0},
+ {"TEST_WARNING_EQUAL": 0},
+ {"EXECUTE_CONTINUE_MAXIMALLY": "false\nfalse\nfalse\n"}
+]
+INK_TEST_END
+*/
\ No newline at end of file
diff --git a/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTagOnChoice.ink b/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTagOnChoice.ink
index a588e09..add96ca 100644
--- a/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTagOnChoice.ink
+++ b/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTagOnChoice.ink
@@ -1,15 +1,11 @@
-* [Hi] Hello -> END #hey
+* [Hi] Hello -> END #hey
/*
INK_TEST_STORY_START
[
- {"TEST_ERROR_EQUAL": 0},
+ {"TEST_ERROR_EQUAL": 1},
{"TEST_WARNING_EQUAL": 0},
- {"EXECUTE_STORY_CONTINUE": "-1"},
- {"EXECUTE_STORY_CHOICE": 0},
- {"EXECUTE_STORY_CONTINUE": "Hello"},
- {"TEST_CURRENT_TAGS": ["hey"]}
]
INK_TEST_END
-*/
\ No newline at end of file
+*/
diff --git a/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTags.ink b/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTags.ink
index 690b012..b967972 100644
--- a/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTags.ink
+++ b/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTags.ink
@@ -16,9 +16,6 @@ Stitch content
# this tag is below some content so isn't included in the static tags for the stitch
-> END
-
-* [Hi] Hello -> END #hey
-
/*
INK_TEST_STORY_START
[
diff --git a/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTagsDynamicContent.ink b/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTagsDynamicContent.ink
new file mode 100644
index 0000000..36771c0
--- /dev/null
+++ b/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTagsDynamicContent.ink
@@ -0,0 +1,12 @@
+tag # pic{5+3}{red|blue}.jpg
+
+/*
+INK_TEST_STORY_START
+[
+ {"TEST_ERROR_EQUAL": 0},
+ {"TEST_WARNING_EQUAL": 0},
+ {"EXECUTE_STORY_CONTINUE": "tag\n"},
+ {"TEST_CURRENT_TAGS": ["pic8red.jpg"]}
+]
+INK_TEST_END
+*/
\ No newline at end of file
diff --git a/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTagsInChoice.ink b/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTagsInChoice.ink
new file mode 100644
index 0000000..a4a3121
--- /dev/null
+++ b/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTagsInChoice.ink
@@ -0,0 +1,20 @@
++ one #one [two #two] three #three -> END
+
+/*
+INK_TEST_STORY_START
+[
+ {"TEST_ERROR_EQUAL": 0},
+ {"TEST_WARNING_EQUAL": 0},
+ {"EXECUTE_STORY_CONTINUE": "-1"},
+ {"TEST_CURRENT_TAG_COUNT": 0},
+ {"TEST_CHOICE_COUNT":1 },
+ {"TEST_CHOICE_TAGS": {
+ "CHOICE": 0,
+ "TAGS": ["one","two"]
+ }},
+ {"EXECUTE_STORY_CHOICE": 0 },
+ {"EXECUTE_STORY_CONTINUE": "one three"},
+ {"TEST_CURRENT_TAGS": ["one","three"] }
+]
+INK_TEST_END
+*/
diff --git a/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTagsInSeq.ink b/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTagsInSeq.ink
new file mode 100644
index 0000000..283810e
--- /dev/null
+++ b/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTagsInSeq.ink
@@ -0,0 +1,18 @@
+-> knot -> knot ->
+
+== knot
+ A {red #red|white #white|blue #blue|green #green} sequence.
+ ->->
+
+/*
+INK_TEST_STORY_START
+[
+ {"TEST_ERROR_EQUAL": 0},
+ {"TEST_WARNING_EQUAL": 0},
+ {"EXECUTE_STORY_CONTINUE": "A red sequence.\n"},
+ {"TEST_CURRENT_TAGS": ["red"]},
+ {"EXECUTE_STORY_CONTINUE": "A white sequence.\n"},
+ {"TEST_CURRENT_TAGS": ["white"]}
+]
+INK_TEST_END
+*/
diff --git a/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTunnelOnwardsToVariableDivertTarget.ink b/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTunnelOnwardsToVariableDivertTarget.ink
new file mode 100644
index 0000000..867fe6b
--- /dev/null
+++ b/InkpotDemo/Plugins/Inkpot/TestInkSource/TestTunnelOnwardsToVariableDivertTarget.ink
@@ -0,0 +1,22 @@
+-> outer ->
+
+== outer
+This is outer
+-> cut_to(-> the_esc)
+
+=== cut_to(-> escape)
+ ->-> escape
+
+== the_esc
+This is the_esc
+-> END
+
+/*
+INK_TEST_STORY_START
+[
+ {"TEST_ERROR_EQUAL": 0},
+ {"TEST_WARNING_EQUAL": 0},
+ {"EXECUTE_CONTINUE_MAXIMALLY": "This is outer\nThis is the_esc\n"}
+]
+INK_TEST_END
+*/
\ No newline at end of file
diff --git a/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/ink-engine-runtime.dll b/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/ink-engine-runtime.dll
index 4c4346b..5f3b9b0 100644
Binary files a/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/ink-engine-runtime.dll and b/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/ink-engine-runtime.dll differ
diff --git a/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/ink-engine-runtime.xml b/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/ink-engine-runtime.xml
deleted file mode 100644
index 3c1a26a..0000000
--- a/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/ink-engine-runtime.xml
+++ /dev/null
@@ -1,1113 +0,0 @@
-
-
-
- ink-engine-runtime
-
-
-
-
- A generated Choice from the story.
- A single ChoicePoint in the Story could potentially generate
- different Choices dynamically dependent on state, so they're
- separated.
-
-
-
-
- The main text to presented to the player for this Choice.
-
-
-
-
- The target path that the Story should be diverted to if
- this Choice is chosen.
-
-
-
-
- Get the path to the original choice point - where was this choice defined in the story?
-
- A dot separated path into the story data.
-
-
-
- The original index into currentChoices list on the Story when
- this Choice was generated, for convenience.
-
-
-
-
- The ChoicePoint represents the point within the Story where
- a Choice instance gets generated. The distinction is made
- because the text of the Choice can be dynamically generated.
-
-
-
-
- The underlying type for a list item in ink. It stores the original list definition
- name as well as the item name, but without the value of the item. When the value is
- stored, it's stored in a KeyValuePair of InkListItem and int.
-
-
-
-
- The name of the list where the item was originally defined.
-
-
-
-
- The main name of the item as defined in ink.
-
-
-
-
- Create an item with the given original list definition name, and the name of this
- item.
-
-
-
-
- Create an item from a dot-separted string of the form "listDefinitionName.listItemName".
-
-
-
-
- Get the full dot-separated name of the item, in the form "listDefinitionName.itemName".
-
-
-
-
- Get the full dot-separated name of the item, in the form "listDefinitionName.itemName".
- Calls fullName internally.
-
-
-
-
- Is this item the same as another item?
-
-
-
-
- Get the hashcode for an item.
-
-
-
-
- The InkList is the underlying type that's used to store an instance of a
- list in ink. It's not used for the *definition* of the list, but for a list
- value that's stored in a variable.
- Somewhat confusingly, it's backed by a C# Dictionary, and has nothing to
- do with a C# List!
-
-
-
-
- Create a new empty ink list.
-
-
-
-
- Create a new ink list that contains the same contents as another list.
-
-
-
-
- Create a new empty ink list that's intended to hold items from a particular origin
- list definition. The origin Story is needed in order to be able to look up that definition.
-
-
-
-
- Converts a string to an ink list and returns for use in the story.
-
- InkList created from string list item
- Item key.
- Origin story.
-
-
-
- Adds the given item to the ink list. Note that the item must come from a list definition that
- is already "known" to this list, so that the item's value can be looked up. By "known", we mean
- that it already has items in it from that source, or it did at one point - it can't be a
- completely fresh empty list, or a list that only contains items from a different list definition.
-
-
-
-
- Adds the given item to the ink list, attempting to find the origin list definition that it belongs to.
- The item must therefore come from a list definition that is already "known" to this list, so that the
- item's value can be looked up. By "known", we mean that it already has items in it from that source, or
- it did at one point - it can't be a completely fresh empty list, or a list that only contains items from
- a different list definition.
-
-
-
-
- Returns true if this ink list contains an item with the given short name
- (ignoring the original list where it was defined).
-
-
-
-
- Get the maximum item in the list, equivalent to calling LIST_MAX(list) in ink.
-
-
-
-
- Get the minimum item in the list, equivalent to calling LIST_MIN(list) in ink.
-
-
-
-
- The inverse of the list, equivalent to calling LIST_INVERSE(list) in ink
-
-
-
-
- The list of all items from the original list definition, equivalent to calling
- LIST_ALL(list) in ink.
-
-
-
-
- Returns a new list that is the combination of the current list and one that's
- passed in. Equivalent to calling (list1 + list2) in ink.
-
-
-
-
- Returns a new list that is the intersection of the current list with another
- list that's passed in - i.e. a list of the items that are shared between the
- two other lists. Equivalent to calling (list1 ^ list2) in ink.
-
-
-
-
- Fast test for the existence of any intersection between the current list and another
-
-
-
-
- Returns a new list that's the same as the current one, except with the given items
- removed that are in the passed in list. Equivalent to calling (list1 - list2) in ink.
-
- List to remove.
-
-
-
- Returns true if the current list contains all the items that are in the list that
- is passed in. Equivalent to calling (list1 ? list2) in ink.
-
- Other list.
-
-
-
- Returns true if the current list contains an item matching the given name.
-
- Other list.
-
-
-
- Returns true if all the item values in the current list are greater than all the
- item values in the passed in list. Equivalent to calling (list1 > list2) in ink.
-
-
-
-
- Returns true if the item values in the current list overlap or are all greater than
- the item values in the passed in list. None of the item values in the current list must
- fall below the item values in the passed in list. Equivalent to (list1 >= list2) in ink,
- or LIST_MIN(list1) >= LIST_MIN(list2) && LIST_MAX(list1) >= LIST_MAX(list2).
-
-
-
-
- Returns true if all the item values in the current list are less than all the
- item values in the passed in list. Equivalent to calling (list1 < list2) in ink.
-
-
-
-
- Returns true if the item values in the current list overlap or are all less than
- the item values in the passed in list. None of the item values in the current list must
- go above the item values in the passed in list. Equivalent to (list1 <= list2) in ink,
- or LIST_MAX(list1) <= LIST_MAX(list2) && LIST_MIN(list1) <= LIST_MIN(list2).
-
-
-
-
- Returns a sublist with the elements given the minimum and maxmimum bounds.
- The bounds can either be ints which are indices into the entire (sorted) list,
- or they can be InkLists themselves. These are intended to be single-item lists so
- you can specify the upper and lower bounds. If you pass in multi-item lists, it'll
- use the minimum and maximum items in those lists respectively.
- WARNING: Calling this method requires a full sort of all the elements in the list.
-
-
-
-
- Returns true if the passed object is also an ink list that contains
- the same items as the current list, false otherwise.
-
-
-
-
- Return the hashcode for this object, used for comparisons and inserting into dictionaries.
-
-
-
-
- Returns a string in the form "a, b, c" with the names of the items in the list, without
- the origin list definition names. Equivalent to writing {list} in ink.
-
-
-
-
- Base class for all ink runtime content.
-
-
-
-
- Runtime.Objects can be included in the main Story as a hierarchy.
- Usually parents are Container objects. (TODO: Always?)
-
- The parent.
-
-
- Allow implicit conversion to bool so you don't have to do:
- if( myObj != null ) ...
-
-
- Required for implicit bool comparison
-
-
- Required for implicit bool comparison
-
-
- Required for implicit bool comparison
-
-
- Required for implicit bool comparison
-
-
-
- Internal structure used to point to a particular / current point in the story.
- Where Path is a set of components that make content fully addressable, this is
- a reference to the current container, and the index of the current piece of
- content within that container. This scheme makes it as fast and efficient as
- possible to increment the pointer (move the story forwards) in a way that's as
- native to the internal engine as possible.
-
-
-
-
- Simple ink profiler that logs every instruction in the story and counts frequency and timing.
- To use:
-
- var profiler = story.StartProfiling(),
-
- (play your story for a bit)
-
- var reportStr = profiler.Report();
-
- story.EndProfiling();
-
-
-
-
-
- The root node in the hierarchical tree of recorded ink timings.
-
-
-
-
- Generate a printable report based on the data recording during profiling.
-
-
-
-
- Generate a printable report specifying the average and maximum times spent
- stepping over different internal ink instruction types.
- This report type is primarily used to profile the ink engine itself rather
- than your own specific ink.
-
-
-
-
- Create a large log of all the internal instructions that were evaluated while profiling was active.
- Log is in a tab-separated format, for easy loading into a spreadsheet application.
-
-
-
-
- Node used in the hierarchical tree of timings used by the Profiler.
- Each node corresponds to a single line viewable in a UI-based representation.
-
-
-
-
- The key for the node corresponds to the printable name of the callstack element.
-
-
-
-
- Horribly hacky field only used by ink unity integration,
- but saves constructing an entire data structure that mirrors
- the one in here purely to store the state of whether each
- node in the UI has been opened or not ///
-
-
-
- Whether this node contains any sub-nodes - i.e. does it call anything else
- that has been recorded?
-
- true if has children; otherwise, false.
-
-
-
- Total number of milliseconds this node has been active for.
-
-
-
-
- Returns a sorted enumerable of the nodes in descending order of
- how long they took to run.
-
-
-
-
- Generates a string giving timing information for this single node, including
- total milliseconds spent on the piece of ink, the time spent within itself
- (v.s. spent in children), as well as the number of samples (instruction steps)
- recorded for both too.
-
- The own report.
-
-
-
- String is a report of the sub-tree from this node, but without any of the header information
- that's prepended by the Profiler in its Report() method.
-
-
-
-
- Simple custom JSON serialisation implementation that takes JSON-able System.Collections that
- are produced by the ink engine and converts to and from JSON text.
-
-
-
-
- A Story is the core class that represents a complete Ink narrative, and
- manages the evaluation and state of it.
-
-
-
-
- The current version of the ink story file format.
-
-
-
-
- The minimum legacy version of ink that can be loaded by the current version of the code.
-
-
-
-
- The list of Choice objects available at the current point in
- the Story. This list will be populated as the Story is stepped
- through with the Continue() method. Once canContinue becomes
- false, this list will be populated, and is usually
- (but not always) on the final Continue() step.
-
-
-
-
- The latest line of text to be generated from a Continue() call.
-
-
-
-
- Gets a list of tags as defined with '#' in source that were seen
- during the latest Continue() call.
-
-
-
-
- Any errors generated during evaluation of the Story.
-
-
-
-
- Any warnings generated during evaluation of the Story.
-
-
-
-
- The current flow name if using multi-flow functionality - see SwitchFlow
-
-
-
-
- Is the default flow currently active? By definition, will also return true if not using multi-flow functionality - see SwitchFlow
-
-
-
-
- Names of currently alive flows (not including the default flow)
-
-
-
-
- Whether the currentErrors list contains any errors.
- THIS MAY BE REMOVED - you should be setting an error handler directly
- using Story.onError.
-
-
-
-
- Whether the currentWarnings list contains any warnings.
-
-
-
-
- The VariablesState object contains all the global variables in the story.
- However, note that there's more to the state of a Story than just the
- global variables. This is a convenience accessor to the full state object.
-
-
-
-
- The entire current state of the story including (but not limited to):
-
- * Global variables
- * Temporary variables
- * Read/visit and turn counts
- * The callstack and evaluation stacks
- * The current threads
-
-
-
-
-
- Error handler for all runtime errors in ink - i.e. problems
- with the source ink itself that are only discovered when playing
- the story.
- It's strongly recommended that you assign an error handler to your
- story instance to avoid getting exceptions for ink errors.
-
-
-
-
- Callback for when ContinueInternal is complete
-
-
-
-
- Callback for when a choice is about to be executed
-
-
-
-
- Callback for when a function is about to be evaluated
-
-
-
-
- Callback for when a function has been evaluated
- This is necessary because evaluating a function can cause continuing
-
-
-
-
- Callback for when a path string is chosen
-
-
-
-
- Start recording ink profiling information during calls to Continue on Story.
- Return a Profiler instance that you can request a report from when you're finished.
-
-
-
-
- Stop recording ink profiling information during calls to Continue on Story.
- To generate a report from the profiler, call
-
-
-
-
- Construct a Story object using a JSON string compiled through inklecate.
-
-
-
-
- The Story itself in JSON representation.
-
-
-
-
- The Story itself in JSON representation.
-
-
-
-
- Reset the Story back to its initial state as it was when it was
- first constructed.
-
-
-
-
- Unwinds the callstack. Useful to reset the Story's evaluation
- without actually changing any meaningful state, for example if
- you want to exit a section of story prematurely and tell it to
- go elsewhere with a call to ChoosePathString(...).
- Doing so without calling ResetCallstack() could cause unexpected
- issues if, for example, the Story was in a tunnel already.
-
-
-
-
- Continue the story for one line of content, if possible.
- If you're not sure if there's more content available, for example if you
- want to check whether you're at a choice point or at the end of the story,
- you should call canContinue before calling this function.
-
- The line of text content.
-
-
-
- Check whether more content is available if you were to call Continue() - i.e.
- are we mid story rather than at a choice point or at the end.
-
- true if it's possible to call Continue().
-
-
-
- If ContinueAsync was called (with milliseconds limit > 0) then this property
- will return false if the ink evaluation isn't yet finished, and you need to call
- it again in order for the Continue to fully complete.
-
-
-
-
- An "asnychronous" version of Continue that only partially evaluates the ink,
- with a budget of a certain time limit. It will exit ink evaluation early if
- the evaluation isn't complete within the time limit, with the
- asyncContinueComplete property being false.
- This is useful if ink evaluation takes a long time, and you want to distribute
- it over multiple game frames for smoother animation.
- If you pass a limit of zero, then it will fully evaluate the ink in the same
- way as calling Continue (and in fact, this exactly what Continue does internally).
-
-
-
-
- Continue the story until the next choice point or until it runs out of content.
- This is as opposed to the Continue() method which only evaluates one line of
- output at a time.
-
- The resulting text evaluated by the ink engine, concatenated together.
-
-
-
- Advanced usage!
- If you have a large story, and saving state to JSON takes too long for your
- framerate, you can temporarily freeze a copy of the state for saving on
- a separate thread. Internally, the engine maintains a "diff patch".
- When you've finished saving your state, call BackgroundSaveComplete()
- and that diff patch will be applied, allowing the story to continue
- in its usual mode.
-
- The state for background thread save.
-
-
-
- See CopyStateForBackgroundThreadSave. This method releases the
- "frozen" save state, applying its patch that it was using internally.
-
-
-
-
- Checks whether contentObj is a control or flow object rather than a piece of content,
- and performs the required command if necessary.
-
- true if object was logic or flow control, false if it's normal content.
- Content object.
-
-
-
- Change the current position of the story to the given path. From here you can
- call Continue() to evaluate the next line.
-
- The path string is a dot-separated path as used internally by the engine.
- These examples should work:
-
- myKnot
- myKnot.myStitch
-
- Note however that this won't necessarily work:
-
- myKnot.myStitch.myLabelledChoice
-
- ...because of the way that content is nested within a weave structure.
-
- By default this will reset the callstack beforehand, which means that any
- tunnels, threads or functions you were in at the time of calling will be
- discarded. This is different from the behaviour of ChooseChoiceIndex, which
- will always keep the callstack, since the choices are known to come from the
- correct state, and known their source thread.
-
- You have the option of passing false to the resetCallstack parameter if you
- don't want this behaviour, and will leave any active threads, tunnels or
- function calls in-tact.
-
- This is potentially dangerous! If you're in the middle of a tunnel,
- it'll redirect only the inner-most tunnel, meaning that when you tunnel-return
- using '->->', it'll return to where you were before. This may be what you
- want though. However, if you're in the middle of a function, ChoosePathString
- will throw an exception.
-
-
- A dot-separted path string, as specified above.
- Whether to reset the callstack first (see summary description).
- Optional set of arguments to pass, if path is to a knot that takes them.
-
-
-
- Chooses the Choice from the currentChoices list with the given
- index. Internally, this sets the current content path to that
- pointed to by the Choice, ready to continue story evaluation.
-
-
-
-
- Checks if a function exists.
-
- True if the function exists, else false.
- The name of the function as declared in ink.
-
-
-
- Evaluates a function defined in ink.
-
- The return value as returned from the ink function with `~ return myValue`, or null if nothing is returned.
- The name of the function as declared in ink.
- The arguments that the ink function takes, if any. Note that we don't (can't) do any validation on the number of arguments right now, so make sure you get it right!
-
-
-
- Evaluates a function defined in ink, and gathers the possibly multi-line text as generated by the function.
- This text output is any text written as normal content within the function, as opposed to the return value, as returned with `~ return`.
-
- The return value as returned from the ink function with `~ return myValue`, or null if nothing is returned.
- The name of the function as declared in ink.
- The text content produced by the function via normal ink, if any.
- The arguments that the ink function takes, if any. Note that we don't (can't) do any validation on the number of arguments right now, so make sure you get it right!
-
-
-
- An ink file can provide a fallback functions for when when an EXTERNAL has been left
- unbound by the client, and the fallback function will be called instead. Useful when
- testing a story in playmode, when it's not possible to write a client-side C# external
- function, but you don't want it to fail to run.
-
-
-
-
- General purpose delegate definition for bound EXTERNAL function definitions
- from ink. Note that this version isn't necessary if you have a function
- with three arguments or less - see the overloads of BindExternalFunction.
-
-
-
-
- Most general form of function binding that returns an object
- and takes an array of object parameters.
- The only way to bind a function with more than 3 arguments.
-
- EXTERNAL ink function name to bind to.
- The C# function to bind.
- The ink engine often evaluates further
- than you might expect beyond the current line just in case it sees
- glue that will cause the two lines to become one. In this case it's
- possible that a function can appear to be called twice instead of
- just once, and earlier than you expect. If it's safe for your
- function to be called in this way (since the result and side effect
- of the function will not change), then you can pass 'true'.
- Usually, you want to pass 'false', especially if you want some action
- to be performed in game code when this function is called.
-
-
-
- Bind a C# function to an ink EXTERNAL function declaration.
-
- EXTERNAL ink function name to bind to.
- The C# function to bind.
- The ink engine often evaluates further
- than you might expect beyond the current line just in case it sees
- glue that will cause the two lines to become one. In this case it's
- possible that a function can appear to be called twice instead of
- just once, and earlier than you expect. If it's safe for your
- function to be called in this way (since the result and side effect
- of the function will not change), then you can pass 'true'.
- Usually, you want to pass 'false', especially if you want some action
- to be performed in game code when this function is called.
-
-
-
- Bind a C# Action to an ink EXTERNAL function declaration.
-
- EXTERNAL ink function name to bind to.
- The C# action to bind.
- The ink engine often evaluates further
- than you might expect beyond the current line just in case it sees
- glue that will cause the two lines to become one. In this case it's
- possible that a function can appear to be called twice instead of
- just once, and earlier than you expect. If it's safe for your
- function to be called in this way (since the result and side effect
- of the function will not change), then you can pass 'true'.
- Usually, you want to pass 'false', especially if you want some action
- to be performed in game code when this function is called.
-
-
-
- Bind a C# function to an ink EXTERNAL function declaration.
-
- EXTERNAL ink function name to bind to.
- The C# function to bind.
- The ink engine often evaluates further
- than you might expect beyond the current line just in case it sees
- glue that will cause the two lines to become one. In this case it's
- possible that a function can appear to be called twice instead of
- just once, and earlier than you expect. If it's safe for your
- function to be called in this way (since the result and side effect
- of the function will not change), then you can pass 'true'.
- Usually, you want to pass 'false', especially if you want some action
- to be performed in game code when this function is called.
-
-
-
- Bind a C# action to an ink EXTERNAL function declaration.
-
- EXTERNAL ink function name to bind to.
- The C# action to bind.
- The ink engine often evaluates further
- than you might expect beyond the current line just in case it sees
- glue that will cause the two lines to become one. In this case it's
- possible that a function can appear to be called twice instead of
- just once, and earlier than you expect. If it's safe for your
- function to be called in this way (since the result and side effect
- of the function will not change), then you can pass 'true'.
- Usually, you want to pass 'false', especially if you want some action
- to be performed in game code when this function is called.
-
-
-
- Bind a C# function to an ink EXTERNAL function declaration.
-
- EXTERNAL ink function name to bind to.
- The C# function to bind.
- The ink engine often evaluates further
- than you might expect beyond the current line just in case it sees
- glue that will cause the two lines to become one. In this case it's
- possible that a function can appear to be called twice instead of
- just once, and earlier than you expect. If it's safe for your
- function to be called in this way (since the result and side effect
- of the function will not change), then you can pass 'true'.
- Usually, you want to pass 'false', especially if you want some action
- to be performed in game code when this function is called.
-
-
-
- Bind a C# action to an ink EXTERNAL function declaration.
-
- EXTERNAL ink function name to bind to.
- The C# action to bind.
- The ink engine often evaluates further
- than you might expect beyond the current line just in case it sees
- glue that will cause the two lines to become one. In this case it's
- possible that a function can appear to be called twice instead of
- just once, and earlier than you expect. If it's safe for your
- function to be called in this way (since the result and side effect
- of the function will not change), then you can pass 'true'.
- Usually, you want to pass 'false', especially if you want some action
- to be performed in game code when this function is called.
-
-
-
- Bind a C# function to an ink EXTERNAL function declaration.
-
- EXTERNAL ink function name to bind to.
- The C# function to bind.
- The ink engine often evaluates further
- than you might expect beyond the current line just in case it sees
- glue that will cause the two lines to become one. In this case it's
- possible that a function can appear to be called twice instead of
- just once, and earlier than you expect. If it's safe for your
- function to be called in this way (since the result and side effect
- of the function will not change), then you can pass 'true'.
- Usually, you want to pass 'false', especially if you want some action
- to be performed in game code when this function is called.
-
-
-
- Bind a C# action to an ink EXTERNAL function declaration.
-
- EXTERNAL ink function name to bind to.
- The C# action to bind.
- The ink engine often evaluates further
- than you might expect beyond the current line just in case it sees
- glue that will cause the two lines to become one. In this case it's
- possible that a function can appear to be called twice instead of
- just once, and earlier than you expect. If it's safe for your
- function to be called in this way (since the result and side effect
- of the function will not change), then you can pass 'true'.
- Usually, you want to pass 'false', especially if you want some action
- to be performed in game code when this function is called.
-
-
-
- Bind a C# function to an ink EXTERNAL function declaration.
-
- EXTERNAL ink function name to bind to.
- The C# function to bind.
- The ink engine often evaluates further
- than you might expect beyond the current line just in case it sees
- glue that will cause the two lines to become one. In this case it's
- possible that a function can appear to be called twice instead of
- just once, and earlier than you expect. If it's safe for your
- function to be called in this way (since the result and side effect
- of the function will not change), then you can pass 'true'.
- Usually, you want to pass 'false', especially if you want some action
- to be performed in game code when this function is called.
-
-
-
- Bind a C# action to an ink EXTERNAL function declaration.
-
- EXTERNAL ink function name to bind to.
- The C# action to bind.
- The ink engine often evaluates further
- than you might expect beyond the current line just in case it sees
- glue that will cause the two lines to become one. In this case it's
- possible that a function can appear to be called twice instead of
- just once, and earlier than you expect. If it's safe for your
- function to be called in this way (since the result and side effect
- of the function will not change), then you can pass 'true'.
- Usually, you want to pass 'false', especially if you want some action
- to be performed in game code when this function is called.
-
-
-
- Remove a binding for a named EXTERNAL ink function.
-
-
-
-
- Check that all EXTERNAL ink functions have a valid bound C# function.
- Note that this is automatically called on the first call to Continue().
-
-
-
-
- Delegate definition for variable observation - see ObserveVariable.
-
-
-
-
- When the named global variable changes it's value, the observer will be
- called to notify it of the change. Note that if the value changes multiple
- times within the ink, the observer will only be called once, at the end
- of the ink's evaluation. If, during the evaluation, it changes and then
- changes back again to its original value, it will still be called.
- Note that the observer will also be fired if the value of the variable
- is changed externally to the ink, by directly setting a value in
- story.variablesState.
-
- The name of the global variable to observe.
- A delegate function to call when the variable changes.
-
-
-
- Convenience function to allow multiple variables to be observed with the same
- observer delegate function. See the singular ObserveVariable for details.
- The observer will get one call for every variable that has changed.
-
- The set of variables to observe.
- The delegate function to call when any of the named variables change.
-
-
-
- Removes the variable observer, to stop getting variable change notifications.
- If you pass a specific variable name, it will stop observing that particular one. If you
- pass null (or leave it blank, since it's optional), then the observer will be removed
- from all variables that it's subscribed to. If you pass in a specific variable name and
- null for the the observer, all observers for that variable will be removed.
-
- (Optional) The observer to stop observing.
- (Optional) Specific variable name to stop observing.
-
-
-
- Get any global tags associated with the story. These are defined as
- hash tags defined at the very top of the story.
-
-
-
-
- Gets any tags associated with a particular knot or knot.stitch.
- These are defined as hash tags defined at the very top of a
- knot or stitch.
-
- The path of the knot or stitch, in the form "knot" or "knot.stitch".
-
-
-
- Useful when debugging a (very short) story, to visualise the state of the
- story. Add this call as a watch and open the extended text. A left-arrow mark
- will denote the current point of the story.
- It's only recommended that this is used on very short debug stories, since
- it can end up generate a large quantity of text otherwise.
-
-
-
-
- Exception that represents an error when running a Story at runtime.
- An exception being thrown of this type is typically when there's
- a bug in your ink, rather than in the ink engine itself!
-
-
-
-
- Constructs a default instance of a StoryException without a message.
-
-
-
-
- Constructs an instance of a StoryException with a message.
-
- The error message.
-
-
-
- All story state information is included in the StoryState class,
- including global variables, read counts, the pointer to the current
- point in the story, the call stack (for tunnels, functions, etc),
- and a few other smaller bits and pieces. You can save the current
- state using the json serialisation functions ToJson and LoadJson.
-
-
-
-
- The current version of the state save file JSON-based format.
-
-
-
-
- Callback for when a state is loaded
-
-
-
-
- Exports the current state to json format, in order to save the game.
-
- The save state in json format.
-
-
-
- Exports the current state to json format, in order to save the game.
- For this overload you can pass in a custom stream, such as a FileStream.
-
-
-
-
- Loads a previously saved state in JSON format.
-
- The JSON string to load.
-
-
-
- Gets the visit/read count of a particular Container at the given path.
- For a knot or stitch, that path string will be in the form:
-
- knot
- knot.stitch
-
-
- The number of times the specific knot or stitch has
- been enountered by the ink engine.
- The dot-separated path string of
- the specific knot or stitch.
-
-
-
- String representation of the location where the story currently is.
-
-
-
-
- Ends the current ink flow, unwrapping the callstack but without
- affecting any variables. Useful if the ink is (say) in the middle
- a nested tunnel, and you want it to reset so that you can divert
- elsewhere using ChoosePathString(). Otherwise, after finishing
- the content you diverted to, it would continue where it left off.
- Calling this is equivalent to calling -> END in ink.
-
-
-
-
- Encompasses all the global variables in an ink Story, and
- allows binding of a VariableChanged event so that that game
- code can be notified whenever the global variables change.
-
-
-
-
- Get or set the value of a named global ink variable.
- The types available are the standard ink types. Certain
- types will be implicitly casted when setting.
- For example, doubles to floats, longs to ints, and bools
- to ints.
-
-
-
-
- Enumerator to allow iteration over all global variables by name.
-
-
-
-
- When saving out JSON state, we can skip saving global values that
- remain equal to the initial values that were declared in ink.
- This makes the save file (potentially) much smaller assuming that
- at least a portion of the globals haven't changed. However, it
- can also take marginally longer to save in the case that the
- majority HAVE changed, since it has to compare all globals.
- It may also be useful to turn this off for testing worst case
- save timing.
-
-
-
-
- Callback for errors throughout both the ink runtime and compiler.
-
-
-
-
- Author errors will only ever come from the compiler so don't need to be handled
- by your Story error handler. The "Error" ErrorType is by far the most common
- for a runtime story error (rather than compiler error), though the Warning type
- is also possible.
-
-
-
- Generated by a "TODO" note in the ink source
-
-
- You should probably fix this, but it's not critical
-
-
- Critical error that can't be recovered from
-
-
-
diff --git a/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/ink_compiler.dll b/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/ink_compiler.dll
index 0402f32..139906d 100644
Binary files a/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/ink_compiler.dll and b/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/ink_compiler.dll differ
diff --git a/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/inklecate.deps.json b/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/inklecate.deps.json
deleted file mode 100644
index 1d9b3d8..0000000
--- a/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/inklecate.deps.json
+++ /dev/null
@@ -1,50 +0,0 @@
-{
- "runtimeTarget": {
- "name": ".NETCoreApp,Version=v3.1",
- "signature": ""
- },
- "compilationOptions": {},
- "targets": {
- ".NETCoreApp,Version=v3.1": {
- "inklecate/1.0.0": {
- "dependencies": {
- "Inkle.Ink.Engine": "0.9.0",
- "ink_compiler": "1.0.0"
- },
- "runtime": {
- "inklecate.dll": {}
- }
- },
- "Inkle.Ink.Engine/0.9.0": {
- "runtime": {
- "ink-engine-runtime.dll": {}
- }
- },
- "ink_compiler/1.0.0": {
- "dependencies": {
- "Inkle.Ink.Engine": "0.9.0"
- },
- "runtime": {
- "ink_compiler.dll": {}
- }
- }
- }
- },
- "libraries": {
- "inklecate/1.0.0": {
- "type": "project",
- "serviceable": false,
- "sha512": ""
- },
- "Inkle.Ink.Engine/0.9.0": {
- "type": "project",
- "serviceable": false,
- "sha512": ""
- },
- "ink_compiler/1.0.0": {
- "type": "project",
- "serviceable": false,
- "sha512": ""
- }
- }
-}
\ No newline at end of file
diff --git a/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/inklecate.dll b/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/inklecate.dll
index 4aaab54..7dd5264 100644
Binary files a/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/inklecate.dll and b/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/inklecate.dll differ
diff --git a/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/inklecate.exe b/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/inklecate.exe
index 96f1fda..ddd8eaf 100644
Binary files a/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/inklecate.exe and b/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/inklecate.exe differ
diff --git a/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/inklecate.runtimeconfig.dev.json b/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/inklecate.runtimeconfig.dev.json
deleted file mode 100644
index cd351b0..0000000
--- a/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/inklecate.runtimeconfig.dev.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "runtimeOptions": {
- "additionalProbingPaths": [
- "C:\\Users\\CaCrasto\\.dotnet\\store\\|arch|\\|tfm|",
- "C:\\Users\\CaCrasto\\.nuget\\packages",
- "C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder"
- ]
- }
-}
\ No newline at end of file
diff --git a/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/inklecate.runtimeconfig.json b/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/inklecate.runtimeconfig.json
index bc456d7..d54914b 100644
--- a/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/inklecate.runtimeconfig.json
+++ b/InkpotDemo/Plugins/Inkpot/ThirdParty/InkCommandLine/inklecate.runtimeconfig.json
@@ -1,9 +1,12 @@
{
"runtimeOptions": {
- "tfm": "netcoreapp3.1",
+ "tfm": "net5.0",
"framework": {
"name": "Microsoft.NETCore.App",
- "version": "3.1.0"
+ "version": "5.0.0"
+ },
+ "configProperties": {
+ "System.Reflection.Metadata.MetadataUpdater.IsSupported": false
}
}
}
\ No newline at end of file
diff --git a/README.md b/README.md
index baad6b9..8b5e1e3 100644
--- a/README.md
+++ b/README.md
@@ -40,11 +40,11 @@ The will create the InkpotStoryAsset, that contains the compiled JSON from sourc
![DragFile](Documentation/DragFile.png)
-### Note: .net 3.1 requirement
-If your Ink file fails to import, then you most likely do not have **.net 3.1** installed.
-Inkpot compiles your source when you import using the InkleCate compiler, & InkleCate requires the **.net 3.1** framework.
-To fix install the **.net 3.1** framework
-https://dotnet.microsoft.com/en-us/download/dotnet/3.1/runtime?cid=getdotnetcore
+### Note: .net 5.0 requirement
+If your Ink file fails to import, then you most likely do not have **.net 5.0** installed.
+Inkpot compiles your source when you import using the InkleCate compiler, & InkleCate requires the **.net 5.0** framework.
+To fix install the **.net 5.0** framework
+https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-5.0.17-windows-x64-installer
## Setting up auto reimport
Open editor, preferences.
@@ -251,7 +251,7 @@ Default is false.
---
# Testing InkPlusPlus
-We have 175 active tests in Inkpot that test the implementaion of the InkPlusPlus module.
+We have 180 active tests in Inkpot that test the implementaion of the InkPlusPlus module.
These can all be run through the *Session Frontend* within the Unreal editor.
To run the tests, first open the *Session Frontend* from Tools, Session Frontend.