Ink Stories
by PlatonymousAllows adding dialogues and events via the ink Scripting Language..
QUOTE
ink is a scripting language built around the idea of marking up pure-text with flow in order to produce interactive scripts.
At its most basic, it can be used to write a Choose Your Own-style story, or a branching dialogue tree. But its real strength is in writing dialogues with lots of options and lots of recombination of the flow.
ink offers several features to enable non-technical writers to branch often, and play out the consequences of those branches, in both minor and major ways, without fuss.
The script aims to be clean and logically ordered, so branching dialogue can be tested "by eye". The flow is described in a declarative fashion where possible.
It's also designed with redrafting in mind; so editing a flow should be fast.
Documentatation for ink: https://github.com/inkle/ink/blob/master/Documentation/WritingWithInk.md
Inky - A Tool for writing ink: https://www.inklestudios.com/ink/
For players: Just add this mod and any contentpack that requires it to the Mods folder.
Ink Stories does nothing on it's own, it just enables other mods to use ink.
Example:
Under Optionale Files you can download an example that adds dialogue to Emily on the 13th and 14th of spring.
After the dialogue with Emily it also adds new dialogue to Alex.
The latest example also changes the flowerfestival event, it requires the latest version of InkStories.
For Modders:
First, write your ink script (and test it in Inky). Save normaly as .ink or export to json, both work.
Important: This section changed with 1.2.0, the old way still works but now only one contentpack is required.
Create a content pack for Content Patcher:
manifest.json
{ <br /> "Name": "NAME OF YOUR MOD", <br /> "Author": "NAME OF THE AUTHOR", <br /> "Version": "1.0.0", <br /> "Description": "YOUR DESCRIPTION", <br /> "UniqueID": "Your.Unique.Id", <br /> "MinimumApiVersion": "3.18.0", <br /> "UpdateKeys": [ "Nexus:" ], <br /> "ContentPackFor": { <br /> "UniqueID": "Pathoschild.ContentPatcher" <br /> }, <br /> "Dependencies": [ <br /> { <br /> "UniqueID": "Platonymous.InkStories" <br /> } <br /> ] <br />} <br />
content.json
{ <br /> "Format": "1.28.0", <br /> "Changes": [ <br /> { <br /> "Action": "Load", <br /> "Target": "{{Platonymous.InkStories/Story:Your.Story.Id}}", <br /> "FromFile": "PATH/TO/FILE.ink" <br /> }, <br /> { <br /> "Action": "EditData", <br /> "Target": "Characters/Dialogue/NPCNAME", <br /> "Entries": { <br /> "SEASON_DAY": "INK Your.Story.Id" <br /> }, <br /> { <br /> "LogName": "InkStories Example Event/Festival", <br /> "Action": "EditData", <br /> "Target": "Data/Festivals/spring24", <br /> "Entries": { <br /> "mainEvent": "speak NPCNAME \"INK Your.Story.Id\"/pause 200/INKCALL Your.Story.Id YOUR PARAMETERS OR NOTHING" <br /> } <br /> } <br /> ] <br />} <br /> <br /> <br />(You can add as many stories as you want and use it in as many Dialogues as you want)
INK Your.Story.Id used as dialogue will start the Inkstory dialogue.
INKCALL Your.Story.Id calls the "EventSetup" function in your ink-code (if defined) and provides everything that follows as parameters.
(if you use INKCALL for the setup phase in your event (third entry) the function needs to return a string of the actual setup)
-- Tags:
In Addition to the functionality of ink as described in the documentation, the Ink Stories Mod handles specific tags as commands you can use:
# BREAK
Ends the dialogue after this line
# READD
Re-adds the story to the same NPC so the player can continue the story
# BR
Shortform of "# BREAK # READD"
# ADD [NPCNAME] [STORY ID]
Adds the specified story to the current dialogues of the npc (instead of the Story ID you can also put THIS, which will use the current story)
# ADDNEXT [NPCNAME] [STORY ID]
Same as # ADD, but the story is added the next day
# RESET
Resets the current story
# LOG [TYPE] [TEXT]
Logs the text to the console with the specified type (INFO,DEBUG,WARN,ERROR,ALERT,TRACE)
-- External functions:
Ink Stories also provides you with optional external functions that you can implement if you need them:
EXTERNAL ADD(npc,ink) <br />EXTERNAL ADDNEXT(npc,ink) <br />EXTERNAL RESET() <br />EXTERNAL LOG(text,type) <br />EXTERNAL SDVCHARS(text,npcname) <br />EXTERNAL HASMOD(id) <br />EXTERNAL SPEAKER() <br />EXTERNAL FRIENDSHIP(npcname, change) <br />EXTERNAL CHECK(conditions) <br />EXTERNAL COMMAND(command) <br />EXTERNAL ADDCOMMAND(command) <br />EXTERNAL SIN(num) <br />EXTERNAL COS(num) <br />EXTERNAL TAN(num) <br />EXTERNAL STEXT(id,key) <br />EXTERNAL SNUM(id,key) <br />EXTERNAL SCOUNT(key,change,isFixed) <br />EXTERNAL SETSTEXT(key,value,isFixed) <br />EXTERNAL SETSNUM(key,num,isFixed) <br />EXTERNAL CONTINUE() <br />EXTERNAL CPTEXT(key) <br />EXTERNAL CPNUM(key) <br />EXTERNAL CPBOOL(key) <br /> <br />=== function CONTINUE() === <br />~ return <br /> <br />=== function ADD(npc,ink) === <br />~ return <br /> <br />=== function ADDNEXT(npc,ink) === <br />~ return <br /> <br />=== function RESET() === <br />~ return <br /> <br />=== function LOG(text,type) === <br />~ return <br /> <br />=== function SDVCHARS(text,npcname) === <br />~ return text <br /> <br />=== function HASMOD(id) === <br />~ return true <br /> <br />=== function SPEAKER === <br />~ return "Emily" <br /> <br />=== function FRIENDSHIP(npcname, change) === <br />~ return 250 <br /> <br />=== function CHECK(conditions) === <br />~ return true <br /> <br />=== function COMMAND(command) === <br />~ return <br /> <br />=== function ADDCOMMAND(command) === <br />~ return <br /> <br />=== function SIN(num) === <br />~ return 0 <br /> <br />=== function COS(num) === <br />~ return 0 <br /> <br />=== function TAN(num) === <br />~ return 0 <br /> <br />=== function STEXT(id,key) === <br />~ return key <br /> <br />=== function SNUM(id,key) === <br />~ return 0 <br /> <br />=== function SCOUNT(key,change,isFixed) === <br />~ return change <br /> <br />=== function SETSTEXT(key,value,isFixed) === <br />~ return <br /> <br />=== function SETSNUM(key,num,isFixed) === <br />~ return <br /> <br />=== function CPTEXT(key) === <br />~ return "" <br /> <br />=== function CPNUM(key) === <br />~ return 0 <br /> <br />=== function CPBOOL(key) === <br />~ return false <br />
(the lower "=== function ... ===" declarations are fallbacks so the external functions don't throw erros in Inky, they aren't requiered ingame,
and in general you only need to include externals that you actually use in your script)
ADD, ADDNEXT, RESET, LOG
Do the same as their Tag-Counterparts
SDVCHARS
Parses the special dialouge characters in a text. Those are getting parsed by the game anyway, but with this you can get the results early and perform checks on them if you need to.
HASMOD
True if the player has the specified mod installed
SPEAKER
Returns the name of the current speaker
FRIENDSHIP
Changes the friendshippoints by the set amount and returns the total
CHECK
Checks eventconditions and returns true or false
COMMAND
Executes an event command
ADDCOMMAND
Adds an event command to the current event
SIN, COS, TAN
Math functions
STEXT, SETSTEXT, SNUM, SETSNUM
Gets or sets text or int values that can be accessed from other stories, setting isFixed to true will mean it's value doesn't get reset even if the story does.
You can only set values from the same story, but you can get values from any story by id (if it has set a value yet, otherwise returns "" or 0);
You can use "THIS" instead of the id if you want to access values from the same story.
SCOUNT
Accesses a SNUM value and adds the change value to it (use negative number for substraction), returns its new value.
CONTINUE
Steps to the next command in the ongoing event. Only needed from an INKCALL.
CPTEXT, CPNUM, CPBOOL
Let's you access data stored under your story Id throgh CP.
e.g.:
{ <br /> "Action": "EditData", <br /> "Target": "{{Platonymous.InkStories/Store:Your.Story.Id}", <br /> "Entries": { <br /> "your.key.1": "Anything" <br /> } <br />}
Then you can access that data for example with: {CPTEXT("your.key.1")}
The text stored will be auto parsed if using CPNUM (to float) or CPBOOL.
-- Internal functions:
Ink Stories calls internal functions at certain times if they are implemented in your ink story:
=== function ShouldShow(npcname,path,day,season,year) === <br />~ return true <br /> <br />=== function Fallback() === <br />~ return "My Fallback Dialogue" <br /> <br />=== function DayEnding(day,season,year) === <br />~ return true <br /> <br />=== function EventSetup(parameter1,parameter2,parameter3,...) === <br />~ return "farmer -30 30 0" <br />
ShouldShow(npcname,path,day,season,year) & Fallback()
Is checked before a story is displayed, if false the story will instead show the text returned in the Fallback function
DayEnding(day,season,year)
Is called on stories that have been loaded (shown at least once and not reset). The returnvalue (true of false) determines wether or not the story should be reset. The default is true, so any story not implementing the function will be reset at the end of the day.
EventSetup
Is called when using "INKCALL Your.Event.Id parameter 1 parameter2 parameter3 ..." as an event command.
You can have as many or as few parameters as you need, as long as you provide them when calling it.
The return value is only used if the call is made from the setup entry (3rd) in an event description.
If you have any questions or feature requests you can usually find me on the Stardew Valley Discord under the username Routine#8715
If you like my mods and want to support me, you can do so via Paypal or on Patreon.