initial commit
commit
fb1b447a10
@ -0,0 +1,4 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
@ -0,0 +1,2 @@
|
||||
# Normalize EOL for all files that Git considers text files.
|
||||
* text=auto eol=lf
|
||||
@ -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")
|
||||
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.
|
||||
[](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 @@
|
||||
uid://dcwohy1x33mp8
|
||||
@ -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 @@
|
||||
uid://jrgs3csjev4o
|
||||
@ -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 @@
|
||||
uid://dqr3nfjf03ov4
|
||||
@ -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 @@
|
||||
uid://mj1x4xcobhpa
|
||||
@ -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 @@
|
||||
uid://dwgcrdfs75ju0
|
||||
@ -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 @@
|
||||
uid://b7c4yha1omm5r
|
||||
@ -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 @@
|
||||
uid://dv72cso206ijq
|
||||
@ -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 @@
|
||||
uid://bhdpuurapp665
|
||||
@ -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 @@
|
||||
uid://nd6xfab2f6e8
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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…
Reference in New Issue