Initial commit
This commit is contained in:
54
singletons/Dialogs.gd
Normal file
54
singletons/Dialogs.gd
Normal file
@@ -0,0 +1,54 @@
|
||||
extends Node
|
||||
|
||||
"""
|
||||
This is the Dialogs system. Any object can send text to it by doing Dialogs.show_dialog(text, speaker)
|
||||
|
||||
Before using it 'dialog_box' should be set to some node that implements the following
|
||||
signal dialog_started
|
||||
signal dialog_ended
|
||||
func show_dialog(text, speaker)
|
||||
|
||||
This script will connect to those signals and use them to set 'active' to true or false and forward them to other nodes,
|
||||
so they can react to the dialog system being active(showing dialog) or inactive
|
||||
|
||||
Calls to show_dialog will be forwarded to the dialog_box which is free to implement them in any way (showing the text on screen,
|
||||
using text to speech, etc)
|
||||
"""
|
||||
|
||||
signal dialog_started
|
||||
signal dialog_ended
|
||||
|
||||
var active = false
|
||||
|
||||
var dialog_box = null: set = _set_dialog_box
|
||||
|
||||
func show_dialog(text:String, speaker:String):
|
||||
if is_instance_valid(dialog_box):
|
||||
dialog_box.show_dialog(text, speaker)
|
||||
|
||||
func _set_dialog_box(node):
|
||||
if not node is Node:
|
||||
push_error("provided node doesn't extend Node")
|
||||
return
|
||||
|
||||
dialog_box = node
|
||||
|
||||
if dialog_box.get_script().has_script_signal("dialog_started"):
|
||||
dialog_box.dialog_started.connect(_on_dialog_started)
|
||||
else:
|
||||
push_error("provided node doesn't implement dialog_started signal")
|
||||
|
||||
if dialog_box.get_script().has_script_signal("dialog_ended"):
|
||||
dialog_box.dialog_ended.connect(_on_dialog_ended)
|
||||
else:
|
||||
push_error("provided node doesn't implement dialog_started signal")
|
||||
|
||||
pass
|
||||
|
||||
func _on_dialog_started():
|
||||
active = true
|
||||
emit_signal("dialog_started")
|
||||
|
||||
func _on_dialog_ended():
|
||||
active = false
|
||||
emit_signal("dialog_ended")
|
||||
63
singletons/Globals.gd
Normal file
63
singletons/Globals.gd
Normal file
@@ -0,0 +1,63 @@
|
||||
extends Node
|
||||
|
||||
# warning-ignore:unused_class_variable
|
||||
var spawnpoint = ""
|
||||
var current_level = ""
|
||||
|
||||
func _ready():
|
||||
RenderingServer.set_default_clear_color(Color.WHITE)
|
||||
|
||||
"""
|
||||
Really simple save file implementation. Just saving some variables to a dictionary
|
||||
"""
|
||||
func save_game():
|
||||
var savefile = FileAccess.open("user://savegame.save", FileAccess.WRITE)
|
||||
var save_dict = {}
|
||||
save_dict.spawnpoint = spawnpoint
|
||||
save_dict.current_level = current_level
|
||||
save_dict.inventory = Inventory.list()
|
||||
save_dict.quests = Quest.get_quest_list()
|
||||
savefile.store_line(JSON.stringify(save_dict))
|
||||
savefile.close()
|
||||
pass
|
||||
|
||||
"""
|
||||
If check_only is true it will only check for a valid save file and return true or false without
|
||||
restoring any data
|
||||
"""
|
||||
func load_game(check_only=false):
|
||||
|
||||
if not FileAccess.file_exists("user://savegame.save"):
|
||||
return false
|
||||
|
||||
var savefile = FileAccess.open("user://savegame.save", FileAccess.READ)
|
||||
|
||||
var test_json_conv = JSON.new()
|
||||
test_json_conv.parse(savefile.get_line())
|
||||
var save_dict = test_json_conv.get_data()
|
||||
if typeof(save_dict) != TYPE_DICTIONARY:
|
||||
return false
|
||||
if not check_only:
|
||||
_restore_data(save_dict)
|
||||
|
||||
savefile.close()
|
||||
return true
|
||||
|
||||
"""
|
||||
Restores data from the JSON dictionary inside the save files
|
||||
"""
|
||||
func _restore_data(save_dict):
|
||||
# JSON numbers are always parsed as floats. In this case we need to turn them into ints
|
||||
for key in save_dict.quests:
|
||||
save_dict.quests[key] = int(save_dict.quests[key])
|
||||
Quest.quest_list = save_dict.quests
|
||||
|
||||
# JSON numbers are always parsed as floats. In this case we need to turn them into ints
|
||||
for key in save_dict.inventory:
|
||||
save_dict.inventory[key] = int(save_dict.inventory[key])
|
||||
Inventory.inventory = save_dict.inventory
|
||||
|
||||
spawnpoint = save_dict.spawnpoint
|
||||
current_level = save_dict.current_level
|
||||
pass
|
||||
|
||||
45
singletons/Inventory.gd
Normal file
45
singletons/Inventory.gd
Normal file
@@ -0,0 +1,45 @@
|
||||
extends Node
|
||||
|
||||
"""
|
||||
Minimal inventory system implementation.
|
||||
It's just a dictionary where items are identified by a string key and hold an int amount
|
||||
"""
|
||||
|
||||
# action can be 'added' some amount of some items is added and 'removed' when some amount
|
||||
# of some item is removed
|
||||
signal item_changed(action, type, amount)
|
||||
|
||||
var inventory = {}
|
||||
|
||||
|
||||
func get_item(type:String) -> int:
|
||||
if inventory.has(type):
|
||||
return inventory[type]
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
func add_item(type:String, amount:int) -> bool:
|
||||
if inventory.has(type):
|
||||
inventory[type] += amount
|
||||
emit_signal("item_changed", "added", type, amount)
|
||||
return true
|
||||
else:
|
||||
inventory[type] = amount
|
||||
emit_signal("item_changed", "added", type, amount)
|
||||
return true
|
||||
|
||||
|
||||
func remove_item(type:String, amount:int) -> bool:
|
||||
if inventory.has(type) and inventory[type] >= amount:
|
||||
inventory[type] -= amount
|
||||
if inventory[type] == 0:
|
||||
inventory.erase(type)
|
||||
emit_signal("item_changed", "removed", type, amount)
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
|
||||
func list() -> Dictionary:
|
||||
return inventory.duplicate()
|
||||
34
singletons/Music.gd
Normal file
34
singletons/Music.gd
Normal file
@@ -0,0 +1,34 @@
|
||||
extends Node
|
||||
|
||||
"""
|
||||
Music singleton that handles crossfading when a new song starts
|
||||
and applies a low pass filter when the game is paused. Nothing too wise
|
||||
"""
|
||||
|
||||
var current_track = ""
|
||||
|
||||
var music_bus
|
||||
|
||||
func _ready():
|
||||
music_bus = AudioServer.get_bus_index($A.bus)
|
||||
|
||||
|
||||
func play(stream):
|
||||
if current_track == "a":
|
||||
$B.stream = load(stream)
|
||||
$anims.play("AtoB")
|
||||
current_track = "b"
|
||||
else:
|
||||
$A.stream = load(stream)
|
||||
$anims.play("BtoA")
|
||||
current_track = "a"
|
||||
|
||||
|
||||
# Simple 'muffled music' effect on pause using a low pass filter
|
||||
func _notification(what):
|
||||
if what == NOTIFICATION_PAUSED:
|
||||
AudioServer.set_bus_effect_enabled(music_bus,0,true)
|
||||
AudioServer.set_bus_volume_db(music_bus,-10)
|
||||
elif what == NOTIFICATION_UNPAUSED:
|
||||
AudioServer.set_bus_effect_enabled(music_bus,0,false)
|
||||
AudioServer.set_bus_volume_db(music_bus,0)
|
||||
130
singletons/Music.tscn
Normal file
130
singletons/Music.tscn
Normal file
@@ -0,0 +1,130 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://d2sehpndnpyg5"]
|
||||
|
||||
[ext_resource type="Script" path="res://singletons/Music.gd" id="1_wold4"]
|
||||
|
||||
[sub_resource type="Animation" id="2"]
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("A:volume_db")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0, 1),
|
||||
"transitions": PackedFloat32Array(1, 1),
|
||||
"update": 0,
|
||||
"values": [0.0, -30.0]
|
||||
}
|
||||
tracks/1/type = "value"
|
||||
tracks/1/imported = false
|
||||
tracks/1/enabled = true
|
||||
tracks/1/path = NodePath("B:volume_db")
|
||||
tracks/1/interp = 1
|
||||
tracks/1/loop_wrap = true
|
||||
tracks/1/keys = {
|
||||
"times": PackedFloat32Array(0, 1),
|
||||
"transitions": PackedFloat32Array(1, 1),
|
||||
"update": 0,
|
||||
"values": [-30.0, 0.0]
|
||||
}
|
||||
tracks/2/type = "value"
|
||||
tracks/2/imported = false
|
||||
tracks/2/enabled = true
|
||||
tracks/2/path = NodePath("A:playing")
|
||||
tracks/2/interp = 1
|
||||
tracks/2/loop_wrap = true
|
||||
tracks/2/keys = {
|
||||
"times": PackedFloat32Array(1),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [false]
|
||||
}
|
||||
tracks/3/type = "value"
|
||||
tracks/3/imported = false
|
||||
tracks/3/enabled = true
|
||||
tracks/3/path = NodePath("B:playing")
|
||||
tracks/3/interp = 1
|
||||
tracks/3/loop_wrap = true
|
||||
tracks/3/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [true]
|
||||
}
|
||||
|
||||
[sub_resource type="Animation" id="3"]
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("A:volume_db")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0, 1),
|
||||
"transitions": PackedFloat32Array(1, 1),
|
||||
"update": 0,
|
||||
"values": [-30.0, 0.0]
|
||||
}
|
||||
tracks/1/type = "value"
|
||||
tracks/1/imported = false
|
||||
tracks/1/enabled = true
|
||||
tracks/1/path = NodePath("B:volume_db")
|
||||
tracks/1/interp = 1
|
||||
tracks/1/loop_wrap = true
|
||||
tracks/1/keys = {
|
||||
"times": PackedFloat32Array(0, 1),
|
||||
"transitions": PackedFloat32Array(1, 1),
|
||||
"update": 0,
|
||||
"values": [0.0, -30.0]
|
||||
}
|
||||
tracks/2/type = "value"
|
||||
tracks/2/imported = false
|
||||
tracks/2/enabled = true
|
||||
tracks/2/path = NodePath("B:playing")
|
||||
tracks/2/interp = 1
|
||||
tracks/2/loop_wrap = true
|
||||
tracks/2/keys = {
|
||||
"times": PackedFloat32Array(1),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [false]
|
||||
}
|
||||
tracks/3/type = "value"
|
||||
tracks/3/imported = false
|
||||
tracks/3/enabled = true
|
||||
tracks/3/path = NodePath("A:playing")
|
||||
tracks/3/interp = 1
|
||||
tracks/3/loop_wrap = true
|
||||
tracks/3/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [true]
|
||||
}
|
||||
|
||||
[sub_resource type="Animation" id="4"]
|
||||
resource_name = "pause"
|
||||
length = 0.5
|
||||
|
||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_y1x2n"]
|
||||
_data = {
|
||||
"AtoB": SubResource("2"),
|
||||
"BtoA": SubResource("3"),
|
||||
"pause": SubResource("4")
|
||||
}
|
||||
|
||||
[node name="Music" type="Node"]
|
||||
process_mode = 3
|
||||
script = ExtResource("1_wold4")
|
||||
|
||||
[node name="A" type="AudioStreamPlayer" parent="."]
|
||||
bus = &"Music"
|
||||
|
||||
[node name="B" type="AudioStreamPlayer" parent="."]
|
||||
volume_db = -30.0
|
||||
bus = &"Music"
|
||||
|
||||
[node name="anims" type="AnimationPlayer" parent="."]
|
||||
libraries = {
|
||||
"": SubResource("AnimationLibrary_y1x2n")
|
||||
}
|
||||
73
singletons/Quest.gd
Normal file
73
singletons/Quest.gd
Normal file
@@ -0,0 +1,73 @@
|
||||
extends Node
|
||||
|
||||
"""
|
||||
Minimal quest system implementation.
|
||||
|
||||
A dictionary where each string key represents a quest and an int value represanting a status
|
||||
"""
|
||||
|
||||
enum STATUS { NONEXISTENT, STARTED, COMPLETE, FAILED }
|
||||
|
||||
|
||||
# Emitted whenever a quests changes. It'll pass the quest name and new status
|
||||
signal quest_changed(quest_name, status)
|
||||
|
||||
var quest_list = {}
|
||||
|
||||
# Get the status of a quest. If it's not found it returns STATUS.NONEXISTENT
|
||||
func get_status(quest_name:String) -> int:
|
||||
if quest_list.has(quest_name):
|
||||
return quest_list[quest_name]
|
||||
else:
|
||||
return STATUS.NONEXISTENT
|
||||
|
||||
|
||||
func get_status_as_text(quest_name:String) -> int:
|
||||
var status = get_status(quest_name)
|
||||
return STATUS.keys()[status]
|
||||
|
||||
|
||||
# Change the state of some quest. status should be Quests.STATUS.<some status>
|
||||
func change_status(quest_name:String, status:int) -> bool:
|
||||
if quest_list.has(quest_name):
|
||||
quest_list[quest_name] = status
|
||||
emit_signal("quest_changed", quest_name, status)
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
|
||||
# Start a new quest
|
||||
func accept_quest(quest_name:String) -> bool:
|
||||
if quest_list.has(quest_name):
|
||||
return false
|
||||
else:
|
||||
quest_list[quest_name] = STATUS.STARTED
|
||||
emit_signal("quest_changed", quest_name, STATUS.STARTED)
|
||||
return true
|
||||
|
||||
|
||||
# List all the quest in a certain status
|
||||
func list(status:int) -> Array:
|
||||
if status == -1:
|
||||
return quest_list.keys()
|
||||
var result = []
|
||||
for quest in quest_list.keys():
|
||||
if quest_list[quest] == status:
|
||||
result.append(quest)
|
||||
return result
|
||||
|
||||
|
||||
func get_quest_list() -> Dictionary:
|
||||
return quest_list.duplicate()
|
||||
|
||||
|
||||
# Remove a quest from the list of quests
|
||||
func remove_quest(quest_name:String) -> bool:
|
||||
if quest_list.has(quest_name):
|
||||
quest_list.erase(quest_name)
|
||||
emit_signal("quest_changed", quest_name, STATUS.NONEXISTENT)
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user