Initial commit

This commit is contained in:
Layla 2022-01-16 02:55:49 -05:00
commit cf136a2e25
10 changed files with 381 additions and 0 deletions

7
LICENSE Normal file
View File

@ -0,0 +1,7 @@
Copyright 2022 Joseph Manley
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.

105
ReadMe.md Normal file
View File

@ -0,0 +1,105 @@
![Release Version](https://img.shields.io/github/v/release/josephbmanley/simple-dialogue)
# Simple Dialogue
Requirements: Godot 3.x and [godot-yaml](https://github.com/Beliaar/godot-yaml-asset)
Table of Contents:
- [Features](#Features)
- [Examples](#Examples)
- [Object Reference](#Objects)
Simple Dialogue is yet another dialogue plugin for Godot that is a lightweight dialogue system that can easily be used at a lower level dialogue.
Currently, this requires all dialogue to be written in `YAML`.
## Features
- Timelines
- Choices
- Localization
## Examples
See the [samples](addons/simple_dialogue/samples) for more examples.
### Dialogue File
```yaml
apiVersion: 1.0
kind: timeline
events:
- name:
en_US: Person
message:
en_US: Hello, I say witty dialogue!
```
### Usage
```gdscipt
var timeline = STimeline.new("res://addons/simple_dialouge/samples/sample.yaml")
var event : SEvent
func _process(_delta):
if(Input.is_action_just_pressed("ui_accept")):
if timeline.is_choice():
var autoDecide = 1
print("You: ", timeline.get_choices()[autoDecide])
timeline.make_choice(autoDecide)
return
event = timeline.read()
if event:
print(event.name,": ", event.message)
```
## Objects
### SChoice
Properties:
|Name|Type|Description|
|---|---|---|
|events|`Array`(`SEvent`)|Array of all events this choice leads towards.|
|choice|`String`|Text associated with choice.|
### SEvent
Properties:
|Name|Type|Description|
|---|---|---|
|name|`String`|Name of current event speaker.|
|message|`String`|Text spoken by speaker.|
|portrait|`Texture`|Texture associated with current speaker.|
|choices|`Array`(`SChoice`)|Array of all choices associated with event.|
Methods:
|Name|Return Type|Description|
|---|---|---|
|get_locale(property)|`String`|Returns the value of a localized property.|
### STimeline
Properties:
|Name|Type|Description|
|---|---|---|
|file_path|`String`|File path associated with timeline.|
|events|`Array`(`SEvent`)|Array of all events associated with timeline.|
Methods:
|Name|Return Type|Description|
|---|---|---|
|get_cursor()|`int`|Returns the current cursor position.|
|seek(new_pos)|`void`|Moves current cursor position on the timeline.|
|get_event(offset=`0`)|`SEvent`|Returns the event at the cursor position.|
|read()|`SEvent`|Returns the current event and moves the cursor foward.|
|get_choices()|`Array`(`String`)|Returns an array of current choices text value at cursor.|
|make_choice(choice_int)|void|Moves cursor to new event stream depending on choice value.|

View File

@ -0,0 +1,7 @@
[plugin]
name="Simple Dialogue"
description=""
author="joseph@cloudsumu.com"
version="1.0"
script="plugin.gd"

View File

@ -0,0 +1,10 @@
tool
extends EditorPlugin
func _enter_tree():
pass
func _exit_tree():
pass

View File

@ -0,0 +1,38 @@
extends Node
var timeline = STimeline.new("res://addons/simple_dialogue/samples/sample.yaml")
var event : SEvent
export var autoDecide : int = 1
func _process(_delta):
if(Input.is_action_just_pressed("ui_accept")):
# Check for a choice before reading the timeline
# using `STimeline.is_choice()`
if timeline.is_choice():
# You can utilize `STimeline.get_choices()` to return
# an array with all choices as strings in order
# This sample uses the `autoDecide` variable for every
# choice
print("You: ", timeline.get_choices()[autoDecide])
# Use `STimeline.make_choice(int)` to make a choice
# and load that choice's events
timeline.make_choice(autoDecide)
return
# Use `STimeline.read()` to get the next SEvent
# in a dialogue timeline
event = timeline.read()
# The primary properties of a SEvent is
# `name`, `message`, and `portrait`
if event:
print(event.name,": ", event.message)
# When STimeline.read() returns null, the timeline has
# completed and you can exit the dialogue
if event == null:
get_tree().quit()

View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/simple_dialouge/samples/minimal/minimal.gd" type="Script" id=1]
[node name="Simple Dialogue Minimal Example" type="Node"]
script = ExtResource( 1 )

View File

@ -0,0 +1,53 @@
apiVersion: 1.0 # `apiVersion` is used just in case of future updates to the schema
kind: timeline # `kind` should be set to timeline for all timeline files
events: # `events` are a list of event objects
# Sample Event Object
- # Two string properties are provided for each locale
# `name` should be the name of the speaker
# `message` should be the words said by the speaker
name:
en_US: Person
message:
en_US: Hello, I say witty dialogue!
# Sample choice
- name:
en_US: Question Asker
message:
en_US: Pick a number between 1-3
# A `choices` block of `choice` objects can be added
# to work as a choice
choices:
- # A choice object contains two properties
# `choice` is a string that works as the display text for the choice
# `events` is a list of all events triggered by this choice
choice:
en_US: 1
events:
- name:
en_US: Question Asker
message:
en_US: You picked '1'
- choice:
en_US: 2
events:
- name:
en_US: Question Asker
message:
en_US: You picked '2'
- choice:
en_US: 3
events:
- name:
en_US: Question Asker
message:
en_US: You picked '3'
# After choice events finish, the timeline returns
# the timeline
- name:
en_US: Person
message:
en_US: Wow! That is a very cool choice!

View File

@ -0,0 +1,31 @@
class_name SChoice
export(Dictionary) var raw_data = {}
export(Array) var event_data = []
var SEVENT = load("res://addons/simple_dialogue/scripts/sevent.gd")
var events : Array setget ,_get_events
var choice : String setget ,_get_choice
func _init(data : Dictionary):
raw_data = data
func _get_events() -> Array:
var event_array = []
if "events" in raw_data:
for event in raw_data["events"]:
var dialogue = SEVENT.new(event)
event_array.append(dialogue)
return event_array
func _get_choice() -> String:
var locale = TranslationServer.get_locale()
if locale in raw_data["choice"]:
return raw_data["choice"][locale]
elif SEVENT.DEFAULT_LOCALE in raw_data["choice"]:
return raw_data["choice"][SEVENT.DEFAULT_LOCALE]
push_error("Choice property does not exist with translation %s and %s"
% [locale, SEVENT.DEFAULT_LOCALE])
return "INVALID"

View File

@ -0,0 +1,41 @@
class_name SEvent
const DEFAULT_LOCALE = "en_US"
var raw_data = {}
var name : String setget ,_get_name
var message : String setget ,_get_message
var portrait : Texture setget ,_get_portrait
var choices : Array setget ,_get_choices
func _init(data : Dictionary):
raw_data = data
func get_locale(property, locale = TranslationServer.get_locale()) -> String:
if locale in raw_data[property]:
return raw_data[property][locale]
elif DEFAULT_LOCALE in raw_data[property]:
return raw_data[property][DEFAULT_LOCALE]
push_error("%s property does not exist with translations %s and %s"
% [property, locale, DEFAULT_LOCALE])
return "INVALID"
func _get_choices() -> Array:
var data = []
if "choices" in raw_data:
for choice in raw_data["choices"]:
var choice_obj = SChoice.new(choice)
data.append(choice_obj)
return data
func _get_name() -> String:
return get_locale("name")
func _get_message() -> String:
return get_locale("message")
func _get_portrait() -> Texture:
if raw_data.get("portrait","") != "":
return load(raw_data["portrait"]) as Texture
return null

View File

@ -0,0 +1,83 @@
class_name STimeline
export(String) var file_path = ""
export(Array) var events = []
var _event_stream = []
var _pos_lifo = []
var _event_lifo = []
var _pos : int = 0
var _yaml = preload("res://addons/godot-yaml/gdyaml.gdns").new()
func _init(path : String):
var file : File = File.new()
if !file.file_exists(path):
push_error("Could not load timeline at path: %s" % path)
return
file_path = path
file.open(path, File.READ)
var raw_data = _yaml.parse(file.get_as_text())
file.close()
if not "apiVersion" in raw_data or raw_data["apiVersion"] != 1.0:
push_error("'%s' is using an outdated timeline!" % file_path)
return
if not "kind" in raw_data or raw_data["kind"] != "timeline":
push_error("'%s' is not a timeline!" % file_path)
return
if "events" in raw_data:
for event in raw_data["events"]:
var dialogue = SEvent.new(event)
events.push_back(dialogue)
else:
push_warning("'%s' timeline does not have any events!" % file_path)
_event_stream = events
_pos = 0
func get_cursor() -> int:
return _pos
func seek(new_pos : int) -> void:
_pos = new_pos
func get_event(offset : int = 0) -> SEvent:
if _pos + offset < len(_event_stream):
return _event_stream[_pos + offset]
return null
func read() -> SEvent:
var event = get_event()
if event == null and len(_event_lifo) > 0:
_event_stream = _event_lifo.pop_back()
_pos = _pos_lifo.pop_back()
event = get_event()
_pos += 1
return event
func get_choices() -> Array:
var choices = []
var event = get_event(-1)
if event:
for choice in event.choices : choices.append(choice.choice)
return choices
func is_choice() -> bool:
return len(get_choices()) > 0
func make_choice(choice : int) -> void:
var event = get_event(-1)
if not event:
push_error("Attempted to find choice at invalid event! Pos '%s' Event Stream: %s"
% [_pos, _event_stream])
return
if choice > len(event.choices):
push_error("Attempted to make an out of bounds choice! Timeline '%s': choice '%s' while expected size is '%s'"
% [self.file_path, choice, len(event.choices)])
return
var choice_obj : SChoice = event.choices[choice]
_event_lifo.push_back(_event_stream)
_event_stream = choice_obj.events
_pos_lifo.push_back(_pos)
_pos = 0