initial commit

master
Leo Coogan 2 months ago
commit fb1b447a10
Signed by: lcoogan
GPG Key ID: 54DBD17B0D75ABB0

BIN
.DS_Store vendored

Binary file not shown.

@ -0,0 +1,4 @@
root = true
[*]
charset = utf-8

2
.gitattributes vendored

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

3
.gitignore vendored

@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/

@ -0,0 +1,61 @@
[gd_scene format=3 uid="uid://hac4l25a1g7v"]
[ext_resource type="Shader" uid="uid://cgfte1tvivngj" path="res://new_shader.gdshader" id="1_gbilv"]
[ext_resource type="Script" uid="uid://bfvl27u51p0lf" path="res://character_body_3d.gd" id="2_atw5s"]
[sub_resource type="BoxMesh" id="BoxMesh_4127l"]
[sub_resource type="CameraTexture" id="CameraTexture_ey7nh"]
[sub_resource type="Gradient" id="Gradient_atw5s"]
[sub_resource type="GradientTexture2D" id="GradientTexture2D_atw5s"]
gradient = SubResource("Gradient_atw5s")
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_atw5s"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_i0stl"]
render_priority = 0
shader = ExtResource("1_gbilv")
shader_parameter/width = 0.1
shader_parameter/height = 0.1
shader_parameter/gap_x = 0.02
shader_parameter/gap_y = 0.04
shader_parameter/sink = -0.5
shader_parameter/brick = SubResource("CameraTexture_ey7nh")
shader_parameter/wall = SubResource("NoiseTexture2D_atw5s")
shader_parameter/gap = SubResource("GradientTexture2D_atw5s")
shader_parameter/ratio = 0.5
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_yp6wb"]
radius = 0.12158203
height = 0.43318358
[sub_resource type="Environment" id="Environment_yp6wb"]
background_mode = 1
background_color = Color(1, 0.5254629, 0.55886984, 1)
ambient_light_color = Color(0.71889627, 0.67300373, 0.93241364, 1)
[node name="Node3D" type="Node3D" unique_id=367464733]
[node name="CSGMesh3D" type="CSGMesh3D" parent="." unique_id=2026853425]
transform = Transform3D(2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0)
use_collision = true
flip_faces = true
mesh = SubResource("BoxMesh_4127l")
material = SubResource("ShaderMaterial_i0stl")
[node name="OmniLight3D" type="OmniLight3D" parent="." unique_id=432683985]
[node name="CharacterBody3D" type="CharacterBody3D" parent="." unique_id=287273578]
script = ExtResource("2_atw5s")
[node name="Camera3D" type="Camera3D" parent="CharacterBody3D" unique_id=871682469]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.53903836)
current = true
[node name="CollisionShape3D" type="CollisionShape3D" parent="CharacterBody3D" unique_id=1029909573]
shape = SubResource("CapsuleShape3D_yp6wb")
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1564628371]
environment = SubResource("Environment_yp6wb")

BIN
addons/.DS_Store vendored

Binary file not shown.

@ -0,0 +1,38 @@
<Project>
<PropertyGroup>
<InkVersion>v1.1.1</InkVersion>
<!-- Directories -->
<GodotDotDirectory>$([System.IO.Path]::Combine($(GodotProjectDir),'.godot'))</GodotDotDirectory>
<GodotMonoDirectory>$([System.IO.Path]::Combine($(GodotDotDirectory),'mono'))</GodotMonoDirectory>
<UnzipDirectory>$([System.IO.Path]::Combine($(GodotDotDirectory),'ink'))</UnzipDirectory>
<InkVersionDirectory>$([System.IO.Path]::Combine($(GodotMonoDirectory),'ink-$(InkVersion)'))</InkVersionDirectory>
<!-- File -->
<InkZip>inklecate_windows.zip</InkZip>
<InkZipPath>$([System.IO.Path]::Combine($(GodotDotDirectory),$(InkZip)))</InkZipPath>
<InkUrl>https://github.com/inkle/ink/releases/download/$(InkVersion)/$(InkZip)</InkUrl>
<InkCompiler>ink_compiler.dll</InkCompiler>
<InkCompilerPath>$([System.IO.Path]::Combine($(InkVersionDirectory),$(InkCompiler)))</InkCompilerPath>
<InkRuntime>ink-engine-runtime.dll</InkRuntime>
<InkRuntimePath>$([System.IO.Path]::Combine($(InkVersionDirectory),$(InkRuntime)))</InkRuntimePath>
</PropertyGroup>
<Target Name="DownloadInkDependency" AfterTargets="BeforeBuild" Condition="!Exists('$(InkCompilerPath)') Or !Exists('$(InkRuntimePath)')">
<!-- Download and unzip -->
<DownloadFile SourceUrl="$(InkUrl)" DestinationFolder="$(GodotDotDirectory)" />
<Unzip SourceFiles="$(InkZipPath)" DestinationFolder="$(UnzipDirectory)" />
<!-- Extract what we want -->
<ItemGroup>
<FilesToMove Include="$([System.IO.Path]::Combine($(UnzipDirectory),$(InkCompiler)))" />
<FilesToMove Include="$([System.IO.Path]::Combine($(UnzipDirectory),$(InkRuntime)))" />
</ItemGroup>
<Move SourceFiles="@(FilesToMove)" DestinationFolder="$(InkVersionDirectory)" />
<!-- Cleanup -->
<Delete Files="$(InkZipPath)" />
<RemoveDir Directories="$(UnzipDirectory)" />
</Target>
<ItemGroup>
<Reference Include="$(InkCompilerPath)" Condition="'$(Configuration)' == 'Debug'" />
<Reference Include="$(InkRuntimePath)" />
</ItemGroup>
</Project>

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7.3136863 1.0451519c-1.1300539.1561083-2.1797857.4941546-3.1354935 1.1410596-.7783707.5268672-1.4612656 1.2265006-1.9842642 2.0139066-.4973947.7488583-.8815365 1.5944212-1.0603099 2.4819785-.95527094 4.7426384 3.3693827 8.8795764 7.9359446 8.2552864.9504617-.129934 1.8803567-.480581 2.6965237-.988244 3.708275-2.306571 4.338996-7.6732378 1.311294-10.8284044-.862632-.8989506-2.009172-1.5183411-3.1925886-1.8473904-.8120607-.2257936-1.7322548-.34407278-2.5711061-.2281923m5.2049197 3.4095282c.46704.363567.831781.9839945 1.065826 1.527371.318783.7401157.640439 1.6601785.553744 2.4819779-.175511 1.663713-1.809807 3.201643-2.999186 4.205327-.794997.670877-1.8195886 1.458842-2.8846556 1.574906-1.0595319.115463-2.119995-.506003-2.9473638-1.107716-.9414982-.684716-1.7807878-1.511478-2.4020398-2.508741-.2483083-.398596-.5356887-.9868407-.3479044-1.4627967.2247735-.56971 1.3074802-1.0017929 1.872006-1.0710849.2424849-.029766.595381-.0485.7412927.1937196.2551389.4235385-.1340085.9985353-.1083388 1.4491962.050084.8792768.4947129 1.7293578 1.00568 2.4183378.1414647.190746.5055731.837878.7871155.787158.1692126-.03049.1222367-.290441.10892-.405315-.051947-.448112-.1258299-.891399-.1989206-1.33645-.1766914-1.075882-.5697899-2.4158386-.281081-3.5002263.1756643-.659794.8205368-.3899122 1.0788423-.8937651.2031841-.3963332-.2225521-.8682646-.2488562-1.2700106.6383703.1869895 1.2930544.7408713 1.6904095 1.2730551.1582256.2119133.3702727.5401447.231145.8135268-.2186648.4296693-1.4769713.6919585-1.1614379 1.410845.3933613.8962013 1.655226-.2672517 2.2404251-.021508.292853.1229782.08341.4680216-.0042.6607119-.188638.4148282-.282515 1.1481662.270435 1.3574162.571502.216271 1.019879-.972216 1.173052-1.3574162.395401-.9943516.716088-2.1033072.762632-3.1820233.02626-.6084688-.228341-1.4782825.0024-2.0364951z" fill="#cf68ea"/></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://op2d8aagi35f"
path="res://.godot/imported/GodotInk.svg-0c8ec83d82b5d336ec3fed92ffcbc8bc.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/GodotInk/GodotInk.svg"
dest_files=["res://.godot/imported/GodotInk.svg-0c8ec83d82b5d336ec3fed92ffcbc8bc.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 - 2024 Paul Joannon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,34 @@
# GodotInk
An [ink](https://github.com/inkle/ink) integration for [Godot Engine 4](https://github.com/godotengine/godot).
If you use and like this project, please consider buying me a coffee.
[![ko-fi](https://img.shields.io/badge/support_me_on_ko--fi-ff5e5b?style=for-the-badge&logo=kofi&logoColor=f5f5f5)](https://ko-fi.com/E1E53SKZF)
## Usage
You'll find a quick-start guide, and the general documentation on the project's
[wiki](https://github.com/paulloz/godot-ink/wiki).
### Installation
The following instructions assume that you have a working Godot and .NET installation. If not, please refer to the
official [engine documentation](https://docs.godotengine.org/).
1. [Download](https://github.com/paulloz/godot-ink/releases/latest) and extract the code at the root of your project.
You should see a new `addons/GodotInk/` folder in your project's directory.
1. Add the following line in your `.csproj` file (before the closing `</Project>` tag).
```xml
<Import Project="addons\GodotInk\GodotInk.props" />
```
If you don't have a `.csproj` file, click the **Create C# Solution** button in the editor's
**Project/Tools** menu.
1. Build your project once.
1. Enable **GodotInk** in your project settings.
## License
*GodotInk* is released under MIT license (see the [LICENSE](/LICENSE) file for more information).

@ -0,0 +1,40 @@
#nullable enable
using System.ComponentModel;
namespace GodotInk;
public partial class InkChoice
{
#pragma warning disable IDE0022
/// <summary>
/// This method is here for GDScript compatibility. Use <see cref="CanContinue" /> instead.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public string GetText() => Text;
/// <summary>
/// This method is here for GDScript compatibility. Use <see cref="CanContinue" /> instead.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public string GetPathStringOnChoice() => PathStringOnChoice;
/// <summary>
/// This method is here for GDScript compatibility. Use <see cref="CanContinue" /> instead.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public string GetSourcePath() => SourcePath;
/// <summary>
/// This method is here for GDScript compatibility. Use <see cref="CanContinue" /> instead.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public int GetIndex() => Index;
/// <summary>
/// This method is here for GDScript compatibility. Use <see cref="CanContinue" /> instead.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public Godot.Collections.Array<string> GetTags() => new(Tags);
#pragma warning restore IDE0022
}

@ -0,0 +1,31 @@
#nullable enable
using Godot;
using System.Collections.Generic;
namespace GodotInk;
[Tool]
#if GODOT4_1_OR_GREATER
[GlobalClass]
#endif
public partial class InkChoice : RefCounted
{
public string Text => inner.text;
public string PathStringOnChoice => inner.pathStringOnChoice;
public string SourcePath => inner.sourcePath;
public int Index => inner.index;
public IReadOnlyList<string> Tags => inner.tags ?? (IReadOnlyList<string>)System.Array.Empty<string>();
private readonly Ink.Runtime.Choice inner;
private InkChoice()
{
inner = new Ink.Runtime.Choice();
}
public InkChoice(Ink.Runtime.Choice inner)
{
this.inner = inner;
}
}

@ -0,0 +1,254 @@
#if TOOLS
#nullable enable
using Godot;
using System;
using System.Linq;
namespace GodotInk;
[Tool]
public partial class InkDock : VBoxContainer
{
private static readonly StringName CHOICE_INDEX_META = "Index";
private Button loadButton = null!;
private Button startButton = null!;
private Button stopButton = null!;
private Button clearButton = null!;
private Label storyNameLabel = null!;
private VBoxContainer storyText = null!;
private VBoxContainer storyChoices = null!;
private ScrollContainer scroll = null!;
private EditorFileDialog fileDialog = null!;
private string storyPath = "";
private InkStory? story;
private bool storyStarted;
private string backupSave = "";
public override void _Ready()
{
base._Ready();
CustomMinimumSize = new Vector2(0.0f, 228.0f);
// Initialize file dialog.
fileDialog = new()
{
FileMode = EditorFileDialog.FileModeEnum.OpenFile,
Access = EditorFileDialog.AccessEnum.Resources,
};
fileDialog.AddFilter("*.ink", "Ink Stories");
fileDialog.FileSelected += LoadStory;
AddChild(fileDialog);
// Initialize top.
loadButton = GetNode<Button>("Container/Left/Top/LoadButton");
storyNameLabel = GetNode<Label>("Container/Left/Top/Label");
startButton = GetNode<Button>("Container/Left/Top/StartButton");
stopButton = GetNode<Button>("Container/Left/Top/StopButton");
clearButton = GetNode<Button>("Container/Left/Top/ClearButton");
// Connect UI events.
loadButton.Pressed += () => fileDialog.PopupCenteredClamped(new Vector2I(1050, 700), 0.8f);
startButton.Pressed += StartStory;
stopButton.Pressed += StopStory;
clearButton.Pressed += () => ClearStory(false);
// Initialize bottom.
storyText = GetNode<VBoxContainer>("Container/Left/Scroll/Margin/StoryText");
storyChoices = GetNode<VBoxContainer>("Container/Right/StoryChoices");
scroll = GetNode<ScrollContainer>("Container/Left/Scroll");
// Set icons.
loadButton.Icon = GetThemeIcon("Load", "EditorIcons");
startButton.Icon = GetThemeIcon("Play", "EditorIcons");
stopButton.Icon = GetThemeIcon("Stop", "EditorIcons");
clearButton.Icon = GetThemeIcon("Clear", "EditorIcons");
// Update UI.
UpdateTop();
}
private void UpdateTop()
{
bool hasStory = story != null;
storyNameLabel.Text = hasStory ? storyPath : string.Empty;
startButton.Visible = hasStory && !storyStarted;
stopButton.Visible = hasStory && storyStarted;
clearButton.Visible = hasStory;
clearButton.Disabled = storyText.GetChildCount() <= 0;
storyChoices.GetParent<Control>().Visible = hasStory;
}
private void LoadStory(string path)
{
try
{
storyPath = path;
story = ResourceLoader.Load<InkStory>(path, null, ResourceLoader.CacheMode.Ignore);
UpdateTop();
}
catch (InvalidCastException)
{
GD.PrintErr($"{path} is not a valid ink story. "
+ "Please make sure it was imported with `is_main_file` set to `true`.");
}
}
private void StartStory()
{
if (story == null) return;
storyStarted = true;
ContinueStory();
UpdateTop();
}
private void StopStory()
{
StopStory(false);
}
private void StopStory(bool setStoryToNull)
{
storyStarted = false;
try
{
story?.ResetState();
}
catch (ObjectDisposedException)
{
story = null;
}
if (setStoryToNull)
story = null;
ClearStory(true);
}
private void ClearStory(bool clearChoices)
{
RemoveAllStoryContent();
if (clearChoices)
RemoveAllChoices();
UpdateTop();
}
private void ContinueStory()
{
if (story == null) return;
if (!story.CanContinue) return;
string currentText = story.ContinueMaximally().Trim();
if (currentText.Length > 0)
{
Label newLine = new()
{
AutowrapMode = TextServer.AutowrapMode.WordSmart,
Text = currentText,
};
AddToStory(newLine);
if (story.CurrentTags.Count > 0)
{
newLine = new Label()
{
AutowrapMode = TextServer.AutowrapMode.WordSmart,
HorizontalAlignment = HorizontalAlignment.Center,
Text = $"# {string.Join(", ", story.CurrentTags)}",
};
newLine.AddThemeColorOverride("font_color", GetThemeColor("font_color_disabled", "Button"));
AddToStory(newLine);
}
}
foreach (InkChoice choice in story.CurrentChoices)
{
Button button = new() { Text = choice.Text };
button.SetMeta(CHOICE_INDEX_META, choice.Index);
button.Connect(Button.SignalName.Pressed, Callable.From(ClickChoice));
storyChoices.AddChild(button);
}
backupSave = storyStarted ? story.SaveState() : "";
}
private void ClickChoice()
{
if (storyChoices.GetChildren().OfType<Button>().First(button => button.ButtonPressed) is not Button button) return;
if (!button.HasMeta(CHOICE_INDEX_META)) return;
try
{
ClickChoice(button.GetMeta(CHOICE_INDEX_META).As<int>());
}
catch
{
story?.LoadState(backupSave);
try
{
ClickChoice(button.GetMeta(CHOICE_INDEX_META).As<int>());
}
catch
{
StopStory(true);
}
}
}
private void ClickChoice(int idx)
{
if (story == null) return;
story.ChooseChoiceIndex(idx);
RemoveAllChoices();
AddToStory(new HSeparator());
ContinueStory();
}
private async void AddToStory(CanvasItem item)
{
storyText.AddChild(item);
await ToSignal(GetTree(), "process_frame");
await ToSignal(GetTree(), "process_frame");
scroll.ScrollVertical = (int)scroll.GetVScrollBar().MaxValue;
}
private void RemoveAllStoryContent()
{
foreach (Node n in storyText.GetChildren())
n.QueueFree();
}
private void RemoveAllChoices()
{
foreach (Node n in storyChoices.GetChildren().OfType<Button>())
n.QueueFree();
}
public void WhenInkResourceReimported()
{
StopStory(true);
}
}
#endif

@ -0,0 +1 @@
uid://cqdq3u7vt365x

@ -0,0 +1,99 @@
[gd_scene load_steps=4 format=3 uid="uid://bi0phc77n67u"]
[ext_resource type="Script" path="res://addons/GodotInk/Src/InkDock.cs" id="1_c6vi7"]
[sub_resource type="Image" id="Image_s0a34"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_a3xdy"]
image = SubResource("Image_s0a34")
[node name="InkDock" type="VBoxContainer"]
custom_minimum_size = Vector2(0, 228)
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1_c6vi7")
[node name="Container" type="HSplitContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Left" type="VBoxContainer" parent="Container"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Top" type="HBoxContainer" parent="Container/Left"]
layout_mode = 2
[node name="LoadButton" type="Button" parent="Container/Left/Top"]
layout_mode = 2
text = "Load..."
icon = SubResource("ImageTexture_a3xdy")
[node name="Label" type="Label" parent="Container/Left/Top"]
layout_mode = 2
size_flags_horizontal = 3
[node name="StartButton" type="Button" parent="Container/Left/Top"]
visible = false
layout_mode = 2
text = "Start"
icon = SubResource("ImageTexture_a3xdy")
[node name="StopButton" type="Button" parent="Container/Left/Top"]
visible = false
layout_mode = 2
text = "Stop"
icon = SubResource("ImageTexture_a3xdy")
[node name="ClearButton" type="Button" parent="Container/Left/Top"]
visible = false
layout_mode = 2
disabled = true
text = "Clear"
icon = SubResource("ImageTexture_a3xdy")
[node name="Scroll" type="ScrollContainer" parent="Container/Left"]
layout_mode = 2
size_flags_horizontal = 11
size_flags_vertical = 3
[node name="Margin" type="MarginContainer" parent="Container/Left/Scroll"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="StoryText" type="VBoxContainer" parent="Container/Left/Scroll/Margin"]
layout_mode = 2
size_flags_horizontal = 11
size_flags_vertical = 3
[node name="Right" type="VBoxContainer" parent="Container"]
visible = false
custom_minimum_size = Vector2(228, 0)
layout_mode = 2
size_flags_vertical = 3
[node name="Top" type="HBoxContainer" parent="Container/Right"]
layout_mode = 2
[node name="Label" type="Label" parent="Container/Right/Top"]
layout_mode = 2
size_flags_horizontal = 3
text = "Choices"
[node name="StoryChoices" type="VBoxContainer" parent="Container/Right"]
layout_mode = 2

@ -0,0 +1,54 @@
#nullable enable
using Godot;
namespace GodotInk;
#if GODOT4_1_OR_GREATER
[GlobalClass]
#endif
public partial class InkList : RefCounted
{
private readonly Ink.Runtime.InkList inner;
public InkList(Ink.Runtime.InkList inner)
{
this.inner = inner;
}
public InkList(InkList otherList)
: this(new Ink.Runtime.InkList(otherList.inner))
{
}
#pragma warning disable IDE0022
public InkList Inverse => new(inner.inverse);
public InkList All => new(inner.all);
public void AddItem(string itemName) => inner.AddItem(itemName);
public bool Contains(string listItemName) => inner.Contains(listItemName);
public bool Contains(InkList otherList) => inner.Contains(otherList.inner);
public bool ContainsItemNamed(string itemName) => inner.ContainsItemNamed(itemName);
public override bool Equals(object? other) => inner.Equals(other);
public override int GetHashCode() => inner.GetHashCode();
public override string ToString() => inner.ToString();
public bool GreaterThan(InkList otherList) => inner.GreaterThan(otherList.inner);
public bool GreaterThanOrEquals(InkList otherList) => inner.GreaterThanOrEquals(otherList.inner);
public bool LessThan(InkList otherList) => inner.LessThan(otherList.inner);
public bool LessThanOrEquals(InkList otherList) => inner.LessThanOrEquals(otherList.inner);
public bool HashIntersection(InkList otherList) => inner.HasIntersection(otherList.inner);
public InkList Intersect(InkList otherList) => new(inner.Intersect(otherList.inner));
public InkList Union(InkList otherList) => new(inner.Union(otherList.inner));
public InkList Without(InkList listToRemove) => new(inner.Without(listToRemove.inner));
public InkList MaxAsList() => new(inner.MaxAsList());
public InkList MinAsList() => new(inner.MinAsList());
public void SetInitialOriginName(string initialOriginName) => inner.SetInitialOriginName(initialOriginName);
public void SetInitialOriginNames(string[] initialOriginNames) => inner.SetInitialOriginNames(new(initialOriginNames));
#pragma warning restore IDE0022
}

@ -0,0 +1 @@
uid://dopg4y7h1e8vj

@ -0,0 +1,84 @@
#nullable enable
using Godot;
using System;
using System.ComponentModel;
using System.Linq;
using VariantGArray = Godot.Collections.Array<Godot.Variant>;
namespace GodotInk;
public partial class InkStory
{
#pragma warning disable IDE0022
/// <summary>
/// This method is here for GDScript compatibility. Use <see cref="CanContinue" /> instead.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool GetCanContinue() => CanContinue;
/// <summary>
/// This method is here for GDScript compatibility. Use <see cref="CurrentChoices" /> instead.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public Godot.Collections.Array<InkChoice> GetCurrentChoices() => new(CurrentChoices);
/// <summary>
/// This method is here for GDScript compatibility. Use <see cref="CurrentTags" /> instead.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public Godot.Collections.Array<string> GetCurrentTags() => new(CurrentTags);
/// <summary>
/// This method is here for GDScript compatibility. Use <see cref="CurrentText" /> instead.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public string GetCurrentText() => CurrentText;
#pragma warning restore IDE0022
[EditorBrowsable(EditorBrowsableState.Never)]
public void ChoosePathString(string path)
{
ChoosePathString(path, true, Array.Empty<Variant>());
}
[EditorBrowsable(EditorBrowsableState.Never)]
public void ChoosePathString(string path, bool resetCallstack)
{
ChoosePathString(path, resetCallstack, Array.Empty<Variant>());
}
[EditorBrowsable(EditorBrowsableState.Never)]
public void ChoosePathString(string path, VariantGArray arguments)
{
ChoosePathString(path, true, arguments.ToArray());
}
[EditorBrowsable(EditorBrowsableState.Never)]
public void ChoosePathString(string path, bool resetCallstack, VariantGArray arguments)
{
ChoosePathString(path, resetCallstack, arguments.ToArray());
}
/// <summary>
/// Evaluates a function defined in ink.
/// </summary>
/// <param name="functionName">The name of the function as declared in ink.</param>
/// <returns>
/// The return value as returned from the ink function with `~ return myValue`, or a nil
/// variant if nothing is returned.
/// </returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public Variant EvaluateFunction(string functionName)
{
return EvaluateFunction(functionName, Array.Empty<Variant>());
}
[EditorBrowsable(EditorBrowsableState.Never)]
public void BindExternalFunction(string funcName, Callable callable)
{
BindExternalFunction(funcName, callable, false);
}
}

@ -0,0 +1,698 @@
#nullable enable
using Godot;
using System;
using System.Collections.Generic;
using static GodotInk.MarshalUtils;
using PropertyList = Godot.Collections.Array<Godot.Collections.Dictionary>;
namespace GodotInk;
[Tool]
#if GODOT4_1_OR_GREATER
[GlobalClass, Icon("../GodotInk.svg")]
#endif
public partial class InkStory : Resource
{
[Signal]
public delegate void ContinuedEventHandler();
[Signal]
public delegate void MadeChoiceEventHandler(InkChoice choice);
protected virtual string RawStory
{
get => rawStory;
set
{
rawStory = value;
#if TOOLS
// There's really no need to instantiate Ink.Runtime in the editor itself.
// if (Engine.IsEditorHint()) return; <- Commenting for now, because it prevents
// the InkDock from running stories.
#endif
InitializeRuntimeStory();
}
}
private string rawStory = string.Empty;
private Ink.Runtime.Story runtimeStory = null!;
private readonly Dictionary<string, HashSet<Callable>> observers = new();
private readonly Dictionary<string, Ink.Runtime.Story.VariableObserver> internalObservers = new();
public static InkStory Create(string rawStory)
{
return new InkStory()
{
rawStory = rawStory
};
}
private void InitializeRuntimeStory()
{
if (runtimeStory != null)
{
runtimeStory.onDidContinue -= OnContinued;
runtimeStory.onMakeChoice -= OnMadeChoice;
}
runtimeStory = new Ink.Runtime.Story(rawStory);
runtimeStory.onDidContinue += OnContinued;
runtimeStory.onMakeChoice += OnMadeChoice;
}
public string CurrentText => runtimeStory.currentText;
public IReadOnlyList<InkChoice> CurrentChoices => ToVariants(runtimeStory.currentChoices);
public IReadOnlyList<string> CurrentTags => runtimeStory.currentTags;
public bool HasWarning => runtimeStory.hasWarning;
public IReadOnlyList<string> CurrentWarnings => runtimeStory.currentWarnings;
public bool HasError => runtimeStory.hasError;
public IReadOnlyList<string> CurrentErrors => runtimeStory.currentErrors;
/// <summary>
/// Check whether more content is available if you were to call <c>Continue()</c> - i.e.
/// are we mid story rather than at a choice point or at the end.
/// </summary>
public bool CanContinue => runtimeStory.canContinue;
/// <summary>
/// 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 <c>canContinue</c> before calling this function.
/// </summary>
/// <returns>The line of text content.</returns>
public string Continue()
{
return runtimeStory.Continue();
}
/// <summary>
/// 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.
/// </summary>
/// <returns>The resulting text evaluated by the ink engine, concatenated together.</returns>
public string ContinueMaximally()
{
return runtimeStory.ContinueMaximally();
}
/// <summary>
/// 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.
/// </summary>
/// <param name="choiceIdx">The index of the choice to choose.</param>
public void ChooseChoiceIndex(int choiceIdx)
{
runtimeStory.ChooseChoiceIndex(choiceIdx);
}
public void ChoosePathString(string path, bool resetCallstack = true, params Variant[] arguments)
{
runtimeStory.ChoosePathString(path, resetCallstack, FromVariants(arguments));
}
/// <summary>
/// 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.
/// </summary>
public void ResetCallstack()
{
runtimeStory.ResetCallstack();
}
/// <summary>
/// Reset the Story back to its initial state as it was when it was
/// first constructed.
/// </summary>
public void ResetState()
{
runtimeStory.ResetState();
}
/// <summary>
/// Get any global tags associated with the story. These are defined as
/// hash tags defined at the very top of the story.
/// </summary>
public IReadOnlyList<string> GlobalTags => runtimeStory.globalTags;
/// <summary>
/// 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.
/// </summary>
/// <param name="path">The path of the knot or stitch, in the form "knot" or "knot.stitch".</param>
/// <returns>The list of tags.</returns>
public IReadOnlyList<string> TagsForContentAtPath(string path)
{
return runtimeStory.TagsForContentAtPath(path);
}
public string CurrentFlowName => runtimeStory.currentFlowName;
public bool CurrentFlowIsDefaultFlow => runtimeStory.currentFlowIsDefaultFlow;
public IReadOnlyList<string> AliveFlowNames => runtimeStory.aliveFlowNames;
/// <summary>
///
/// </summary>
/// <param name="flowName"></param>
public void RemoveFlow(string flowName)
{
runtimeStory.RemoveFlow(flowName);
}
/// <summary>
///
/// </summary>
/// <param name="flowName"></param>
public void SwitchFlow(string flowName)
{
runtimeStory.SwitchFlow(flowName);
}
/// <summary>
///
/// </summary>
public void SwitchToDefaultFlow()
{
runtimeStory.SwitchToDefaultFlow();
}
public int VisitCountAtPathString(string pathString)
{
return runtimeStory.state.VisitCountAtPathString(pathString);
}
public Variant FetchVariable(string variableName)
{
return ToVariant(runtimeStory.variablesState[variableName]);
}
public T FetchVariable<[MustBeVariant] T>(string variableName)
{
return FetchVariable(variableName).As<T>();
}
public void StoreVariable(string variableName, Variant value)
{
runtimeStory.variablesState[variableName] = FromVariant(value);
}
public void StoreVariable<[MustBeVariant] T>(string variableName, T value)
{
StoreVariable(variableName, Variant.From(value));
}
/// <summary>
///
/// </summary>
/// <param name="variableName"></param>
/// <param name="observer"></param>
public void ObserveVariable(string variableName, Callable observer)
{
if (!internalObservers.ContainsKey(variableName))
{
Ink.Runtime.Story.VariableObserver internalObserver = BuildObserver();
runtimeStory.ObserveVariable(variableName, internalObserver);
internalObservers[variableName] = internalObserver;
}
if (observers.ContainsKey(variableName))
_ = observers[variableName].Add(observer);
else
observers[variableName] = new() { observer };
}
/// <summary>
///
/// </summary>
/// <param name="variableNames"></param>
/// <param name="observer"></param>
public void ObserveVariable(string[] variableNames, Callable observer)
{
foreach (string variableName in variableNames)
ObserveVariable(variableName, observer);
}
private Ink.Runtime.Story.VariableObserver BuildObserver()
{
return delegate (string name, object? value)
{
if (!observers.TryGetValue(name, out var callables)) return;
Variant variant = ToVariant(value);
foreach (Callable callable in callables)
_ = callable.Call(name, variant);
};
}
/// <summary>
///
/// </summary>
/// <param name="callable"></param>
public void RemoveVariableObserver(Callable callable)
{
foreach (string variableName in observers.Keys)
RemoveVariableObserver(callable, variableName);
}
/// <summary>
///
/// </summary>
/// <param name="specificVariableName"></param>
public void RemoveVariableObserver(string specificVariableName)
{
runtimeStory.RemoveVariableObserver(null, specificVariableName);
_ = internalObservers.Remove(specificVariableName);
_ = observers.Remove(specificVariableName);
}
/// <summary>
///
/// </summary>
/// <param name="callable"></param>
/// <param name="specificVariableName"></param>
public void RemoveVariableObserver(Callable callable, string specificVariableName)
{
var callables = observers[specificVariableName];
if (!callables.Contains(callable)) return;
_ = callables.Remove(callable);
if (callables.Count > 0) return;
runtimeStory.RemoveVariableObserver(null, specificVariableName);
_ = internalObservers.Remove(specificVariableName);
}
/// <summary>
/// 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 play mode, when it's not possible to write a client-side C# external
/// function, but you don't want it to fail to run.
/// </summary>
public bool AllowExternalFunctionFallbacks => runtimeStory.allowExternalFunctionFallbacks;
/// <summary>
/// Checks if a function exists.
/// </summary>
/// <param name="functionName">The name of the function as declared in ink.</param>
/// <returns>True if the function exists, else false.</returns>
public bool HasFunction(string functionName)
{
return runtimeStory.HasFunction(functionName);
}
/// <summary>
/// Evaluates a function defined in ink.
/// </summary>
/// <param name="functionName">The name of the function as declared in ink.</param>
/// <param name="arguments">
/// 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!
/// </param>
/// <returns>
/// The return value as returned from the ink function with `~ return myValue`, or a nil
/// variant if nothing is returned.
/// </returns>
public Variant EvaluateFunction(string functionName, params Variant[] arguments)
{
object? result = runtimeStory.EvaluateFunction(functionName, FromVariants(arguments));
return ToVariant(result);
}
/// <summary>
/// Evaluates a function defined in ink.
/// </summary>
/// <param name="functionName">The name of the function as declared in ink.</param>
/// <param name="arguments">
/// 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!
/// </param>
/// <returns>
/// The return value as returned from the ink function with `~ return myValue`, or a nil
/// variant if nothing is returned.
/// </returns>
public Variant EvaluateFunction(string functionName, Godot.Collections.Array<Variant> arguments)
{
object? result = runtimeStory.EvaluateFunction(functionName, FromVariants(arguments));
return ToVariant(result);
}
/// <summary>
/// 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`.
/// </summary>
/// <param name="functionName">The name of the function as declared in ink.</param>
/// <param name="textOutput">The text content produced by the function via normal ink, if any.</param>
/// <param name="arguments">
/// 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!
/// </param>
/// <returns>
/// The return value as returned from the ink function with `~ return myValue`, or a nil
/// variant if nothing is returned.
/// </returns>
public Variant EvaluateFunction(string functionName, out string textOutput, params Variant[] arguments)
{
object? result = runtimeStory.EvaluateFunction(functionName, out textOutput, FromVariants(arguments));
return ToVariant(result);
}
/// <summary>
/// Bind a C# function to an ink EXTERNAL function declaration.
/// </summary>
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
/// <param name="callable">The Godot Callable to bind.</param>
/// <param name="lookaheadSafe">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.</param>
public void BindExternalFunction(string funcName, Callable callable, bool lookaheadSafe = false)
{
runtimeStory.BindExternalFunctionGeneral(funcName, trampoline, lookaheadSafe);
object? trampoline(object?[] arguments) => FromVariant(callable.Call(ToVariants(arguments)));
}
/// <summary>
/// Bind a C# function to an ink EXTERNAL function declaration.
/// </summary>
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
/// <param name="func">The C# function to bind.</param>
/// <param name="lookaheadSafe">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.</param>
public void BindExternalFunction(string funcName, Func<Variant> func, bool lookaheadSafe = false)
{
runtimeStory.BindExternalFunction(funcName, trampoline, lookaheadSafe);
object? trampoline() => FromVariant(func.Invoke());
}
/// <summary>
/// Bind a C# function to an ink EXTERNAL function declaration.
/// </summary>
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
/// <param name="func">The C# function to bind.</param>
/// <param name="lookaheadSafe">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.</param>
public void BindExternalFunction<T>(string funcName, Func<T, Variant> func, bool lookaheadSafe = false)
{
runtimeStory.BindExternalFunction(funcName, (Func<T, object?>)trampoline, lookaheadSafe);
object? trampoline(T a) => FromVariant(func.Invoke(a));
}
/// <summary>
/// Bind a C# function to an ink EXTERNAL function declaration.
/// </summary>
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
/// <param name="func">The C# function to bind.</param>
/// <param name="lookaheadSafe">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.</param>
public void BindExternalFunction<T1, T2>(string funcName, Func<T1, T2, Variant> func, bool lookaheadSafe = false)
{
runtimeStory.BindExternalFunction(funcName, (Func<T1, T2, object?>)trampoline, lookaheadSafe);
object? trampoline(T1 a, T2 b) => FromVariant(func.Invoke(a, b));
}
/// <summary>
/// Bind a C# function to an ink EXTERNAL function declaration.
/// </summary>
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
/// <param name="func">The C# function to bind.</param>
/// <param name="lookaheadSafe">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.</param>
public void BindExternalFunction<T1, T2, T3>(string funcName, Func<T1, T2, T3, Variant> func, bool lookaheadSafe = false)
{
runtimeStory.BindExternalFunction(funcName, (Func<T1, T2, T3, object?>)trampoline, lookaheadSafe);
object? trampoline(T1 a, T2 b, T3 c) => FromVariant(func.Invoke(a, b, c));
}
/// <summary>
/// Bind a C# function to an ink EXTERNAL function declaration.
/// </summary>
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
/// <param name="func">The C# function to bind.</param>
/// <param name="lookaheadSafe">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.</param>
public void BindExternalFunction<T1, T2, T3, T4>(string funcName, Func<T1, T2, T3, T4, Variant> func, bool lookaheadSafe = false)
{
runtimeStory.BindExternalFunction(funcName, (Func<T1, T2, T3, T4, object?>)trampoline, lookaheadSafe);
object? trampoline(T1 a, T2 b, T3 c, T4 d) => FromVariant(func.Invoke(a, b, c, d));
}
/// <summary>
/// Bind a C# Action to an ink EXTERNAL function declaration.
/// </summary>
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
/// <param name="action">The C# action to bind.</param>
/// <param name="lookaheadSafe">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.</param>
public void BindExternalFunction(string funcName, Action action, bool lookaheadSafe = false)
{
runtimeStory.BindExternalFunction(funcName, action, lookaheadSafe);
}
/// <summary>
/// Bind a C# Action to an ink EXTERNAL function declaration.
/// </summary>
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
/// <param name="action">The C# action to bind.</param>
/// <param name="lookaheadSafe">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.</param>
public void BindExternalFunction<T>(string funcName, Action<T> action, bool lookaheadSafe = false)
{
runtimeStory.BindExternalFunction(funcName, action, lookaheadSafe);
}
/// <summary>
/// Bind a C# Action to an ink EXTERNAL function declaration.
/// </summary>
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
/// <param name="action">The C# action to bind.</param>
/// <param name="lookaheadSafe">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.</param>
public void BindExternalFunction<T1, T2>(string funcName, Action<T1, T2> action, bool lookaheadSafe = false)
{
runtimeStory.BindExternalFunction(funcName, action, lookaheadSafe);
}
/// <summary>
/// Bind a C# Action to an ink EXTERNAL function declaration.
/// </summary>
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
/// <param name="action">The C# action to bind.</param>
/// <param name="lookaheadSafe">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.</param>
public void BindExternalFunction<T1, T2, T3>(string funcName, Action<T1, T2, T3> action, bool lookaheadSafe = false)
{
runtimeStory.BindExternalFunction(funcName, action, lookaheadSafe);
}
/// <summary>
/// Bind a C# Action to an ink EXTERNAL function declaration.
/// </summary>
/// <param name="funcName">EXTERNAL ink function name to bind to.</param>
/// <param name="action">The C# action to bind.</param>
/// <param name="lookaheadSafe">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.</param>
public void BindExternalFunction<T1, T2, T3, T4>(string funcName, Action<T1, T2, T3, T4> action, bool lookaheadSafe = false)
{
runtimeStory.BindExternalFunction(funcName, action, lookaheadSafe);
}
/// <summary>
/// Remove a binding for a named EXTERNAL ink function.
/// </summary>
/// <param name="funcName">The name of the EXTERNAL ink function to unbind.</param>
public void UnbindExternalFunction(string funcName)
{
runtimeStory.UnbindExternalFunction(funcName);
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public void Error(string message)
{
runtimeStory.Error(message);
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
/// <param name="useEndLineNumber"></param>
public void Error(string message, bool useEndLineNumber)
{
runtimeStory.Error(message, useEndLineNumber);
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public void Warning(string message)
{
runtimeStory.Warning(message);
}
/// <summary>
/// Save the current story state a JSON string.
/// </summary>
/// <returns>The current state serialized into a JSON string.</returns>
public string SaveState()
{
return runtimeStory.state.ToJson();
}
/// <summary>
/// Save the current story state to a JSON file.
/// </summary>
/// <param name="filePath">The path to the file we will be writing to.</param>
public void SaveStateFile(string filePath)
{
using FileAccess file = FileAccess.Open(filePath, FileAccess.ModeFlags.Write);
file.StoreString(SaveState());
}
/// <summary>
/// Load a JSON string as the current story state.
/// </summary>
/// <param name="jsonState">The JSON string to load.</param>
public void LoadState(string jsonState)
{
runtimeStory.state.LoadJson(jsonState);
}
/// <summary>
/// Load the content of a JSON file as the current story state.
/// </summary>
/// <param name="filePath">The path to the file we will be reading from.</param>
public void LoadStateFile(string filePath)
{
using FileAccess file = FileAccess.Open(filePath, FileAccess.ModeFlags.Read);
LoadState(file.GetAsText());
}
private void OnContinued()
{
_ = EmitSignal(SignalName.Continued);
}
private void OnMadeChoice(Ink.Runtime.Choice choice)
{
_ = EmitSignal(SignalName.MadeChoice, new InkChoice(choice));
}
public override PropertyList _GetPropertyList()
{
PropertyList properties = base._GetPropertyList() ?? new PropertyList();
properties.Add(new Godot.Collections.Dictionary()
{
{ "name", PropertyName.RawStory },
{ "type", Variant.From(Variant.Type.Object) },
{ "usage", Variant.From(PropertyUsageFlags.NoEditor) },
});
return properties;
}
}

@ -0,0 +1,172 @@
#if TOOLS
#nullable enable
using Godot;
using Ink;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.RegularExpressions;
using DependenciesCache = System.Collections.Generic.Dictionary<string, string[]>;
using GArrayGictionary = Godot.Collections.Array<Godot.Collections.Dictionary>;
using GArrayString = Godot.Collections.Array<string>;
using Gictionary = Godot.Collections.Dictionary;
namespace GodotInk;
[Tool]
public partial class InkStoryImporter : EditorImportPlugin
{
private const string OPT_MAIN_FILE = "is_main_file";
private const string OPT_COMPRESS = "compress";
private static readonly Regex includeRegex = new(@"^\s*INCLUDE\s*(?<Path>.*)\s*$", RegexOptions.Multiline | RegexOptions.Compiled);
#pragma warning disable IDE0022
public override string _GetImporterName() => "ink";
public override string _GetVisibleName() => "Ink story";
public override string[] _GetRecognizedExtensions() => new string[] { "ink" };
public override string _GetResourceType() => nameof(Resource);
public override string _GetSaveExtension() => "res";
public override float _GetPriority() => 1.0f;
public override int _GetPresetCount() => 0;
public override int _GetImportOrder() => 0;
public override GArrayGictionary _GetImportOptions(string path, int presetIndex) => new()
{
new() { { "name", OPT_MAIN_FILE }, { "default_value", false } },
new() { { "name", OPT_COMPRESS }, { "default_value", true } }
};
public override bool _GetOptionVisibility(string path, StringName optionName, Gictionary options) => true;
#pragma warning restore IDE0022
public override Error _Import(string sourceFile, string savePath, Gictionary options, GArrayString _, GArrayString __)
{
UpdateCache(sourceFile, ExtractIncludes(sourceFile));
string destFile = $"{savePath}.{_GetSaveExtension()}";
Error returnValue = options[OPT_MAIN_FILE].AsBool()
? ImportFromInk(sourceFile, destFile, options[OPT_COMPRESS].AsBool())
: ResourceSaver.Save(new StubInkStory(), destFile);
string[] additionalFiles = GetCache().Where(kvp => kvp.Value.Contains(sourceFile)).Select(kvp => kvp.Key).ToArray();
foreach (var additionalFile in additionalFiles)
AppendImportExternalResource(additionalFile);
return returnValue;
}
private static Error ImportFromInk(string sourceFile, string destFile, bool shouldCompress)
{
using Godot.FileAccess file = Godot.FileAccess.Open(sourceFile, Godot.FileAccess.ModeFlags.Read);
if (file == null)
return Godot.FileAccess.GetOpenError();
Compiler compiler = new(file.GetAsText(), new Compiler.Options
{
sourceFilename = sourceFile,
errorHandler = InkCompilerErrorHandler,
fileHandler = new FileHandler(
Path.GetDirectoryName(file.GetPathAbsolute()) ?? ProjectSettings.GlobalizePath("res://")
),
});
try
{
string storyContent = compiler.Compile().ToJson();
InkStory resource = InkStory.Create(storyContent);
ResourceSaver.SaverFlags flags = shouldCompress ? ResourceSaver.SaverFlags.Compress
: ResourceSaver.SaverFlags.None;
return ResourceSaver.Save(resource, destFile, flags);
}
catch (InvalidInkException)
{
return Error.CompilationFailed;
}
}
private static List<string> ExtractIncludes(string sourceFile)
{
using Godot.FileAccess file = Godot.FileAccess.Open(sourceFile, Godot.FileAccess.ModeFlags.Read);
return includeRegex.Matches(file.GetAsText())
.OfType<Match>()
.Select(match => sourceFile.GetBaseDir().PathJoin(match.Groups["Path"].Value.TrimEnd('\r')))
.ToList();
}
private static void InkCompilerErrorHandler(string message, ErrorType errorType)
{
switch (errorType)
{
case ErrorType.Warning:
GD.PushWarning(message);
break;
case ErrorType.Error:
GD.PushError(message);
throw new InvalidInkException();
case ErrorType.Author:
default:
break;
}
}
private const string CACHE_FILE = "user://ink_cache.json";
public static void UpdateCache(string sourceFile, List<string> dependencies)
{
DependenciesCache cache = GetCache();
cache[sourceFile] = dependencies.ToArray();
cache = cache.Where(kvp => kvp.Value.Length > 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
using Godot.FileAccess file = Godot.FileAccess.Open(CACHE_FILE, Godot.FileAccess.ModeFlags.Write);
file?.StoreString(JsonSerializer.Serialize(cache));
}
public static DependenciesCache GetCache()
{
using Godot.FileAccess file = Godot.FileAccess.Open(CACHE_FILE, Godot.FileAccess.ModeFlags.Read);
try
{
return JsonSerializer.Deserialize<DependenciesCache>(file?.GetAsText() ?? "{}")
?? new DependenciesCache();
}
catch (JsonException)
{
return new DependenciesCache();
}
}
private class FileHandler : IFileHandler
{
private readonly string rootDir;
public FileHandler(string rootDir)
{
this.rootDir = rootDir;
}
public string ResolveInkFilename(string includeName)
{
return Path.Combine(rootDir, includeName);
}
public string LoadInkFileContents(string fullFilename)
{
return File.ReadAllText(fullFilename);
}
}
}
#endif

@ -0,0 +1,14 @@
#nullable enable
namespace GodotInk;
public class InvalidInkException : System.Exception
{
public InvalidInkException() : base()
{
}
public InvalidInkException(string message) : base(message)
{
}
}

@ -0,0 +1,105 @@
#nullable enable
using Godot;
using System;
using System.Collections.Generic;
namespace GodotInk;
public static class MarshalUtils
{
/// <summary>
/// Convert from an ink variable type to a Godot Variant.
/// </summary>
/// <param name="obj">The ink variable to convert.</param>
/// <returns>The converted Godot Variant.</returns>
/// <exception cref="ArgumentException"></exception>
public static Variant ToVariant(object? obj)
{
return obj switch
{
bool b => Variant.CreateFrom(b),
int n => Variant.CreateFrom(n),
float n => Variant.CreateFrom(n),
string str => Variant.CreateFrom(str),
Ink.Runtime.Choice choice => new InkChoice(choice),
Ink.Runtime.InkList list => new InkList(list),
null => new Variant(),
_ => throw new ArgumentException($"Argument of type {obj.GetType()} is not valid."),
};
}
/// <summary>
/// Convert from a Godot Variant to an ink variable.
/// </summary>
/// <param name="variant">The Godot Variant to convert.</param>
/// <returns>The converted ink variable.</returns>
/// <exception cref="ArgumentException"></exception>
public static object? FromVariant(Variant variant)
{
return variant.VariantType switch
{
Variant.Type.Bool => variant.AsBool(),
Variant.Type.Int => variant.AsInt32(),
Variant.Type.Float => variant.AsSingle(),
Variant.Type.String => variant.AsString(),
Variant.Type.Nil => null,
_ => throw new ArgumentException($"Argument of type {variant.Obj?.GetType()} is not valid."),
};
}
/// <summary>
/// Convert from a collection of ink variables to a collection of Godot Variants. The
/// collection doesn't need to be homogeneous in type.
/// </summary>
/// <param name="objects">The collection of ink variables to convert.</param>
/// <returns>The collection of converted Godot Variants.</returns>
/// <exception cref="ArgumentException"></exception>
public static Variant[] ToVariants(IReadOnlyList<object?> objects)
{
if (objects.Count <= 0)
return Array.Empty<Variant>();
Variant[] variants = new Variant[objects.Count];
for (int i = 0; i < objects.Count; ++i)
variants[i] = ToVariant(objects[i]);
return variants;
}
/// <summary>
/// Convert from a collection of Godot Variants to a collection of ink variables. The
/// collection doesn't need to be homogeneous in type.
/// </summary>
/// <param name="variants">The collection of Godot Variants to convert.</param>
/// <returns>The collection of converted ink variables.</returns>
/// <exception cref="ArgumentException"></exception>
public static object?[] FromVariants(IReadOnlyList<Variant> variants)
{
if (variants.Count <= 0)
return Array.Empty<object?>();
object?[] objects = new object[variants.Count];
for (int i = 0; i < variants.Count; ++i)
objects[i] = FromVariant(variants[i]);
return objects;
}
// Specific case for List<Choice>, we want to stay true to the ink implementation and return
// a List<T> instead of a T[]. So we're converting to a List<InkChoice>.
public static List<InkChoice> ToVariants(IReadOnlyList<Ink.Runtime.Choice> choices)
{
List<InkChoice> inkChoices = new();
foreach (Ink.Runtime.Choice choice in choices)
inkChoices.Add((InkChoice)ToVariant(choice));
return inkChoices;
}
}

@ -0,0 +1,22 @@
#nullable enable
using Godot;
namespace GodotInk;
public partial class StubInkStory : InkStory
{
protected override string RawStory
{
get => base.RawStory;
set
{
#if TOOLS
if (Engine.IsEditorHint()) return;
#endif
throw new InvalidInkException(
"To load this story directly, please import it with 'is_main_file' set to true."
);
}
}
}

@ -0,0 +1,7 @@
[plugin]
name="ink"
description="An integration for ink, a powerful narrative scripting language."
author="Paul Joannon"
version="1.1.2"
script="plugin.gd"

@ -0,0 +1,62 @@
@tool
extends EditorPlugin
var _importer: EditorImportPlugin
var _dock: Control
func _enter_tree() -> void:
var src_path : String = get_script().get_path().get_base_dir().path_join("Src")
var importer_script : CSharpScript = load(src_path.path_join("InkStoryImporter.cs")) as CSharpScript
if not importer_script.can_instantiate():
return
_importer = importer_script.new() as EditorImportPlugin
add_import_plugin(_importer)
var dock_scene : PackedScene = load(src_path.path_join("InkDock.tscn")) as PackedScene
_dock = dock_scene.instantiate() as Control
add_control_to_bottom_panel(_dock, "Ink preview")
var fs : EditorFileSystem = get_editor_interface().get_resource_filesystem()
if not fs.resources_reimported.is_connected(_when_resources_reimported):
fs.resources_reimported.connect(_when_resources_reimported)
func _exit_tree() -> void:
var fs : EditorFileSystem = get_editor_interface().get_resource_filesystem()
if fs.resources_reimported.is_connected(_when_resources_reimported):
fs.resources_reimported.disconnect(_when_resources_reimported)
if _dock != null:
remove_control_from_bottom_panel(_dock)
_dock.free()
_dock = null
if _importer != null:
remove_import_plugin(_importer)
_importer = null
func _notification(what : int) -> void:
if what == NOTIFICATION_POST_ENTER_TREE:
if _importer != null:
return
get_editor_interface().set_plugin_enabled("GodotInk", false)
printerr(
"GodotInk could not be loaded.\n" +
"Make sure your .csproj file references GodotInk.props, and rebuild the solution.\n" +
"See the installation guide for more information: https://github.com/paulloz/godot-ink/wiki\n" +
"If you think this is not an issue on your end, please open a bug report: https://github.com/paulloz/godot-ink/issues/new/choose"
)
func _when_resources_reimported(resources : PackedStringArray) -> void:
if _dock == null:
return
for resource in resources:
if resource.get_extension() == "ink":
_dock.call("WhenInkResourceReimported")
return

@ -0,0 +1 @@
uid://4gcqopmrdxyx

Binary file not shown.

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016-2023 The Godot Engine community
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

File diff suppressed because it is too large Load Diff

@ -0,0 +1,10 @@
[configuration]
entry_symbol = "git_plugin_init"
compatibility_minimum = "4.2.0"
[libraries]
linux.editor.x86_64 = "linux/libgit_plugin.linux.editor.x86_64.so"
macos.editor = "macos/libgit_plugin.macos.editor.universal.dylib"
windows.editor.x86_64 = "windows/libgit_plugin.windows.editor.x86_64.dll"

@ -0,0 +1,33 @@
extends CharacterBody3D
# need to do:
# direction -> $CharacterBody3D.position.z
func _physics_process(delta: float) -> void:
var direction = Vector3.ZERO;
if Input.is_action_pressed("moveForward"):
direction.z -= 1
#print("forward")
velocity.z = direction.z;
move_and_slide();
if Input.is_action_pressed("moveBackward"):
direction.z += 1
velocity.z = direction.z;
move_and_slide();
if Input.is_action_pressed("moveLeft"):
direction.x -= 1;
velocity.x = direction.x;
move_and_slide();
if Input.is_action_pressed("moveRight"):
direction.x += 1;
velocity.x = direction.x;
move_and_slide();
if Input.is_action_pressed("moveJump"):
direction.y += 1;
velocity.y = velocity.y;
move_and_slide();

@ -0,0 +1 @@
uid://bfvl27u51p0lf

@ -0,0 +1,15 @@
extends Control
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
pass
func _on_button_pressed() -> void:
get_tree().change_scene_to_file("res://000.tscn")

@ -0,0 +1 @@
uid://4fjs8or62ab2

@ -0,0 +1,41 @@
[gd_scene format=3 uid="uid://dti3yc6lnv2p4"]
[ext_resource type="Script" uid="uid://4fjs8or62ab2" path="res://control.gd" id="1_0fbet"]
[node name="Control" type="Control" unique_id=1628240064]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_0fbet")
[node name="ColorRect" type="ColorRect" parent="." unique_id=1373649046]
layout_mode = 0
offset_right = 1152.0
offset_bottom = 648.0
[node name="Label" type="Label" parent="." unique_id=1263255604]
layout_mode = 0
offset_right = 40.0
offset_bottom = 23.0
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_outline_color = Color(0.9705853, 0.6323348, 0.8903854, 1)
theme_override_font_sizes/font_size = 60
text = "The Weather in Me is Tibet"
[node name="Button" type="Button" parent="." unique_id=1930893329]
layout_mode = 1
anchors_preset = -1
anchor_left = 0.5
anchor_top = 0.51900005
anchor_right = 0.5
anchor_bottom = 0.51900005
offset_left = -139.0
offset_top = -26.312042
offset_right = 134.0
offset_bottom = 25.687958
text = "Long ago, if my memory serves..."
[connection signal="pressed" from="Button" to="." method="_on_button_pressed"]

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 995 B

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c0oqj527nnpbd"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

@ -0,0 +1,36 @@
shader_type spatial;
uniform float width = 0.1;
uniform float height = 0.1;
uniform float gap_x = 0.02;
uniform float gap_y = 0.04;
uniform float sink = -0.5;
uniform sampler2D brick;
uniform sampler2D wall;
uniform sampler2D gap;
uniform float ratio = 0.5;
void fragment() {
ALBEDO = vec3(0.75);
ROUGHNESS = 0.5;
float s = 1.0/height;
float shift = float(int(UV.y*s)) / 2.0*width;
float x = mod(shift+UV.x, width)/width;
float y = mod(UV.y, height)/height;
ALBEDO = texture(gap, vec2(UV.x, UV.y)).rgb / 2.5;
if(x > gap_x && x < 1.0-gap_x && y > gap_y && y < 1.0-gap_y){
NORMAL = vec3(0.0, 0.0, 0.0);
ALBEDO = texture(brick, vec2(x, y)).rgb * ratio;
ALBEDO += texture(wall, vec2(UV.x, UV.y)).rgb * (1.0-ratio);
}else if(x<y && (1.0-y)>x){
NORMAL = vec3(x*-sink, 0.0, 0.0);
}else if(x>y && (1.0-y)<x){
NORMAL = vec3((1.0-x)*sink, 0.0, 0.0);
}else if(y<x){
NORMAL = vec3(0.0, y*-sink, 0.0);
}else if(y>x){
NORMAL = vec3(0.0, (1.0-y)*sink, 0.0);
}
}

@ -0,0 +1 @@
uid://cgfte1tvivngj

@ -0,0 +1,36 @@
shader_type spatial;
uniform float width = 0.1;
uniform float height = 0.1;
uniform float gap_x = 0.02;
uniform float gap_y = 0.04;
uniform float sink = -0.5;
uniform sampler2D brick;
uniform sampler2D wall;
uniform sampler2D gap;
uniform float ratio = 0.5;
void fragment() {
ALBEDO = vec3(0.75);
ROUGHNESS = 0.5;
float s = 1.0/height;
float shift = float(int(UV.y*s)) / 2.0*width;
float x = mod(shift+UV.x, width)/width;
float y = mod(UV.y, height)/height;
ALBEDO = texture(gap, vec2(UV.x, UV.y)).rgb / 2.5;
if(x > gap_x && x < 1.0-gap_x && y > gap_y && y < 1.0-gap_y){
NORMAL = vec3(0.0, 0.0, 0.0);
ALBEDO = texture(brick, vec2(x, y)).rgb * ratio;
ALBEDO += texture(wall, vec2(UV.x, UV.y)).rgb * (1.0-ratio);
}else if(x<y && (1.0-y)>x){
NORMAL = vec3(x*-sink, 0.0, 0.0);
}else if(x>y && (1.0-y)<x){
NORMAL = vec3((1.0-x)*sink, 0.0, 0.0);
}else if(y<x){
NORMAL = vec3(0.0, y*-sink, 0.0);
}else if(y>x){
NORMAL = vec3(0.0, (1.0-y)*sink, 0.0);
}
}

@ -0,0 +1 @@
uid://dbb44crkih6xp

@ -0,0 +1,76 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="Tibet"
run/main_scene="uid://hac4l25a1g7v"
config/features=PackedStringArray("4.7", "Forward Plus")
config/icon="res://icon.svg"
[display]
window/stretch/mode="canvas_items"
window/stretch/aspect="expand"
[dotnet]
project/assembly_name="shadrerTest"
[editor]
version_control/plugin_name="GitPlugin"
version_control/autoload_on_startup=true
[input]
moveForward={
"deadzone": 0.2,
"events": [null, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
moveBackward={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
moveRight={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
moveLeft={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
moveJump={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
turnLeft={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":81,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
turnRight={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
[physics]
3d/physics_engine="Jolt Physics"
[rendering]
rendering_device/driver.windows="d3d12"
Loading…
Cancel
Save