Release
This commit is contained in:
commit
8be7c8e7c1
201 changed files with 15471 additions and 0 deletions
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
.import/
|
||||
logs/
|
||||
*.translation
|
||||
|
||||
# Platform specific metadata files
|
||||
.DS_Store
|
||||
thumbs.db
|
||||
.*.swp
|
||||
*~
|
74
LICENCE
Normal file
74
LICENCE
Normal file
|
@ -0,0 +1,74 @@
|
|||
Texte
|
||||
=====
|
||||
|
||||
Kujiu
|
||||
|
||||
Graphisme
|
||||
=========
|
||||
|
||||
Springouille
|
||||
|
||||
Voix, SFX et programmation
|
||||
==========================
|
||||
|
||||
Kujiu
|
||||
|
||||
Musique
|
||||
=======
|
||||
|
||||
Dreaming Under the Stars
|
||||
par Darren Curtis -
|
||||
Licence Creative Commons CC-BY -
|
||||
https://www.darrencurtismusic.com
|
||||
|
||||
Robot Lover Song
|
||||
par Neku (feat. Aline) -
|
||||
Licence Creative Commons
|
||||
|
||||
Caractères
|
||||
==========
|
||||
|
||||
Lavi font
|
||||
par Ruben Holthuijsen -
|
||||
Licence GPLv3
|
||||
|
||||
Rendu avec FreeType
|
||||
The FreeType Project. All rights reserved. -
|
||||
http://www.freetype.org
|
||||
|
||||
Moteur
|
||||
======
|
||||
|
||||
Godot Engine
|
||||
par Juan Linietsky, Ariel Manzur et contributeurs -
|
||||
Licence MIT -
|
||||
https://www.godotengine.org
|
||||
|
||||
Escoria
|
||||
par Juan Linietsky, Ariel Manzur et contributeurs -
|
||||
Licence MIT -
|
||||
https://www.github.com/godotengine/escoria
|
||||
|
||||
ENet
|
||||
par Lee Salzman -
|
||||
Licence MIT-like
|
||||
|
||||
MBedTLS
|
||||
Licence Apache version 2.0
|
||||
|
||||
Production
|
||||
==========
|
||||
|
||||
Une Narration Expérimentale de Réalité Vectographiée
|
||||
|
||||
Nerv Project
|
||||
|
||||
https://www.nerv-project.eu
|
||||
|
||||
Licence
|
||||
=======
|
||||
Distribué sous licence Creative Commons CC-BY-SA 4.0
|
||||
|
||||
http://creativecommons.org/licenses/by-sa/4.0/
|
||||
|
||||
(Sauf travaux sous licence spécifique)
|
BIN
device/actors/dragon/assets/dragon.png
Normal file
BIN
device/actors/dragon/assets/dragon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 710 KiB |
34
device/actors/dragon/assets/dragon.png.import
Normal file
34
device/actors/dragon/assets/dragon.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/dragon.png-b942aab86d4b9317a870944d76fcaaeb.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://actors/dragon/assets/dragon.png"
|
||||
dest_files=[ "res://.import/dragon.png-b942aab86d4b9317a870944d76fcaaeb.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=false
|
||||
svg/scale=1.0
|
49
device/actors/dragon/dragon.tscn
Normal file
49
device/actors/dragon/dragon.tscn
Normal file
|
@ -0,0 +1,49 @@
|
|||
[gd_scene load_steps=5 format=2]
|
||||
|
||||
[ext_resource path="res://globals/item.gd" type="Script" id=1]
|
||||
[ext_resource path="res://actors/dragon/assets/dragon.png" type="Texture" id=2]
|
||||
[ext_resource path="res://rooms/garden/assets/rain.ogg" type="AudioStream" id=3]
|
||||
|
||||
[sub_resource type="Animation" id=1]
|
||||
resource_name = "rain"
|
||||
length = 23.0
|
||||
tracks/0/type = "audio"
|
||||
tracks/0/path = NodePath("RainPlayer")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/keys = {
|
||||
"clips": [ {
|
||||
"end_offset": 0.0,
|
||||
"start_offset": 0.0,
|
||||
"stream": ExtResource( 3 )
|
||||
} ],
|
||||
"times": PoolRealArray( 0 )
|
||||
}
|
||||
|
||||
[node name="Dragon" type="Node2D"]
|
||||
script = ExtResource( 1 )
|
||||
global_id = "Dragon"
|
||||
active = false
|
||||
use_action_menu = false
|
||||
|
||||
[node name="area" type="Area2D" parent="."]
|
||||
|
||||
[node name="Sprite" type="Sprite" parent="area"]
|
||||
scale = Vector2( 1.32615, 1.31784 )
|
||||
texture = ExtResource( 2 )
|
||||
centered = false
|
||||
|
||||
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="area"]
|
||||
position = Vector2( -24.1903, -25.9038 )
|
||||
scale = Vector2( 1.38245, 1.38245 )
|
||||
polygon = PoolVector2Array( 379.768, 88.1424, 298.145, 208.428, 301.009, 272.867, 273.802, 294.346, 227.979, 338.737, 202.203, 401.744, 226.547, 438.975, 216.523, 454.727, 189.315, 451.863, 196.475, 494.822, 176.428, 557.829, 104.829, 559.261, 93.3732, 627.996, 89.0773, 672.387, 67.5977, 731.098, 67.5977, 759.737, 106.261, 748.281, 154.948, 733.962, 179.292, 705.322, 226.547, 688.138, 270.938, 692.434, 302.441, 696.73, 296.713, 658.067, 289.553, 626.564, 323.921, 640.883, 376.904, 636.587, 402.679, 620.836, 478.574, 646.611, 563.06, 643.747, 608.883, 620.836, 634.658, 586.468, 594.563, 569.285, 570.22, 569.285, 558.764, 527.758, 584.539, 493.39, 610.315, 447.567, 613.179, 390.288, 604.587, 350.193, 585.971, 304.37, 547.308, 287.186, 512.941, 308.666, 550.172, 351.625, 497.189, 312.962, 441.342, 312.962, 462.822, 350.193, 429.887, 324.418, 432.75, 277.163, 468.55, 254.251, 538.716, 241.363, 620.339, 241.363, 677.618, 251.387, 716.281, 282.891, 776.424, 255.683, 832.271, 272.867, 873.798, 300.074, 888.117, 348.761, 865.206, 400.312, 812.223, 437.543, 773.56, 430.384, 733.464, 407.472, 684.777, 407.472, 693.369, 447.567, 713.417, 469.047, 670.458, 509.142, 670.458, 577.877, 683.345, 623.7, 724.873, 633.724, 753.512, 678.115, 825.111, 676.683, 908.165, 675.251, 982.628, 665.227, 962.527, 734.022, 996.064, 765.323, 1084.3, 728.234, 1105.78, 686.706, 1100.05, 642.315, 1090.03, 593.628, 1027.02, 583.604, 975.468, 583.604, 953.988, 567.853, 956.852, 534.917, 913.893, 537.781, 880.958, 572.149, 852.318, 539.213, 890.981, 513.438, 952.556, 491.958, 1005.54, 460.455, 1034.18, 407.472, 1065.68, 335.873, 1042.77, 277.163, 1088.59, 317.258, 1097.19, 265.707, 1052.79, 224.18, 1024.15, 221.316, 998.379, 181.221, 952.556, 188.38, 942.532, 151.149, 879.526, 105.326, 830.839, 95.3022, 803.631, 56.639, 736.328, 22.2717, 677.618, 43.7513, 620.339, 65.2309, 567.356, 99.5982, 492.893, 103.894, 467.118, 145.421, 427.023, 115.35 )
|
||||
|
||||
[node name="animation" type="AnimationPlayer" parent="."]
|
||||
anims/rain = SubResource( 1 )
|
||||
|
||||
[node name="RainPlayer" type="AudioStreamPlayer2D" parent="."]
|
||||
stream = ExtResource( 3 )
|
||||
volume_db = 4.0
|
||||
max_distance = 20000.0
|
BIN
device/actors/god/assets/god.png
Normal file
BIN
device/actors/god/assets/god.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 319 KiB |
34
device/actors/god/assets/god.png.import
Normal file
34
device/actors/god/assets/god.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/god.png-3f4e537a13a15c396d20f3618c703300.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://actors/god/assets/god.png"
|
||||
dest_files=[ "res://.import/god.png-3f4e537a13a15c396d20f3618c703300.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=false
|
||||
svg/scale=1.0
|
45
device/actors/god/god.tscn
Normal file
45
device/actors/god/god.tscn
Normal file
|
@ -0,0 +1,45 @@
|
|||
[gd_scene load_steps=5 format=2]
|
||||
|
||||
[ext_resource path="res://globals/item.gd" type="Script" id=1]
|
||||
[ext_resource path="res://actors/god/assets/god.png" type="Texture" id=2]
|
||||
[ext_resource path="res://rooms/garden/assets/plouf.ogg" type="AudioStream" id=3]
|
||||
|
||||
[sub_resource type="Animation" id=1]
|
||||
resource_name = "plouf"
|
||||
length = 1.6
|
||||
tracks/0/type = "audio"
|
||||
tracks/0/path = NodePath("PloufPlayer")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/keys = {
|
||||
"clips": [ {
|
||||
"end_offset": 0.0,
|
||||
"start_offset": 0.0,
|
||||
"stream": ExtResource( 3 )
|
||||
} ],
|
||||
"times": PoolRealArray( 0 )
|
||||
}
|
||||
|
||||
[node name="God" type="Node2D"]
|
||||
script = ExtResource( 1 )
|
||||
global_id = "God"
|
||||
|
||||
[node name="area" type="Area2D" parent="."]
|
||||
|
||||
[node name="Sprite" type="Sprite" parent="area"]
|
||||
position = Vector2( 280, 559 )
|
||||
scale = Vector2( 1.8, 1.8 )
|
||||
texture = ExtResource( 2 )
|
||||
|
||||
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="area"]
|
||||
polygon = PoolVector2Array( 215.206, -35.7623, 287.913, -3.95319, 56.1606, 89.202, 24.3516, 289.145, -46.0829, 480, -84.7083, 602.692, -32.4504, 636.773, 72.0652, 604.964, 97.058, 643.589, -100.613, 993.489, 67.5211, 1032.11, 219.75, 982.129, 346.987, 995.761, 540.113, 1016.21, 521.937, 916.239, 408.333, 679.942, 419.693, 618.596, 537.841, 654.95, 687.799, 668.582, 683.254, 564.066, 553.746, 400.477, 574.195, 214.166, 487.856, -3.95319, 297.001, -149.366, 224.294, -117.557, 224.294, -117.557, 219.75, -119.829 )
|
||||
|
||||
[node name="animation" type="AnimationPlayer" parent="."]
|
||||
anims/plouf = SubResource( 1 )
|
||||
|
||||
[node name="PloufPlayer" type="AudioStreamPlayer2D" parent="."]
|
||||
stream = ExtResource( 3 )
|
||||
volume_db = 4.0
|
||||
max_distance = 20000.0
|
8
device/actors/narrator/narrator.tscn
Normal file
8
device/actors/narrator/narrator.tscn
Normal file
|
@ -0,0 +1,8 @@
|
|||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://globals/item.gd" type="Script" id=1]
|
||||
|
||||
[node name="Narrator" type="Node2D"]
|
||||
script = ExtResource( 1 )
|
||||
global_id = "Narrator"
|
||||
active = false
|
BIN
device/bin/win64/release/libgitapi.dll
Normal file
BIN
device/bin/win64/release/libgitapi.dll
Normal file
Binary file not shown.
BIN
device/bin/win64/release/libgitapi.exp
Normal file
BIN
device/bin/win64/release/libgitapi.exp
Normal file
Binary file not shown.
BIN
device/bin/win64/release/libgitapi.pdb
Normal file
BIN
device/bin/win64/release/libgitapi.pdb
Normal file
Binary file not shown.
BIN
device/bin/x11/release/libgitapi.so
Normal file
BIN
device/bin/x11/release/libgitapi.so
Normal file
Binary file not shown.
17
device/contrib/custom/spine.gd
Normal file
17
device/contrib/custom/spine.gd
Normal file
|
@ -0,0 +1,17 @@
|
|||
extends Node2D
|
||||
|
||||
# Custom functions to be used in your game.
|
||||
|
||||
# Spine example
|
||||
func spine_change_skin(params):
|
||||
# Show-and-tell, ignore
|
||||
var obj = params[0]
|
||||
var func_name = params[1]
|
||||
|
||||
# Change all parent's Spine nodes' skins
|
||||
var skin = params[2]
|
||||
|
||||
for node in get_parent().get_children():
|
||||
if node.get_class() == "Spine":
|
||||
node.set_indexed("playback/skin", skin)
|
||||
|
BIN
device/contrib/scenes/shadow/cap.png
Normal file
BIN
device/contrib/scenes/shadow/cap.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
34
device/contrib/scenes/shadow/cap.png.import
Normal file
34
device/contrib/scenes/shadow/cap.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/cap.png-3d49dcef40d6831b180435be18953959.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://contrib/scenes/shadow/cap.png"
|
||||
dest_files=[ "res://.import/cap.png-3d49dcef40d6831b180435be18953959.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
BIN
device/contrib/scenes/shadow/shadow.png
Normal file
BIN
device/contrib/scenes/shadow/shadow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
34
device/contrib/scenes/shadow/shadow.png.import
Normal file
34
device/contrib/scenes/shadow/shadow.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/shadow.png-acc6ebdec702b2dc2e8d7d7cc818c744.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://contrib/scenes/shadow/shadow.png"
|
||||
dest_files=[ "res://.import/shadow.png-acc6ebdec702b2dc2e8d7d7cc818c744.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
27
device/contrib/scenes/shadow/shadow.tscn
Normal file
27
device/contrib/scenes/shadow/shadow.tscn
Normal file
|
@ -0,0 +1,27 @@
|
|||
[gd_scene load_steps=5 format=2]
|
||||
|
||||
[ext_resource path="res://globals/shadow.gd" type="Script" id=1]
|
||||
[ext_resource path="res://contrib/shaders/shadow.tres" type="Material" id=2]
|
||||
[ext_resource path="res://contrib/scenes/shadow/shadow.png" type="Texture" id=3]
|
||||
[ext_resource path="res://contrib/scenes/shadow/cap.png" type="Texture" id=4]
|
||||
|
||||
[node name="shadow" type="Node2D"]
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="perspective_scale" type="Node2D" parent="."]
|
||||
scale = Vector2( 1, 0.5 )
|
||||
|
||||
[node name="rotation" type="Node2D" parent="perspective_scale"]
|
||||
|
||||
[node name="shadow" type="Sprite" parent="perspective_scale/rotation"]
|
||||
material = ExtResource( 2 )
|
||||
z_index = -1
|
||||
texture = ExtResource( 3 )
|
||||
centered = false
|
||||
offset = Vector2( 0, -32 )
|
||||
|
||||
[node name="shadow_cap" type="Sprite" parent="perspective_scale/rotation"]
|
||||
material = ExtResource( 2 )
|
||||
z_index = -1
|
||||
texture = ExtResource( 4 )
|
||||
offset = Vector2( -16, 0 )
|
10
device/contrib/shaders/shadow.shader
Normal file
10
device/contrib/shaders/shadow.shader
Normal file
|
@ -0,0 +1,10 @@
|
|||
shader_type canvas_item;
|
||||
render_mode blend_mix;
|
||||
|
||||
uniform float alpha_value = 0.5;
|
||||
|
||||
void fragment() {
|
||||
float v = texture(TEXTURE, UV).a;
|
||||
COLOR = vec4(vec3(0), v*alpha_value);
|
||||
}
|
||||
|
11
device/contrib/shaders/shadow.tres
Normal file
11
device/contrib/shaders/shadow.tres
Normal file
|
@ -0,0 +1,11 @@
|
|||
[gd_resource type="ShaderMaterial" load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://contrib/shaders/shadow.shader" type="Shader" id=1]
|
||||
|
||||
[resource]
|
||||
|
||||
render_priority = 0
|
||||
shader = ExtResource( 1 )
|
||||
shader_param/alpha_value = 1.0
|
||||
_sections_unfolded = [ "shader_param" ]
|
||||
|
29
device/default_bus_layout.tres
Normal file
29
device/default_bus_layout.tres
Normal file
|
@ -0,0 +1,29 @@
|
|||
[gd_resource type="AudioBusLayout" format=2]
|
||||
|
||||
[resource]
|
||||
|
||||
bus/0/name = "Master"
|
||||
bus/0/solo = false
|
||||
bus/0/mute = false
|
||||
bus/0/bypass_fx = false
|
||||
bus/0/volume_db = 0.0
|
||||
bus/0/send = ""
|
||||
bus/1/name = "Music"
|
||||
bus/1/solo = false
|
||||
bus/1/mute = false
|
||||
bus/1/bypass_fx = false
|
||||
bus/1/volume_db = 0.0
|
||||
bus/1/send = "Master"
|
||||
bus/2/name = "Speech"
|
||||
bus/2/solo = false
|
||||
bus/2/mute = false
|
||||
bus/2/bypass_fx = false
|
||||
bus/2/volume_db = 0.0
|
||||
bus/2/send = "Master"
|
||||
bus/3/name = "SFX"
|
||||
bus/3/solo = false
|
||||
bus/3/mute = false
|
||||
bus/3/bypass_fx = false
|
||||
bus/3/volume_db = 0.0
|
||||
bus/3/send = "Master"
|
||||
|
67
device/export_presets.cfg
Normal file
67
device/export_presets.cfg
Normal file
|
@ -0,0 +1,67 @@
|
|||
[preset.0]
|
||||
|
||||
name="Linux/X11"
|
||||
platform="Linux/X11"
|
||||
runnable=true
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter="*.esc"
|
||||
exclude_filter=""
|
||||
export_path="../../../../../../../../home/kujiu/dieu_du_jardin/linux/dieu_du_jardin.x86_64"
|
||||
patch_list=PoolStringArray( )
|
||||
script_export_mode=1
|
||||
script_encryption_key=""
|
||||
|
||||
[preset.0.options]
|
||||
|
||||
texture_format/bptc=true
|
||||
texture_format/s3tc=true
|
||||
texture_format/etc=true
|
||||
texture_format/etc2=true
|
||||
texture_format/no_bptc_fallbacks=false
|
||||
binary_format/64_bits=true
|
||||
binary_format/embed_pck=true
|
||||
custom_template/release=""
|
||||
custom_template/debug=""
|
||||
|
||||
[preset.1]
|
||||
|
||||
name="Windows Desktop"
|
||||
platform="Windows Desktop"
|
||||
runnable=true
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter="*.esc"
|
||||
exclude_filter=""
|
||||
export_path="dieu_du_jardin.exe"
|
||||
patch_list=PoolStringArray( )
|
||||
script_export_mode=1
|
||||
script_encryption_key=""
|
||||
|
||||
[preset.1.options]
|
||||
|
||||
texture_format/bptc=false
|
||||
texture_format/s3tc=true
|
||||
texture_format/etc=false
|
||||
texture_format/etc2=false
|
||||
texture_format/no_bptc_fallbacks=true
|
||||
binary_format/64_bits=true
|
||||
binary_format/embed_pck=true
|
||||
custom_template/release=""
|
||||
custom_template/debug=""
|
||||
codesign/enable=false
|
||||
codesign/identity=""
|
||||
codesign/password=""
|
||||
codesign/timestamp=true
|
||||
codesign/timestamp_server_url=""
|
||||
codesign/digest_algorithm=1
|
||||
codesign/description=""
|
||||
codesign/custom_options=PoolStringArray( )
|
||||
application/icon=""
|
||||
application/file_version=""
|
||||
application/product_version=""
|
||||
application/company_name=""
|
||||
application/product_name=""
|
||||
application/file_description=""
|
||||
application/copyright=""
|
||||
application/trademarks=""
|
7
device/game.esc
Normal file
7
device/game.esc
Normal file
|
@ -0,0 +1,7 @@
|
|||
:start
|
||||
|
||||
cut_scene telon fade_out
|
||||
|
||||
change_scene res://rooms/garden/garden.tscn
|
||||
|
||||
cut_scene telon fade_in
|
16
device/git_api.gdnlib
Normal file
16
device/git_api.gdnlib
Normal file
|
@ -0,0 +1,16 @@
|
|||
[general]
|
||||
|
||||
singleton=true
|
||||
load_once=true
|
||||
symbol_prefix="godot_"
|
||||
reloadable=false
|
||||
|
||||
[entry]
|
||||
|
||||
Windows.64="res://bin/win64/release/libgitapi.dll"
|
||||
X11.64="res://bin/x11/release/libgitapi.so"
|
||||
|
||||
[dependencies]
|
||||
|
||||
Windows.64=[ ]
|
||||
X11.64=[ ]
|
9
device/git_api.gdns
Normal file
9
device/git_api.gdns
Normal file
|
@ -0,0 +1,9 @@
|
|||
[gd_resource type="NativeScript" load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://git_api.gdnlib" type="GDNativeLibrary" id=1]
|
||||
|
||||
[resource]
|
||||
resource_name = "GitAPI"
|
||||
class_name = "GitAPI"
|
||||
library = ExtResource( 1 )
|
||||
script_class_name = "GitAPI"
|
50
device/globals/achievements.gd
Normal file
50
device/globals/achievements.gd
Normal file
|
@ -0,0 +1,50 @@
|
|||
var GameCenter = null
|
||||
var iOS = null
|
||||
var achieves_left = 2
|
||||
|
||||
func flush():
|
||||
while GameCenter.get_pending_event_count() > 0:
|
||||
GameCenter.pop_pending_event()
|
||||
|
||||
func award(aid):
|
||||
if GameCenter == null:
|
||||
return
|
||||
|
||||
flush()
|
||||
|
||||
printt("******** awarding achievement ", aid)
|
||||
GameCenter.award_achievement( { "name": aid, "progress": 100, "show_completion_banner": true } )
|
||||
|
||||
if vm.settings.rate_shown:
|
||||
return
|
||||
|
||||
achieves_left -= 1
|
||||
if achieves_left > 0:
|
||||
return
|
||||
|
||||
printt("showing rate screen")
|
||||
var url = iOS.get_rate_url(ProjectSettings.get_setting("ios/app_id"))
|
||||
vm.show_rate(url)
|
||||
|
||||
vm.settings.rate_shown = true
|
||||
vm.save_settings()
|
||||
|
||||
|
||||
func reset():
|
||||
if GameCenter == null:
|
||||
return
|
||||
|
||||
flush()
|
||||
GameCenter.reset_achievements()
|
||||
|
||||
func is_ready():
|
||||
return true
|
||||
|
||||
func start():
|
||||
# TODO: This initialization should probably be somewhere else
|
||||
# if ProjectSettings.has_setting("GameCenter"):
|
||||
# GameCenter = ProjectSettings.get_singleton("GameCenter")
|
||||
# iOS = ProjectSettings.get_singleton("iOS")
|
||||
|
||||
pass
|
||||
|
74
device/globals/action_menu.gd
Normal file
74
device/globals/action_menu.gd
Normal file
|
@ -0,0 +1,74 @@
|
|||
extends Container
|
||||
|
||||
var target
|
||||
func action_pressed(action):
|
||||
if !is_visible():
|
||||
return
|
||||
if !get_node("/root/vm").can_interact():
|
||||
return
|
||||
|
||||
get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFAULT, "game", "action_menu_selected", target, action)
|
||||
|
||||
func target_visibility_changed():
|
||||
stop()
|
||||
|
||||
func _clamp(click_pos):
|
||||
var scale = ProjectSettings.get_setting("escoria/platform/action_menu_scale")
|
||||
set_scale(Vector2(scale, scale))
|
||||
|
||||
var width = float(ProjectSettings.get("display/window/size/width"))
|
||||
var height = float(ProjectSettings.get("display/window/size/height"))
|
||||
var my_size = get_size() * Vector2(scale, scale)
|
||||
var center_offset = my_size / Vector2(2, 2) # Half to the left, half up
|
||||
|
||||
# Set the action menu in the middle
|
||||
click_pos -= center_offset
|
||||
|
||||
var dist_from_right = width - (click_pos.x + my_size.x)
|
||||
var dist_from_left = click_pos.x
|
||||
var dist_from_bottom = height - (click_pos.y + my_size.y)
|
||||
var dist_from_top = click_pos.y
|
||||
|
||||
if dist_from_right < 0:
|
||||
click_pos.x += dist_from_right
|
||||
if dist_from_left < 0:
|
||||
click_pos.x -= dist_from_left
|
||||
if dist_from_bottom < 0:
|
||||
click_pos.y += dist_from_bottom
|
||||
if dist_from_top < 0:
|
||||
click_pos.y -= dist_from_top
|
||||
|
||||
return click_pos
|
||||
|
||||
func start(p_target):
|
||||
if target != p_target:
|
||||
target = p_target
|
||||
target.connect("visibility_changed", self, "target_visibility_changed")
|
||||
|
||||
# Do not display the tooltip alongside the menu
|
||||
if vm.tooltip and ProjectSettings.get_setting("escoria/ui/tooltip_follows_mouse"):
|
||||
vm.tooltip.hide() # XXX: Maybe the tooltip should hide itself automatically if the action menu is visible
|
||||
vm.hover_teardown()
|
||||
|
||||
func stop(show_tooltip=true):
|
||||
if target != null:
|
||||
target.disconnect("visibility_changed", self, "target_visibility_changed")
|
||||
target = null
|
||||
hide()
|
||||
if vm.tooltip and ProjectSettings.get_setting("escoria/ui/tooltip_follows_mouse") and show_tooltip:
|
||||
vm.hover_rebuild()
|
||||
|
||||
func set_position(pos, keep_margins=false):
|
||||
.set_position(_clamp(pos), keep_margins)
|
||||
|
||||
func _ready():
|
||||
var acts = get_node("actions")
|
||||
|
||||
for i in range(acts.get_child_count()):
|
||||
var c = acts.get_child(i)
|
||||
if !(c is BaseButton):
|
||||
continue
|
||||
c.connect("pressed", self, "action_pressed", [c.get_name()])
|
||||
|
||||
vm.register_action_menu(self)
|
||||
|
BIN
device/globals/assets/fonts/Lavi.ttf
Normal file
BIN
device/globals/assets/fonts/Lavi.ttf
Normal file
Binary file not shown.
8
device/globals/assets/fonts/lavi.tres
Normal file
8
device/globals/assets/fonts/lavi.tres
Normal file
|
@ -0,0 +1,8 @@
|
|||
[gd_resource type="DynamicFont" load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://globals/assets/fonts/Lavi.ttf" type="DynamicFontData" id=1]
|
||||
|
||||
[resource]
|
||||
size = 64
|
||||
outline_color = Color( 0, 0, 0, 1 )
|
||||
font_data = ExtResource( 1 )
|
13
device/globals/assets/fonts/lavi_credit.tres
Normal file
13
device/globals/assets/fonts/lavi_credit.tres
Normal file
|
@ -0,0 +1,13 @@
|
|||
[gd_resource type="DynamicFont" load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://globals/assets/fonts/Lavi.ttf" type="DynamicFontData" id=1]
|
||||
|
||||
[resource]
|
||||
size = 92
|
||||
outline_size = 12
|
||||
outline_color = Color( 0, 0, 0, 1 )
|
||||
extra_spacing_top = 20
|
||||
extra_spacing_bottom = 20
|
||||
extra_spacing_char = 8
|
||||
extra_spacing_space = 20
|
||||
font_data = ExtResource( 1 )
|
13
device/globals/assets/fonts/lavi_credit_title.tres
Normal file
13
device/globals/assets/fonts/lavi_credit_title.tres
Normal file
|
@ -0,0 +1,13 @@
|
|||
[gd_resource type="DynamicFont" load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://globals/assets/fonts/Lavi.ttf" type="DynamicFontData" id=1]
|
||||
|
||||
[resource]
|
||||
size = 150
|
||||
outline_size = 10
|
||||
outline_color = Color( 0, 0, 0, 1 )
|
||||
extra_spacing_top = 20
|
||||
extra_spacing_bottom = 20
|
||||
extra_spacing_char = 8
|
||||
extra_spacing_space = 20
|
||||
font_data = ExtResource( 1 )
|
674
device/globals/assets/fonts/license.txt
Normal file
674
device/globals/assets/fonts/license.txt
Normal file
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
674
device/globals/assets/fonts/readme.txt
Normal file
674
device/globals/assets/fonts/readme.txt
Normal file
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
BIN
device/globals/assets/icon.png
Normal file
BIN
device/globals/assets/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 92 KiB |
34
device/globals/assets/icon.png.import
Normal file
34
device/globals/assets/icon.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/icon.png-def7f3b9d27680badc8b63c9e232bd0d.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://globals/assets/icon.png"
|
||||
dest_files=[ "res://.import/icon.png-def7f3b9d27680badc8b63c9e232bd0d.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
BIN
device/globals/assets/musics/dreaming_under_the_stars.ogg
Normal file
BIN
device/globals/assets/musics/dreaming_under_the_stars.ogg
Normal file
Binary file not shown.
|
@ -0,0 +1,15 @@
|
|||
[remap]
|
||||
|
||||
importer="ogg_vorbis"
|
||||
type="AudioStreamOGGVorbis"
|
||||
path="res://.import/dreaming_under_the_stars.ogg-0b8bed0517102fd70d5fd3c4f101a176.oggstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://globals/assets/musics/dreaming_under_the_stars.ogg"
|
||||
dest_files=[ "res://.import/dreaming_under_the_stars.ogg-0b8bed0517102fd70d5fd3c4f101a176.oggstr" ]
|
||||
|
||||
[params]
|
||||
|
||||
loop=true
|
||||
loop_offset=0
|
BIN
device/globals/assets/musics/end_credits.ogg
Normal file
BIN
device/globals/assets/musics/end_credits.ogg
Normal file
Binary file not shown.
15
device/globals/assets/musics/end_credits.ogg.import
Normal file
15
device/globals/assets/musics/end_credits.ogg.import
Normal file
|
@ -0,0 +1,15 @@
|
|||
[remap]
|
||||
|
||||
importer="ogg_vorbis"
|
||||
type="AudioStreamOGGVorbis"
|
||||
path="res://.import/end_credits.ogg-dd304750412eabcabfb61beec53568e1.oggstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://globals/assets/musics/end_credits.ogg"
|
||||
dest_files=[ "res://.import/end_credits.ogg-dd304750412eabcabfb61beec53568e1.oggstr" ]
|
||||
|
||||
[params]
|
||||
|
||||
loop=true
|
||||
loop_offset=0
|
BIN
device/globals/assets/musics/end_credits.opus
Normal file
BIN
device/globals/assets/musics/end_credits.opus
Normal file
Binary file not shown.
BIN
device/globals/assets/speech/fr/player_speak_1_1.ogg
Normal file
BIN
device/globals/assets/speech/fr/player_speak_1_1.ogg
Normal file
Binary file not shown.
15
device/globals/assets/speech/fr/player_speak_1_1.ogg.import
Normal file
15
device/globals/assets/speech/fr/player_speak_1_1.ogg.import
Normal file
|
@ -0,0 +1,15 @@
|
|||
[remap]
|
||||
|
||||
importer="ogg_vorbis"
|
||||
type="AudioStreamOGGVorbis"
|
||||
path="res://.import/player_speak_1_1.ogg-d9371869a5c54909461671798ce50534.oggstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://globals/assets/speech/fr/player_speak_1_1.ogg"
|
||||
dest_files=[ "res://.import/player_speak_1_1.ogg-d9371869a5c54909461671798ce50534.oggstr" ]
|
||||
|
||||
[params]
|
||||
|
||||
loop=true
|
||||
loop_offset=0
|
BIN
device/globals/assets/speech/fr/player_speak_1_2.ogg
Normal file
BIN
device/globals/assets/speech/fr/player_speak_1_2.ogg
Normal file
Binary file not shown.
15
device/globals/assets/speech/fr/player_speak_1_2.ogg.import
Normal file
15
device/globals/assets/speech/fr/player_speak_1_2.ogg.import
Normal file
|
@ -0,0 +1,15 @@
|
|||
[remap]
|
||||
|
||||
importer="ogg_vorbis"
|
||||
type="AudioStreamOGGVorbis"
|
||||
path="res://.import/player_speak_1_2.ogg-502257ada099ffd6390d707c6bf3d9d8.oggstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://globals/assets/speech/fr/player_speak_1_2.ogg"
|
||||
dest_files=[ "res://.import/player_speak_1_2.ogg-502257ada099ffd6390d707c6bf3d9d8.oggstr" ]
|
||||
|
||||
[params]
|
||||
|
||||
loop=true
|
||||
loop_offset=0
|
BIN
device/globals/assets/speech/fr/player_speak_2_1.ogg
Normal file
BIN
device/globals/assets/speech/fr/player_speak_2_1.ogg
Normal file
Binary file not shown.
15
device/globals/assets/speech/fr/player_speak_2_1.ogg.import
Normal file
15
device/globals/assets/speech/fr/player_speak_2_1.ogg.import
Normal file
|
@ -0,0 +1,15 @@
|
|||
[remap]
|
||||
|
||||
importer="ogg_vorbis"
|
||||
type="AudioStreamOGGVorbis"
|
||||
path="res://.import/player_speak_2_1.ogg-a3ca3305840eb0aafe813f6b45f43c8d.oggstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://globals/assets/speech/fr/player_speak_2_1.ogg"
|
||||
dest_files=[ "res://.import/player_speak_2_1.ogg-a3ca3305840eb0aafe813f6b45f43c8d.oggstr" ]
|
||||
|
||||
[params]
|
||||
|
||||
loop=true
|
||||
loop_offset=0
|
BIN
device/globals/assets/speech/fr/player_speak_2_2.ogg
Normal file
BIN
device/globals/assets/speech/fr/player_speak_2_2.ogg
Normal file
Binary file not shown.
15
device/globals/assets/speech/fr/player_speak_2_2.ogg.import
Normal file
15
device/globals/assets/speech/fr/player_speak_2_2.ogg.import
Normal file
|
@ -0,0 +1,15 @@
|
|||
[remap]
|
||||
|
||||
importer="ogg_vorbis"
|
||||
type="AudioStreamOGGVorbis"
|
||||
path="res://.import/player_speak_2_2.ogg-8b81201a31f56a692ca3155ecd36e501.oggstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://globals/assets/speech/fr/player_speak_2_2.ogg"
|
||||
dest_files=[ "res://.import/player_speak_2_2.ogg-8b81201a31f56a692ca3155ecd36e501.oggstr" ]
|
||||
|
||||
[params]
|
||||
|
||||
loop=true
|
||||
loop_offset=0
|
BIN
device/globals/assets/speech/fr/player_speak_3_1.ogg
Normal file
BIN
device/globals/assets/speech/fr/player_speak_3_1.ogg
Normal file
Binary file not shown.
15
device/globals/assets/speech/fr/player_speak_3_1.ogg.import
Normal file
15
device/globals/assets/speech/fr/player_speak_3_1.ogg.import
Normal file
|
@ -0,0 +1,15 @@
|
|||
[remap]
|
||||
|
||||
importer="ogg_vorbis"
|
||||
type="AudioStreamOGGVorbis"
|
||||
path="res://.import/player_speak_3_1.ogg-e611aa6a79b2e975dc1657ab2ad9f190.oggstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://globals/assets/speech/fr/player_speak_3_1.ogg"
|
||||
dest_files=[ "res://.import/player_speak_3_1.ogg-e611aa6a79b2e975dc1657ab2ad9f190.oggstr" ]
|
||||
|
||||
[params]
|
||||
|
||||
loop=true
|
||||
loop_offset=0
|
BIN
device/globals/assets/speech/fr/player_speak_4_1.ogg
Normal file
BIN
device/globals/assets/speech/fr/player_speak_4_1.ogg
Normal file
Binary file not shown.
15
device/globals/assets/speech/fr/player_speak_4_1.ogg.import
Normal file
15
device/globals/assets/speech/fr/player_speak_4_1.ogg.import
Normal file
|
@ -0,0 +1,15 @@
|
|||
[remap]
|
||||
|
||||
importer="ogg_vorbis"
|
||||
type="AudioStreamOGGVorbis"
|
||||
path="res://.import/player_speak_4_1.ogg-df1a79565682b2572d3db332ced70a48.oggstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://globals/assets/speech/fr/player_speak_4_1.ogg"
|
||||
dest_files=[ "res://.import/player_speak_4_1.ogg-df1a79565682b2572d3db332ced70a48.oggstr" ]
|
||||
|
||||
[params]
|
||||
|
||||
loop=true
|
||||
loop_offset=0
|
BIN
device/globals/assets/speech/fr/player_speak_4_2.ogg
Normal file
BIN
device/globals/assets/speech/fr/player_speak_4_2.ogg
Normal file
Binary file not shown.
15
device/globals/assets/speech/fr/player_speak_4_2.ogg.import
Normal file
15
device/globals/assets/speech/fr/player_speak_4_2.ogg.import
Normal file
|
@ -0,0 +1,15 @@
|
|||
[remap]
|
||||
|
||||
importer="ogg_vorbis"
|
||||
type="AudioStreamOGGVorbis"
|
||||
path="res://.import/player_speak_4_2.ogg-1b22b3091e7789a3d5f14827b230bc76.oggstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://globals/assets/speech/fr/player_speak_4_2.ogg"
|
||||
dest_files=[ "res://.import/player_speak_4_2.ogg-1b22b3091e7789a3d5f14827b230bc76.oggstr" ]
|
||||
|
||||
[params]
|
||||
|
||||
loop=true
|
||||
loop_offset=0
|
BIN
device/globals/assets/speech/fr/player_speak_5_1.ogg
Normal file
BIN
device/globals/assets/speech/fr/player_speak_5_1.ogg
Normal file
Binary file not shown.
15
device/globals/assets/speech/fr/player_speak_5_1.ogg.import
Normal file
15
device/globals/assets/speech/fr/player_speak_5_1.ogg.import
Normal file
|
@ -0,0 +1,15 @@
|
|||
[remap]
|
||||
|
||||
importer="ogg_vorbis"
|
||||
type="AudioStreamOGGVorbis"
|
||||
path="res://.import/player_speak_5_1.ogg-7a975be5a670b9220f3f093489c3cb99.oggstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://globals/assets/speech/fr/player_speak_5_1.ogg"
|
||||
dest_files=[ "res://.import/player_speak_5_1.ogg-7a975be5a670b9220f3f093489c3cb99.oggstr" ]
|
||||
|
||||
[params]
|
||||
|
||||
loop=true
|
||||
loop_offset=0
|
BIN
device/globals/assets/speech/fr/player_speak_5_2.ogg
Normal file
BIN
device/globals/assets/speech/fr/player_speak_5_2.ogg
Normal file
Binary file not shown.
15
device/globals/assets/speech/fr/player_speak_5_2.ogg.import
Normal file
15
device/globals/assets/speech/fr/player_speak_5_2.ogg.import
Normal file
|
@ -0,0 +1,15 @@
|
|||
[remap]
|
||||
|
||||
importer="ogg_vorbis"
|
||||
type="AudioStreamOGGVorbis"
|
||||
path="res://.import/player_speak_5_2.ogg-a0aa549d098f204d36a0159cfeb85619.oggstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://globals/assets/speech/fr/player_speak_5_2.ogg"
|
||||
dest_files=[ "res://.import/player_speak_5_2.ogg-a0aa549d098f204d36a0159cfeb85619.oggstr" ]
|
||||
|
||||
[params]
|
||||
|
||||
loop=true
|
||||
loop_offset=0
|
BIN
device/globals/assets/speech/fr/player_speak_6_1.ogg
Normal file
BIN
device/globals/assets/speech/fr/player_speak_6_1.ogg
Normal file
Binary file not shown.
15
device/globals/assets/speech/fr/player_speak_6_1.ogg.import
Normal file
15
device/globals/assets/speech/fr/player_speak_6_1.ogg.import
Normal file
|
@ -0,0 +1,15 @@
|
|||
[remap]
|
||||
|
||||
importer="ogg_vorbis"
|
||||
type="AudioStreamOGGVorbis"
|
||||
path="res://.import/player_speak_6_1.ogg-eba85ad2bc93334c86ba34ff7db5b83c.oggstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://globals/assets/speech/fr/player_speak_6_1.ogg"
|
||||
dest_files=[ "res://.import/player_speak_6_1.ogg-eba85ad2bc93334c86ba34ff7db5b83c.oggstr" ]
|
||||
|
||||
[params]
|
||||
|
||||
loop=true
|
||||
loop_offset=0
|
BIN
device/globals/assets/speech/fr/player_speak_6_2.ogg
Normal file
BIN
device/globals/assets/speech/fr/player_speak_6_2.ogg
Normal file
Binary file not shown.
15
device/globals/assets/speech/fr/player_speak_6_2.ogg.import
Normal file
15
device/globals/assets/speech/fr/player_speak_6_2.ogg.import
Normal file
|
@ -0,0 +1,15 @@
|
|||
[remap]
|
||||
|
||||
importer="ogg_vorbis"
|
||||
type="AudioStreamOGGVorbis"
|
||||
path="res://.import/player_speak_6_2.ogg-ee74e6f6fddf1b5b4631904a0716f3ff.oggstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://globals/assets/speech/fr/player_speak_6_2.ogg"
|
||||
dest_files=[ "res://.import/player_speak_6_2.ogg-ee74e6f6fddf1b5b4631904a0716f3ff.oggstr" ]
|
||||
|
||||
[params]
|
||||
|
||||
loop=true
|
||||
loop_offset=0
|
BIN
device/globals/assets/speech/fr/player_speak_7_1.ogg
Normal file
BIN
device/globals/assets/speech/fr/player_speak_7_1.ogg
Normal file
Binary file not shown.
15
device/globals/assets/speech/fr/player_speak_7_1.ogg.import
Normal file
15
device/globals/assets/speech/fr/player_speak_7_1.ogg.import
Normal file
|
@ -0,0 +1,15 @@
|
|||
[remap]
|
||||
|
||||
importer="ogg_vorbis"
|
||||
type="AudioStreamOGGVorbis"
|
||||
path="res://.import/player_speak_7_1.ogg-1a3fb2794d0772cef9b262a0e1156a02.oggstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://globals/assets/speech/fr/player_speak_7_1.ogg"
|
||||
dest_files=[ "res://.import/player_speak_7_1.ogg-1a3fb2794d0772cef9b262a0e1156a02.oggstr" ]
|
||||
|
||||
[params]
|
||||
|
||||
loop=true
|
||||
loop_offset=0
|
BIN
device/globals/assets/speech/fr/player_speak_7_2.ogg
Normal file
BIN
device/globals/assets/speech/fr/player_speak_7_2.ogg
Normal file
Binary file not shown.
15
device/globals/assets/speech/fr/player_speak_7_2.ogg.import
Normal file
15
device/globals/assets/speech/fr/player_speak_7_2.ogg.import
Normal file
|
@ -0,0 +1,15 @@
|
|||
[remap]
|
||||
|
||||
importer="ogg_vorbis"
|
||||
type="AudioStreamOGGVorbis"
|
||||
path="res://.import/player_speak_7_2.ogg-f6ee24a4eff25f046a3e575fd0228fcf.oggstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://globals/assets/speech/fr/player_speak_7_2.ogg"
|
||||
dest_files=[ "res://.import/player_speak_7_2.ogg-f6ee24a4eff25f046a3e575fd0228fcf.oggstr" ]
|
||||
|
||||
[params]
|
||||
|
||||
loop=true
|
||||
loop_offset=0
|
7
device/globals/assets/speech_locales.gd
Normal file
7
device/globals/assets/speech_locales.gd
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Define your game's speech/voice locales within the game
|
||||
# because ProjectSettings doesn't deal well with arrays.
|
||||
# These can be used in your game to change the spoken language.
|
||||
|
||||
#warning-ignore:unused_class_variable
|
||||
var speech_locales = ['fr']
|
||||
|
69
device/globals/background.gd
Normal file
69
device/globals/background.gd
Normal file
|
@ -0,0 +1,69 @@
|
|||
extends Sprite
|
||||
|
||||
signal left_click_on_bg
|
||||
signal right_click_on_bg # Connect this in your game/signal_script
|
||||
|
||||
export var action = "walk"
|
||||
var area
|
||||
|
||||
# Godot doesn't do doubleclicks so we must
|
||||
var last_lmb_dt = 0
|
||||
var waiting_dblclick = null # null or [pos, event]
|
||||
|
||||
func input(_viewport, event, _shape_idx):
|
||||
if event is InputEventMouseButton and event.pressed:
|
||||
# If we are hovering items, do not allow background to receive a click
|
||||
# and let the items sort out who's on top and gets to be `clicked`
|
||||
if vm.hover_stack:
|
||||
return
|
||||
|
||||
if event.is_action("game_general"):
|
||||
last_lmb_dt = 0
|
||||
waiting_dblclick = [get_global_mouse_position(), event]
|
||||
elif event.is_action("game_rmb"):
|
||||
emit_signal("right_click_on_bg", self, get_global_mouse_position(), event)
|
||||
|
||||
func get_action():
|
||||
return action
|
||||
|
||||
func _physics_process(dt):
|
||||
last_lmb_dt += dt
|
||||
|
||||
if waiting_dblclick and last_lmb_dt > vm.DOUBLECLICK_TIMEOUT:
|
||||
emit_signal("left_click_on_bg", self, waiting_dblclick[0], waiting_dblclick[1])
|
||||
last_lmb_dt = 0
|
||||
waiting_dblclick = null
|
||||
|
||||
func _enter_tree():
|
||||
# Use size of background texture to calculate collision shape
|
||||
var size = get_texture().get_size()
|
||||
|
||||
area = Area2D.new()
|
||||
var shape = RectangleShape2D.new()
|
||||
|
||||
var sid = area.create_shape_owner(area)
|
||||
|
||||
# Move origin of Area2D to center of Sprite
|
||||
var transform = area.shape_owner_get_transform(sid)
|
||||
transform.origin = size / 2
|
||||
area.shape_owner_set_transform(sid, transform)
|
||||
|
||||
# Set extents of RectangleShape2D to cover entire Sprite
|
||||
shape.set_extents(size / 2)
|
||||
area.shape_owner_add_shape(sid, shape)
|
||||
|
||||
add_child(area)
|
||||
|
||||
func _ready():
|
||||
var conn_err
|
||||
|
||||
conn_err = area.connect("input_event", self, "input")
|
||||
if conn_err:
|
||||
vm.report_errors("item", ["area.input_event -> input error: " + String(conn_err)])
|
||||
|
||||
conn_err = connect("left_click_on_bg", $"/root/scene/game", "ev_left_click_on_bg")
|
||||
if conn_err:
|
||||
vm.report_errors("item", ["left_click_on_bg -> ev_left_click_on_bg error: " + String(conn_err)])
|
||||
|
||||
add_to_group("background")
|
||||
|
41
device/globals/bg_music.gd
Normal file
41
device/globals/bg_music.gd
Normal file
|
@ -0,0 +1,41 @@
|
|||
extends Control
|
||||
|
||||
var stream
|
||||
|
||||
var state = "default"
|
||||
|
||||
export var global_id = "bg_music"
|
||||
|
||||
func game_cleared():
|
||||
set_state("off", true)
|
||||
self.disconnect("tree_exited", vm, "object_exit_scene")
|
||||
vm.register_object(global_id, self)
|
||||
|
||||
func set_state(p_state, p_force = false):
|
||||
# If already playing this stream, keep playing, unless p_force
|
||||
if p_state == state and not p_force and stream.is_playing():
|
||||
return
|
||||
|
||||
state = p_state
|
||||
|
||||
# If state is "off"/"default", turn off music
|
||||
if state == "off" or state == "default":
|
||||
stream.stream = null
|
||||
return
|
||||
|
||||
var resource = load(p_state)
|
||||
|
||||
stream.stream = resource
|
||||
|
||||
if stream.stream:
|
||||
resource.set_loop(true)
|
||||
stream.play()
|
||||
stream.volume_db = vm.settings.music_volume
|
||||
|
||||
func _ready():
|
||||
stream = $"stream"
|
||||
|
||||
vm.register_object(global_id, self)
|
||||
|
||||
add_to_group("game")
|
||||
|
31
device/globals/bg_music.tscn
Normal file
31
device/globals/bg_music.tscn
Normal file
|
@ -0,0 +1,31 @@
|
|||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://globals/bg_music.gd" type="Script" id=1]
|
||||
|
||||
[node name="bg_music" type="Control" index="0"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_right = 40.0
|
||||
margin_bottom = 40.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
script = ExtResource( 1 )
|
||||
global_id = "bg_music"
|
||||
|
||||
[node name="stream" type="AudioStreamPlayer" parent="." index="0"]
|
||||
|
||||
stream = null
|
||||
volume_db = 0.0
|
||||
pitch_scale = 1.0
|
||||
autoplay = false
|
||||
mix_target = 0
|
||||
bus = "Music"
|
||||
|
||||
|
31
device/globals/bg_snd.gd
Normal file
31
device/globals/bg_snd.gd
Normal file
|
@ -0,0 +1,31 @@
|
|||
extends Control
|
||||
|
||||
var stream
|
||||
|
||||
#warning-ignore:unused_class_variable
|
||||
var state = "default" # To play along with the `set_state` semantics
|
||||
|
||||
export var global_id = "bg_snd"
|
||||
|
||||
func game_cleared():
|
||||
stream.stream = null
|
||||
self.disconnect("tree_exited", vm, "object_exit_scene")
|
||||
vm.register_object(global_id, self)
|
||||
|
||||
func play_snd(p_snd, p_loop=false):
|
||||
var resource = load(p_snd)
|
||||
if !resource:
|
||||
vm.report_errors("bg_snd", ["play_snd resource not found " + p_snd])
|
||||
return
|
||||
stream.stream = resource
|
||||
stream.stream.set_loop(p_loop)
|
||||
stream.volume_db = vm.settings.sfx_volume
|
||||
stream.play()
|
||||
|
||||
func _ready():
|
||||
stream = $"stream"
|
||||
|
||||
vm.register_object(global_id, self)
|
||||
|
||||
add_to_group("game")
|
||||
|
31
device/globals/bg_snd.tscn
Normal file
31
device/globals/bg_snd.tscn
Normal file
|
@ -0,0 +1,31 @@
|
|||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://globals/bg_snd.gd" type="Script" id=1]
|
||||
|
||||
[node name="bg_snd" type="Control"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_right = 40.0
|
||||
margin_bottom = 40.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 2
|
||||
script = ExtResource( 1 )
|
||||
global_id = "bg_snd"
|
||||
|
||||
[node name="stream" type="AudioStreamPlayer" parent="." index="0"]
|
||||
|
||||
stream = null
|
||||
volume_db = 0.0
|
||||
pitch_scale = 1.0
|
||||
autoplay = false
|
||||
mix_target = 0
|
||||
bus = "SFX"
|
||||
|
||||
|
167
device/globals/camera.gd
Normal file
167
device/globals/camera.gd
Normal file
|
@ -0,0 +1,167 @@
|
|||
extends Camera2D
|
||||
|
||||
onready var tween = $"tween"
|
||||
|
||||
var default_limits = {} # This does not change once set
|
||||
|
||||
var speed = 0
|
||||
var target
|
||||
var target_pos
|
||||
|
||||
var zoom_time
|
||||
var zoom_target
|
||||
|
||||
# This is needed to adjust dialog positions and such, see dialog_instance.gd
|
||||
var zoom_transform
|
||||
|
||||
func set_limits(kwargs=null):
|
||||
if not kwargs:
|
||||
kwargs = {
|
||||
"limit_left": -10000,
|
||||
"limit_right": 10000,
|
||||
"limit_top": -10000,
|
||||
"limit_bottom": 10000,
|
||||
"set_default": false,
|
||||
}
|
||||
print_stack()
|
||||
|
||||
self.limit_left = kwargs["limit_left"]
|
||||
self.limit_right = kwargs["limit_right"]
|
||||
self.limit_top = kwargs["limit_top"]
|
||||
self.limit_bottom = kwargs["limit_bottom"]
|
||||
|
||||
if "set_default" in kwargs and kwargs["set_default"] and not default_limits:
|
||||
default_limits = kwargs
|
||||
|
||||
func resolve_target_pos():
|
||||
if typeof(target) == TYPE_VECTOR2:
|
||||
target_pos = target
|
||||
elif typeof(target) == TYPE_ARRAY:
|
||||
var count = 0
|
||||
|
||||
for obj in target:
|
||||
target_pos += obj.get_camera_pos()
|
||||
count += 1
|
||||
|
||||
# Let the error in if an empty array was passed (divzero)
|
||||
target_pos = target_pos / count
|
||||
else:
|
||||
target_pos = target.get_camera_pos()
|
||||
|
||||
return target_pos
|
||||
|
||||
func set_drag_margin_enabled(p_dm_h_enabled, p_dm_v_enabled):
|
||||
self.drag_margin_h_enabled = p_dm_h_enabled
|
||||
self.drag_margin_v_enabled = p_dm_v_enabled
|
||||
|
||||
func set_target(p_speed, p_target):
|
||||
speed = p_speed
|
||||
target = p_target
|
||||
|
||||
resolve_target_pos()
|
||||
|
||||
if speed == 0:
|
||||
self.global_position = target_pos
|
||||
else:
|
||||
var time = self.global_position.distance_to(target_pos) / speed
|
||||
|
||||
if tween.is_active():
|
||||
var tweenstat = String(tween.tell()) + "/" + String(tween.get_runtime())
|
||||
vm.report_warnings("camera", ["Tween still active running camera_set_target: " + tweenstat])
|
||||
tween.emit_signal("tween_completed")
|
||||
|
||||
tween.interpolate_property(self, "global_position", self.global_position, target_pos, time, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
|
||||
|
||||
tween.start()
|
||||
|
||||
func set_camera_zoom(p_zoom_level, p_time):
|
||||
if p_zoom_level <= 0.0:
|
||||
vm.report_errors("camera", ["Tried to set negative or zero zoom level"])
|
||||
|
||||
zoom_time = p_time
|
||||
zoom_target = Vector2(1, 1) * p_zoom_level
|
||||
|
||||
if zoom_time == 0:
|
||||
self.zoom = zoom_target
|
||||
else:
|
||||
if tween.is_active():
|
||||
var tweenstat = String(tween.tell()) + "/" + String(tween.get_runtime())
|
||||
vm.report_warnings("camera", ["Tween still active running camera_set_zoom: " + tweenstat])
|
||||
tween.emit_signal("tween_completed")
|
||||
|
||||
tween.interpolate_property(self, "zoom", self.zoom, zoom_target, zoom_time, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
|
||||
|
||||
tween.start()
|
||||
|
||||
func push(p_target, p_time, p_type):
|
||||
var time = float(p_time)
|
||||
var type = "TRANS_" + p_type
|
||||
|
||||
target = p_target
|
||||
|
||||
var camera_pos
|
||||
var camera_pos_coords
|
||||
if target.has_node("camera_pos"):
|
||||
camera_pos = target.get_node("camera_pos")
|
||||
camera_pos_coords = camera_pos.global_position
|
||||
else:
|
||||
camera_pos_coords = target.global_position
|
||||
|
||||
if time == 0:
|
||||
self.global_position = camera_pos_coords
|
||||
|
||||
if camera_pos and camera_pos is Camera2D:
|
||||
self.zoom = camera_pos.zoom
|
||||
else:
|
||||
if tween.is_active():
|
||||
var tweenstat = String(tween.tell()) + "/" + String(tween.get_runtime())
|
||||
vm.report_warnings("camera", ["Tween still active running camera_push: " + tweenstat])
|
||||
tween.emit_signal("tween_completed")
|
||||
|
||||
if camera_pos and camera_pos is Camera2D:
|
||||
tween.interpolate_property(self, "zoom", self.zoom, camera_pos.zoom, time, tween.get(type), Tween.EASE_IN_OUT)
|
||||
|
||||
tween.interpolate_property(self, "global_position", self.global_position, camera_pos_coords, time, tween.get(type), Tween.EASE_IN_OUT)
|
||||
|
||||
tween.start()
|
||||
|
||||
func shift(p_x, p_y, p_time, p_type):
|
||||
var x = int(p_x)
|
||||
var y = int(p_y)
|
||||
var time = float(p_time)
|
||||
var type = "TRANS_" + p_type
|
||||
|
||||
var new_pos = self.global_position + Vector2(x, y)
|
||||
|
||||
target = new_pos
|
||||
|
||||
if tween.is_active():
|
||||
var tweenstat = String(tween.tell()) + "/" + String(tween.get_runtime())
|
||||
vm.report_warnings("camera", ["Tween still active running camera_shift: " + tweenstat])
|
||||
tween.emit_signal("tween_completed")
|
||||
|
||||
tween.interpolate_property(self, "global_position", self.global_position, new_pos, time, tween.get(type), Tween.EASE_IN_OUT)
|
||||
|
||||
tween.start()
|
||||
|
||||
func target_reached(_obj=null, _key=null):
|
||||
tween.stop_all()
|
||||
|
||||
func _process(_delta):
|
||||
zoom_transform = self.get_canvas_transform()
|
||||
|
||||
if target and not tween.is_active():
|
||||
if typeof(target) == TYPE_VECTOR2 or typeof(target) == TYPE_ARRAY:
|
||||
self.global_position = resolve_target_pos()
|
||||
elif "moved" in target and target.moved:
|
||||
self.global_position = resolve_target_pos()
|
||||
|
||||
func _ready():
|
||||
# Init some kind of target if there is none
|
||||
if not target:
|
||||
target = vm.get_object("player")
|
||||
if not target:
|
||||
target = Vector2(0, 0)
|
||||
|
||||
tween.connect("tween_completed", self, "target_reached")
|
||||
|
34
device/globals/credits.gd
Normal file
34
device/globals/credits.gd
Normal file
|
@ -0,0 +1,34 @@
|
|||
extends Control
|
||||
|
||||
func close():
|
||||
main.menu_close(self)
|
||||
queue_free()
|
||||
|
||||
func menu_collapsed():
|
||||
close()
|
||||
|
||||
func input(event):
|
||||
if event.is_action("menu_request") && event.is_pressed() && !event.is_echo():
|
||||
close()
|
||||
|
||||
func back_pressed():
|
||||
close()
|
||||
|
||||
func _find_menu_buttons(node=self):
|
||||
for c in node.get_children():
|
||||
if c is preload("res://ui/menu_button.gd") or c is preload("res://ui/menu_texturebutton.gd"):
|
||||
var sighandler_name = c.name + "_pressed"
|
||||
|
||||
c.connect("pressed", self, sighandler_name)
|
||||
else:
|
||||
_find_menu_buttons(c)
|
||||
|
||||
func _ready():
|
||||
_find_menu_buttons()
|
||||
|
||||
add_to_group("ui")
|
||||
|
||||
get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFAULT, "ui", "language_changed")
|
||||
|
||||
main.menu_open(self)
|
||||
|
24
device/globals/dd_player.gd
Normal file
24
device/globals/dd_player.gd
Normal file
|
@ -0,0 +1,24 @@
|
|||
extends ResourcePreloader
|
||||
|
||||
func start(params, level):
|
||||
var type
|
||||
if params.size() < 2 || !has_resource(params[1]):
|
||||
type = "default"
|
||||
else:
|
||||
type = params[1]
|
||||
|
||||
type = type + ProjectSettings.get_setting("escoria/platform/dialog_type_suffix")
|
||||
|
||||
printt("******* instancing dialog ", type)
|
||||
|
||||
var type_resource = get_resource(type)
|
||||
# De-instantiated in dialog_dialog.gd stop() and game_cleared()
|
||||
var inst = type_resource.instance()
|
||||
get_parent().add_child(inst)
|
||||
|
||||
# check the type and instance it here?
|
||||
inst.call_deferred("start", params, level)
|
||||
|
||||
func _ready():
|
||||
add_to_group("dialog_dialog")
|
||||
|
175
device/globals/dialog_dialog.gd
Normal file
175
device/globals/dialog_dialog.gd
Normal file
|
@ -0,0 +1,175 @@
|
|||
extends Control
|
||||
|
||||
export(bool) var use_mouse_enter_shadow = true
|
||||
export(bool) var use_mouse_exit_shadow = true
|
||||
export(Color) var mouse_enter_color = Color(1, 1, 1)
|
||||
export(Color) var mouse_enter_shadow_color = Color(0.6, 0.4, 0)
|
||||
export(Color) var mouse_exit_color = Color(1, 1, 1)
|
||||
export(Color) var mouse_exit_shadow_color = Color(1, 1, 1)
|
||||
|
||||
var cmd
|
||||
var container
|
||||
var context
|
||||
var item
|
||||
var animation
|
||||
var ready = false
|
||||
var option_selected
|
||||
var timer
|
||||
var timer_timeout = 0
|
||||
var timeout_option = 0
|
||||
|
||||
func selected(n):
|
||||
if !ready:
|
||||
return
|
||||
option_selected = n
|
||||
animation.play("hide")
|
||||
ready = false
|
||||
if timer != null:
|
||||
timer.stop()
|
||||
animation.set_speed(1)
|
||||
|
||||
func timer_timed_out():
|
||||
selected(timeout_option)
|
||||
|
||||
# called from global_vm.gd::dialog() function
|
||||
func start(params, p_context):
|
||||
printt("dialog start with params ", params.size())
|
||||
context = p_context
|
||||
cmd = params[0]
|
||||
var i = 0
|
||||
for q in cmd:
|
||||
if !vm.test(q):
|
||||
i+=1
|
||||
continue
|
||||
var it = item.duplicate()
|
||||
var but = it.get_node("button")
|
||||
var label = but.get_node("label")
|
||||
|
||||
var force_ids = ProjectSettings.get_setting("escoria/platform/force_text_ids")
|
||||
var tag_untranslated = ProjectSettings.get_setting("escoria/debug/tag_untranslated_strings")
|
||||
var text = q.params[0]
|
||||
var sep = text.find(":\"")
|
||||
if sep > 0:
|
||||
var tid = text.substr(0, sep)
|
||||
text = text.substr(sep + 2, text.length() - (sep + 2))
|
||||
|
||||
if TranslationServer.get_locale() != ProjectSettings.get_setting("escoria/platform/development_lang"):
|
||||
var ptext = TranslationServer.translate(tid)
|
||||
# An untranslated string comes back as it was, so we must tag it as not translated
|
||||
if ptext != tid and ptext != text:
|
||||
text = ptext
|
||||
elif tag_untranslated:
|
||||
text = "(NOT TRANSLATED)\n\n" + text
|
||||
|
||||
elif force_ids:
|
||||
vm.report_errors("dialog_dialog", ["Missing text_id for string '" + text + "'"])
|
||||
text = "(no id) " + text
|
||||
|
||||
if label is RichTextLabel and label.bbcode_enabled:
|
||||
label.bbcode_text = text
|
||||
else:
|
||||
label.text = text
|
||||
|
||||
but.connect("pressed", self, "selected", [i])
|
||||
label.connect("mouse_entered", self, "_on_mouse_enter", [label])
|
||||
label.connect("mouse_exited", self, "_on_mouse_exit", [label])
|
||||
|
||||
var height_ratio = ProjectSettings.get_setting("escoria/platform/dialog_option_height")
|
||||
var size = it.get_custom_minimum_size()
|
||||
size.y = size.y * height_ratio
|
||||
it.set_custom_minimum_size(size)
|
||||
|
||||
container.add_child(it)
|
||||
if i == 0:
|
||||
but.grab_focus()
|
||||
i+=1
|
||||
|
||||
_on_mouse_exit(label)
|
||||
|
||||
if has_node("wrapper/avatars"):
|
||||
var avatar = "default"
|
||||
if params.size() >= 3:
|
||||
avatar = params[2]
|
||||
var avatars = get_node("wrapper/avatars")
|
||||
for i in range(avatars.get_child_count()):
|
||||
var c = avatars.get_child(i)
|
||||
if c.get_name() == avatar:
|
||||
c.show()
|
||||
else:
|
||||
c.hide()
|
||||
|
||||
timer_timeout = 0
|
||||
timeout_option = 0
|
||||
if params.size() >= 4:
|
||||
timer_timeout = float(params[3])
|
||||
if params.size() >= 5:
|
||||
timeout_option = int(params[4])
|
||||
|
||||
if timer_timeout > 0:
|
||||
printt("********* dialog has timeout", timer_timeout, timeout_option)
|
||||
timer = Timer.new()
|
||||
add_child(timer)
|
||||
timer.set_one_shot(true)
|
||||
timer.set_wait_time(timer_timeout)
|
||||
timer.connect("timeout", self, "timer_timed_out")
|
||||
#timer.start()
|
||||
|
||||
ready = false
|
||||
animation.play("show")
|
||||
animation.seek(0, true)
|
||||
|
||||
func _on_mouse_enter(label):
|
||||
label.set("custom_colors/default_color", mouse_enter_color)
|
||||
if use_mouse_enter_shadow:
|
||||
label.set("custom_colors/font_color_shadow", mouse_enter_shadow_color)
|
||||
|
||||
func _on_mouse_exit(label):
|
||||
label.set("custom_colors/default_color", mouse_exit_color)
|
||||
if use_mouse_exit_shadow:
|
||||
label.set("custom_colors/font_color_shadow", mouse_exit_shadow_color)
|
||||
|
||||
func stop():
|
||||
hide()
|
||||
while container.get_child_count() > 0:
|
||||
var c = container.get_child(0)
|
||||
container.remove_child(c)
|
||||
c.free()
|
||||
vm.request_autosave()
|
||||
queue_free()
|
||||
|
||||
func game_cleared():
|
||||
queue_free()
|
||||
|
||||
func anim_finished(anim_name):
|
||||
if anim_name == "show":
|
||||
ready = true
|
||||
if timer_timeout > 0:
|
||||
timer.start()
|
||||
if animation.has_animation("timer"):
|
||||
animation.set_current_animation("timer")
|
||||
var length = animation.get_current_animation_length()
|
||||
animation.set_speed(length / timer_timeout)
|
||||
animation.play()
|
||||
|
||||
if anim_name == "hide":
|
||||
vm.finished(context)
|
||||
var ev_level = cmd[option_selected].params[1]
|
||||
var ev = vm.compiler.EscoriaEvent.new("dialog_choice_done", ev_level, [])
|
||||
vm.add_level(ev, false)
|
||||
stop()
|
||||
|
||||
func _ready():
|
||||
printt("dialog ready")
|
||||
hide()
|
||||
container = get_node("wrapper/margin/scroll/container")
|
||||
container.set_mouse_filter(MOUSE_FILTER_PASS)
|
||||
add_to_group("dialog_dialog")
|
||||
item = get_node("item")
|
||||
item.get_node("button/label").set_mouse_filter(MOUSE_FILTER_PASS)
|
||||
item.get_node("button").set_mouse_filter(MOUSE_FILTER_PASS)
|
||||
item.set_mouse_filter(MOUSE_FILTER_PASS)
|
||||
call_deferred("remove_child", item)
|
||||
animation = get_node("animation")
|
||||
animation.connect("animation_finished", self, "anim_finished")
|
||||
add_to_group("game")
|
||||
|
319
device/globals/dialog_instance.gd
Normal file
319
device/globals/dialog_instance.gd
Normal file
|
@ -0,0 +1,319 @@
|
|||
extends Node2D
|
||||
|
||||
var animation
|
||||
|
||||
var context
|
||||
var text
|
||||
var elapsed = 0
|
||||
var total_time
|
||||
var character
|
||||
export var typewriter_text = true
|
||||
export var characters_per_second = 45.0
|
||||
var force_disable_typewriter_text = ProjectSettings.get_setting("escoria/platform/force_disable_typewriter_text")
|
||||
var finished = false
|
||||
var play_intro = true
|
||||
var play_outro = true
|
||||
var label
|
||||
var text_done = false
|
||||
var text_timeout_seconds = ProjectSettings.get_setting("escoria/application/text_timeout_seconds")
|
||||
|
||||
var speech_stream
|
||||
var speech_player
|
||||
var speech_suffix
|
||||
var speech_paused = false
|
||||
|
||||
var dialog_pos # The designated position
|
||||
var pos # The transformed position
|
||||
var prev_pos # Check this to see if `character` moved and reposition
|
||||
|
||||
var dialog_min_vis = ProjectSettings.get_setting("escoria/application/dialog_minimum_visible_seconds")
|
||||
|
||||
var damp_db = ProjectSettings.get_setting("escoria/application/dialog_damp_music_by_db")
|
||||
onready var bg_music = $"/root/main/layers/telon/bg_music/stream"
|
||||
|
||||
export var fixed_pos = false
|
||||
|
||||
func _process(time):
|
||||
if finished:
|
||||
return
|
||||
|
||||
if not fixed_pos:
|
||||
dialog_pos = character.get_dialog_pos()
|
||||
pos = vm.camera.zoom_transform.xform(dialog_pos)
|
||||
|
||||
if prev_pos != pos:
|
||||
set_position(pos)
|
||||
prev_pos = pos
|
||||
|
||||
if force_disable_typewriter_text or !typewriter_text:
|
||||
label.set_visible_characters(label.get_total_character_count())
|
||||
text_done = true
|
||||
|
||||
elapsed += time
|
||||
|
||||
# For eg. a short fallback dialog, leave the text for it visible. The player
|
||||
# will be able to skip it anyway.
|
||||
if dialog_min_vis and elapsed < dialog_min_vis:
|
||||
return
|
||||
|
||||
if !text_done:
|
||||
if elapsed >= total_time:
|
||||
label.set_visible_characters(label.get_total_character_count())
|
||||
text_done = true
|
||||
|
||||
return
|
||||
else:
|
||||
label.set_visible_characters(text.length() * elapsed / (total_time))
|
||||
pass
|
||||
|
||||
if text_done && !vm.settings.skip_dialog:
|
||||
finish()
|
||||
return
|
||||
|
||||
if elapsed > text_timeout_seconds and (!speech_player or !speech_player.is_playing()):
|
||||
finish()
|
||||
return
|
||||
|
||||
if vm.settings.skip_dialog && speech_stream != null && !speech_player.is_playing() && text_done:
|
||||
finish()
|
||||
return
|
||||
|
||||
func skipped():
|
||||
if finished:
|
||||
return
|
||||
if elapsed < total_time:
|
||||
elapsed = total_time
|
||||
else:
|
||||
finish()
|
||||
|
||||
func finish():
|
||||
character.set_speaking(false)
|
||||
set_process(false)
|
||||
finished = true
|
||||
|
||||
if bg_music.is_playing():
|
||||
bg_music.volume_db += damp_db
|
||||
|
||||
if animation and play_outro:
|
||||
if animation.has_animation("hide"):
|
||||
animation.play("hide")
|
||||
elif not animation:
|
||||
_queue_free()
|
||||
|
||||
func _clamp(p_dialog_pos):
|
||||
var width = float(ProjectSettings.get("display/window/size/width"))
|
||||
var height = float(ProjectSettings.get("display/window/size/height"))
|
||||
var my_size = $"anchor/text".get_size()
|
||||
var center_offset = my_size.x / 2
|
||||
|
||||
var dist_from_right = width - (p_dialog_pos.x + center_offset)
|
||||
var dist_from_left = p_dialog_pos.x - center_offset
|
||||
var dist_from_bottom = height - (p_dialog_pos.y + my_size.y)
|
||||
var dist_from_top = p_dialog_pos.y - my_size.y
|
||||
|
||||
if dist_from_right < 0:
|
||||
p_dialog_pos.x += dist_from_right
|
||||
if dist_from_left < 0:
|
||||
p_dialog_pos.x -= dist_from_left
|
||||
if dist_from_bottom < 0:
|
||||
p_dialog_pos.y += dist_from_bottom
|
||||
if dist_from_top < 0:
|
||||
p_dialog_pos.y -= dist_from_top
|
||||
|
||||
return p_dialog_pos
|
||||
|
||||
func init(p_params, p_context, p_intro, p_outro):
|
||||
character = vm.get_object(p_params[0])
|
||||
context = p_context
|
||||
text = p_params[1]
|
||||
var force_ids = ProjectSettings.get_setting("escoria/platform/force_text_ids")
|
||||
var tag_untranslated = ProjectSettings.get_setting("escoria/debug/tag_untranslated_strings")
|
||||
var sep = text.find(":\"")
|
||||
var text_id = null
|
||||
if sep > 0:
|
||||
text_id = text.substr(0, sep)
|
||||
text = text.substr(sep + 2, text.length() - (sep + 2))
|
||||
|
||||
if TranslationServer.get_locale() != ProjectSettings.get_setting("escoria/platform/development_lang"):
|
||||
var ptext = TranslationServer.translate(text_id)
|
||||
# An untranslated string comes back as it was, so we must tag it as not translated
|
||||
if ptext != text_id and ptext != text:
|
||||
text = ptext
|
||||
elif tag_untranslated:
|
||||
text = "(NOT TRANSLATED)\n\n" + text
|
||||
|
||||
elif force_ids:
|
||||
vm.report_errors("dialog_instance", ["Missing text_id for string '" + text + "'"])
|
||||
text = "(no id) " + text
|
||||
|
||||
# This BBCode may be the only way to center text for a RichTextLabel
|
||||
if ProjectSettings.get_setting("escoria/platform/dialog_force_centered"):
|
||||
text = "[center]" + text + "[/center]"
|
||||
|
||||
play_intro = p_intro
|
||||
play_outro = p_outro
|
||||
total_time = text.length() / characters_per_second
|
||||
if !fixed_pos:
|
||||
dialog_pos = character.get_dialog_pos()
|
||||
|
||||
pos = vm.camera.zoom_transform.xform(dialog_pos)
|
||||
set_position(pos)
|
||||
prev_pos = pos
|
||||
|
||||
if has_node("anchor/avatars"):
|
||||
var avatars = get_node("anchor/avatars")
|
||||
if avatars.get_child_count() > 0:
|
||||
var name
|
||||
if p_params.size() < 4:
|
||||
name = avatars.get_child(0).get_name()
|
||||
else:
|
||||
name = p_params[3]
|
||||
for i in range(0, avatars.get_child_count()):
|
||||
var c = avatars.get_child(i)
|
||||
if c.get_name() == name:
|
||||
c.show()
|
||||
else:
|
||||
c.hide()
|
||||
|
||||
character.set_speaking(true)
|
||||
get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFAULT, "game", "set_mode", "dialog")
|
||||
if animation and play_intro:
|
||||
if animation.has_animation("show"):
|
||||
animation.play("show")
|
||||
animation.seek(0, true)
|
||||
else:
|
||||
show()
|
||||
set_process(true)
|
||||
else:
|
||||
show()
|
||||
set_process(true)
|
||||
|
||||
if character.dialog_color:
|
||||
label["custom_colors/default_color"] = character.dialog_color
|
||||
|
||||
label.bbcode_enabled = true
|
||||
|
||||
var parsed_ok = label.parse_bbcode(text)
|
||||
|
||||
if parsed_ok != OK:
|
||||
vm.report_errors("dialog_instance", ["Label failed parsing bbcode: " + text])
|
||||
|
||||
label.bbcode_text = "\n" + text
|
||||
label.set_visible_characters(0) # This length is always adjusted later
|
||||
|
||||
set_z_index(1)
|
||||
|
||||
setup_speech(text_id)
|
||||
|
||||
func setup_speech(tid):
|
||||
if tid == null || tid == "":
|
||||
return
|
||||
|
||||
if !vm.settings.speech_enabled:
|
||||
return
|
||||
|
||||
var use_binaural = ProjectSettings.get_setting("escoria/application/binaural_speech")
|
||||
|
||||
var speech_path = ProjectSettings.get_setting("escoria/application/speech_path")
|
||||
var fname = speech_path + vm.settings.voice_lang + "/" + tid + speech_suffix
|
||||
printt(" ** loading speech ", fname)
|
||||
speech_stream = load(fname)
|
||||
if !speech_stream:
|
||||
printt("*** unable to load speech stream ", fname)
|
||||
return
|
||||
|
||||
speech_stream.set_loop(false)
|
||||
|
||||
if use_binaural:
|
||||
speech_player = AudioStreamPlayer2D.new()
|
||||
else:
|
||||
speech_player = AudioStreamPlayer.new()
|
||||
|
||||
speech_player.set_name("speech_player_" + character.global_id if "global_id" in character else "player")
|
||||
speech_player.bus = "Speech"
|
||||
|
||||
if use_binaural:
|
||||
character.add_child(speech_player)
|
||||
else:
|
||||
add_child(speech_player)
|
||||
|
||||
speech_player.set_stream(speech_stream)
|
||||
speech_player.volume_db = vm.settings.voice_volume * ProjectSettings.get_setting("escoria/application/max_voice_volume")
|
||||
|
||||
if bg_music.is_playing():
|
||||
bg_music.volume_db -= damp_db
|
||||
|
||||
speech_player.play()
|
||||
|
||||
# XXX: For whatever reason the AudioStreamPlayer2D does not appear to be playing, but it actually is
|
||||
if not use_binaural and not speech_player.is_playing():
|
||||
print(" *** not playing? :(")
|
||||
# error?
|
||||
speech_stream = null
|
||||
speech_player.free()
|
||||
return
|
||||
|
||||
#total_time = speech_stream.get_length() * 0.8 + 1.5
|
||||
#printt("total time ", total_time, speech_stream.get_length())
|
||||
|
||||
func game_paused(p_pause):
|
||||
if speech_stream == null || speech_player == null:
|
||||
return
|
||||
|
||||
if p_pause:
|
||||
if speech_player.is_playing():
|
||||
speech_player.set_paused(true)
|
||||
speech_paused = true
|
||||
else:
|
||||
if speech_paused:
|
||||
speech_player.set_paused(false)
|
||||
|
||||
|
||||
func _queue_free():
|
||||
if speech_player != null:
|
||||
speech_player.free()
|
||||
|
||||
get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFAULT, "game", "set_mode", "default")
|
||||
vm.finished(context)
|
||||
queue_free()
|
||||
|
||||
|
||||
#warning-ignore:unused_argument
|
||||
func anim_finished(anim_name):
|
||||
# TODO use the parameter here?
|
||||
var cur = animation.get_current_animation()
|
||||
if cur == "show":
|
||||
set_process(true)
|
||||
if cur == "hide":
|
||||
_queue_free()
|
||||
|
||||
func set_position(p_pos):
|
||||
.set_position(_clamp(p_pos))
|
||||
|
||||
func _ready():
|
||||
var conn_err
|
||||
|
||||
speech_suffix = ProjectSettings.get_setting("escoria/application/speech_suffix")
|
||||
add_to_group("events")
|
||||
if has_node("animation"):
|
||||
animation = $"animation"
|
||||
|
||||
conn_err = animation.connect("animation_finished", self, "anim_finished")
|
||||
if conn_err:
|
||||
vm.report_errors("dialog_instance", ["animation_finished -> anim_finished error: " + String(conn_err)])
|
||||
|
||||
label = get_node("anchor/text")
|
||||
|
||||
# Ensure a supported speech locale has been set, or not set if no speech is desired
|
||||
var speech_locales_path = ProjectSettings.get_setting("escoria/application/speech_locales_path")
|
||||
if speech_locales_path:
|
||||
var speech_locales_def = load(speech_locales_path).new()
|
||||
|
||||
if not vm.settings.voice_lang in speech_locales_def.speech_locales:
|
||||
vm.report_errors("dialog_instance", ["Settings voice_lang not in defined speech locales: " + vm.settings.voice_lang])
|
||||
|
||||
conn_err = vm.connect("paused", self, "game_paused")
|
||||
if conn_err:
|
||||
vm.report_errors("dialog_instance", ["paused -> game_paused error: " + String(conn_err)])
|
||||
|
||||
|
51
device/globals/dialog_player.gd
Normal file
51
device/globals/dialog_player.gd
Normal file
|
@ -0,0 +1,51 @@
|
|||
extends ResourcePreloader
|
||||
|
||||
var types = {}
|
||||
|
||||
func say(params, callback):
|
||||
var character = vm.get_object(params[0])
|
||||
|
||||
var type
|
||||
if params.size() < 3 || !has_resource(params[2]):
|
||||
type = "default"
|
||||
else:
|
||||
type = params[2]
|
||||
|
||||
# Zooming will also affect the type. But account for the fact floats are actual floats and hard to compare.
|
||||
var zoom_diff = abs(vm.camera.zoom.x - $"/root/scene".default_zoom)
|
||||
var need_zoomed = round(zoom_diff) > 0
|
||||
|
||||
# Check if we have an inventory, because it might affect dialog positioning
|
||||
if (vm.inventory and vm.inventory.blocks_tooltip()) or need_zoomed:
|
||||
type = "bottom"
|
||||
# Same for a hidden character
|
||||
elif not character.visible:
|
||||
type = "bottom"
|
||||
|
||||
type = type + ProjectSettings.get_setting("escoria/platform/dialog_type_suffix")
|
||||
|
||||
# De-instantiated in dialog_instance.gd _queue_free()
|
||||
var inst = get_resource(type).instance()
|
||||
var z = inst.get_z_index()
|
||||
|
||||
if (vm.inventory and vm.inventory.blocks_tooltip()) or need_zoomed:
|
||||
inst.fixed_pos = true
|
||||
elif not character.visible:
|
||||
inst.fixed_pos = true
|
||||
|
||||
$"/root/scene/game/dialog_layer".add_child(inst)
|
||||
|
||||
var intro = true
|
||||
var outro = true
|
||||
if type in types:
|
||||
intro = types[type][0]
|
||||
outro = types[type][1]
|
||||
inst.init(params, callback, intro, outro)
|
||||
inst.set_z_index(z)
|
||||
|
||||
func config(params):
|
||||
types[params[0]] = [params[1], params[2]]
|
||||
|
||||
func _ready():
|
||||
add_to_group("dialog")
|
||||
|
7
device/globals/errors.gd
Normal file
7
device/globals/errors.gd
Normal file
|
@ -0,0 +1,7 @@
|
|||
|
||||
|
||||
func dialog_confirmed():
|
||||
queue_free()
|
||||
|
||||
func _ready():
|
||||
connect("confirmed", self, "dialog_confirmed")
|
456
device/globals/esc_compile.gd
Normal file
456
device/globals/esc_compile.gd
Normal file
|
@ -0,0 +1,456 @@
|
|||
class EscoriaEvent:
|
||||
var ev_name
|
||||
var ev_level
|
||||
var ev_flags
|
||||
|
||||
func _init(p_name, p_level, p_flags):
|
||||
ev_name = p_name
|
||||
ev_level = p_level
|
||||
ev_flags = p_flags
|
||||
|
||||
var commands = {
|
||||
"set_global": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING] },
|
||||
"dec_global": { "min_args": 2, "types": [TYPE_STRING, TYPE_INT] },
|
||||
"inc_global": { "min_args": 2, "types": [TYPE_STRING, TYPE_INT] },
|
||||
"set_globals": { "min_args": 2, "types": [TYPE_STRING, TYPE_BOOL] },
|
||||
"accept_input": { "min_args": 1, "types": [TYPE_STRING] },
|
||||
"debug": { "min_args": 1 },
|
||||
"anim": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING, TYPE_BOOL, TYPE_BOOL, TYPE_BOOL] },
|
||||
"play_snd": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING, TYPE_BOOL] },
|
||||
"set_state": { "min_args": 2 },
|
||||
"set_hud_visible": { "min_args": 1, "types": [TYPE_BOOL]},
|
||||
"say": { "min_args": 2 },
|
||||
"?": { "alias": "dialog"},
|
||||
"!": { "alias": "end_dialog", "min_args": 0 },
|
||||
"cut_scene": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING, TYPE_BOOL, TYPE_BOOL, TYPE_BOOL] },
|
||||
">": { "alias": "branch"},
|
||||
"inventory_add": { "min_args": 1 },
|
||||
"inventory_remove": { "min_args": 1 },
|
||||
"inventory_open": { "min_args": 1, "types": [TYPE_BOOL] },
|
||||
"set_active": { "min_args": 2, "types": [TYPE_STRING, TYPE_BOOL] },
|
||||
"set_interactive": { "min_args": 2, "types": [TYPE_STRING, TYPE_BOOL] },
|
||||
"stop": true,
|
||||
"repeat": true,
|
||||
"wait": true,
|
||||
"set_speed": { "min_args": 2, "types": [TYPE_STRING, TYPE_INT] },
|
||||
"teleport": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING, TYPE_INT] },
|
||||
"teleport_pos": { "min_args": 3 },
|
||||
"slide": { "min_args": 2 },
|
||||
"slide_block": { "min_args": 2 },
|
||||
"walk": { "min_args": 2 },
|
||||
"walk_block": { "min_args": 2 },
|
||||
"turn_to": { "min_args": 2 },
|
||||
"set_angle": { "min_args": 2 },
|
||||
"change_scene": { "min_args": 1, "types": [TYPE_STRING, TYPE_BOOL] },
|
||||
"spawn": { "min_args": 1 },
|
||||
"%": { "alias": "label", "min_args": 1},
|
||||
"jump": { "min_args": 1 },
|
||||
"dialog_config": { "min_args": 3, "types": [TYPE_STRING, TYPE_BOOL, TYPE_BOOL] },
|
||||
"sched_event": { "min_args": 3, "types": [TYPE_REAL, TYPE_STRING, TYPE_STRING] },
|
||||
"custom": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING] },
|
||||
"camera_set_drag_margin_enabled": { "min_args": 2, "types": [TYPE_BOOL, TYPE_BOOL] },
|
||||
"camera_set_target": { "min_args": 1, "types": [TYPE_REAL] },
|
||||
"camera_set_pos": { "min_args": 3, "types": [TYPE_REAL, TYPE_INT, TYPE_INT] },
|
||||
"camera_set_zoom": { "min_args": 1, "types": [TYPE_REAL] },
|
||||
"camera_set_zoom_height": { "min_args": 1, "types": [TYPE_INT] },
|
||||
"camera_push": { "min_args": 1, "types": [TYPE_STRING] },
|
||||
"camera_shift": { "min_args": 2, "types": [TYPE_INT, TYPE_INT] },
|
||||
"autosave": { "min_args": 0 },
|
||||
"queue_resource": { "min_args": 1, "types": [TYPE_STRING, TYPE_BOOL] },
|
||||
"queue_animation": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING, TYPE_BOOL] },
|
||||
"game_over": { "min_args": 1, "types": [TYPE_BOOL] },
|
||||
}
|
||||
|
||||
func check_command(cmd, state, errors):
|
||||
if !(cmd.name in commands):
|
||||
errors.push_back("line "+str(state.line_count)+": command "+cmd.name+" not valid.")
|
||||
return false
|
||||
|
||||
var cmd_data = commands[cmd.name]
|
||||
if typeof(cmd_data) == TYPE_BOOL:
|
||||
return true
|
||||
|
||||
if "alias" in cmd_data:
|
||||
cmd.name = cmd_data.alias
|
||||
|
||||
if "min_args" in cmd_data:
|
||||
if cmd.params.size() < cmd_data.min_args:
|
||||
errors.push_back("line "+str(state.line_count)+": command "+cmd.name+" takes "+str(cmd_data.min_args)+" parameters ("+str(cmd.params.size())+" were given).")
|
||||
return false
|
||||
|
||||
var ret = true
|
||||
if "types" in cmd_data:
|
||||
var i = 0
|
||||
for t in cmd_data.types:
|
||||
if i >= cmd.params.size():
|
||||
break
|
||||
if t == TYPE_BOOL:
|
||||
if cmd.params[i] == "true":
|
||||
cmd.params[i] = true
|
||||
elif cmd.params[i] == "false":
|
||||
cmd.params[i] = false
|
||||
else:
|
||||
errors.push_back("line " + str(state.line_count) + ": Invalid parameter " + cmd.params[i] + " for command " + cmd.name + ". Must be 'true' or 'false'.")
|
||||
ret = false
|
||||
if t == TYPE_INT:
|
||||
if not cmd.params[i].is_valid_integer():
|
||||
errors.push_back("line " + str(state.line_count) + ": Invalid parameter " + cmd.params[i] + " for command " + cmd.name + ". Expected integer.")
|
||||
cmd.params[i] = int(cmd.params[i])
|
||||
if t == TYPE_REAL:
|
||||
if not cmd.params[i].is_valid_float():
|
||||
errors.push_back("line " + str(state.line_count) + ": Invalid parameter " + cmd.params[i] + " for command " + cmd.name + ". Expected float.")
|
||||
cmd.params[i] = float(cmd.params[i])
|
||||
i+=1
|
||||
|
||||
return ret
|
||||
|
||||
func read_line(state):
|
||||
while true:
|
||||
if _eof_reached(state.file):
|
||||
state.line = null
|
||||
return
|
||||
else:
|
||||
state.line = _get_line(state.file)
|
||||
state.line_count += 1
|
||||
if !is_comment(state.line):
|
||||
return
|
||||
|
||||
func is_comment(line):
|
||||
for i in range(0, line.length()):
|
||||
var c = line[i]
|
||||
if c == "#":
|
||||
return true
|
||||
if c != " " && c != "\t":
|
||||
return false
|
||||
return true
|
||||
|
||||
func get_indent(line):
|
||||
for i in range(0, line.length()):
|
||||
if line[i] != " " && line[i] != "\t":
|
||||
return i
|
||||
|
||||
func is_event(line):
|
||||
var trimmed = trim(line)
|
||||
if trimmed.find(":") == 0:
|
||||
return trimmed.substr(1, trimmed.length()-1)
|
||||
return false
|
||||
|
||||
func is_flags(tk):
|
||||
var trimmed = trim(tk)
|
||||
if trimmed.find("[") == 0 && trimmed.find("]") == trimmed.length()-1:
|
||||
return true
|
||||
return false
|
||||
|
||||
func add_level(state, level, errors):
|
||||
read_line(state)
|
||||
while typeof(state.line) != typeof(null):
|
||||
if is_event(state.line):
|
||||
return
|
||||
var ind_level = get_indent(state.line)
|
||||
if ind_level < state.indent:
|
||||
return
|
||||
if ind_level > state.indent:
|
||||
errors.push_back("line "+str(state.line_count)+": invalid indentation for group")
|
||||
read_line(state)
|
||||
continue
|
||||
|
||||
read_cmd(state, level, errors)
|
||||
|
||||
func add_dialog(state, level, errors):
|
||||
read_line(state)
|
||||
|
||||
while typeof(state.line) != typeof(null):
|
||||
if is_event(state.line):
|
||||
return
|
||||
|
||||
var ind_level = get_indent(state.line)
|
||||
|
||||
if ind_level < state.indent:
|
||||
return
|
||||
|
||||
if ind_level > state.indent:
|
||||
errors.push_back("line "+str(state.line_count)+": invalid indentation for dialog")
|
||||
read_line(state)
|
||||
continue
|
||||
|
||||
read_dialog_option(state, level, errors)
|
||||
|
||||
func get_token(line, p_from, line_count, errors):
|
||||
while p_from < line.length():
|
||||
if line[p_from] == " " || line[p_from] == "\t":
|
||||
p_from += 1
|
||||
else:
|
||||
break
|
||||
if p_from >= line.length():
|
||||
return -1
|
||||
var tk_end
|
||||
if line[p_from] == "[":
|
||||
tk_end = line.find("]", p_from)
|
||||
if tk_end == -1:
|
||||
errors.push_back("line "+str(line_count)+": unterminated flags")
|
||||
tk_end += 1
|
||||
elif line[p_from] == "\"":
|
||||
tk_end = line.find("\"", p_from+1)
|
||||
if tk_end == -1:
|
||||
errors.push_back("line "+str(line_count)+": unterminated quotes, line '"+line+"'")
|
||||
else:
|
||||
tk_end = p_from
|
||||
while tk_end < line.length():
|
||||
if line[tk_end] == ":":
|
||||
var ntk = get_token(line, tk_end+1, line_count, errors)
|
||||
tk_end = ntk
|
||||
break
|
||||
if line[tk_end] == " " || line[tk_end] == "\t":
|
||||
break
|
||||
tk_end += 1
|
||||
return tk_end
|
||||
|
||||
func trim(p_str):
|
||||
while p_str.length() && (p_str[0] == " " || p_str[0] == "\t"):
|
||||
p_str = p_str.substr(1, p_str.length()-1)
|
||||
while p_str.length() && p_str[p_str.length()-1] == " " || p_str[p_str.length()-1] == "\t":
|
||||
p_str = p_str.substr(0, p_str.length()-1)
|
||||
|
||||
if p_str[0] == "\"":
|
||||
p_str = p_str.substr(1, p_str.length()-1)
|
||||
if p_str[p_str.length()-1] == "\"":
|
||||
p_str = p_str.substr(0, p_str.length()-1)
|
||||
return p_str
|
||||
|
||||
func parse_flags(p_flags, flags_list, ifs):
|
||||
var from = 1
|
||||
while true:
|
||||
var next = p_flags.find(",", from)
|
||||
var flag
|
||||
if next == -1:
|
||||
flag = p_flags.substr(from, (p_flags.length()-1) - from)
|
||||
else:
|
||||
flag = p_flags.substr(from, next - from)
|
||||
flag = trim(flag)
|
||||
var list = []
|
||||
|
||||
if flag[0] == "!":
|
||||
list.push_back(true)
|
||||
flag = trim(flag.substr(1, flag.length()-1))
|
||||
if flag.find("inv-") == 0:
|
||||
ifs["if_not_inv"].push_back(trim(flag).substr(4, flag.length()-1))
|
||||
elif flag.find("a/") == 0:
|
||||
ifs["if_not_active"].push_back(trim(flag).substr(2, flag.length() - 1))
|
||||
elif flag.substr(0, 3) in ["eq ", "gt ", "lt "]:
|
||||
var elems = flag.split(" ", true, 2)
|
||||
var comparison = "ne" if elems[0] == "eq" else "le" if elems[0] == "gt" else "ge"
|
||||
ifs["if_" + comparison].push_back([elems[1], elems[2]])
|
||||
else:
|
||||
ifs["if_false"].push_back(trim(flag))
|
||||
else:
|
||||
list.push_back(false)
|
||||
if flag.find("inv-") == 0:
|
||||
ifs["if_inv"].push_back(trim(flag).substr(4, flag.length()-1))
|
||||
elif flag.find("a/") == 0:
|
||||
ifs["if_active"].push_back(trim(flag).substr(2, flag.length() - 1))
|
||||
elif flag.substr(0, 3) in ["eq ", "gt ", "lt "]:
|
||||
var elems = flag.split(" ", true, 2)
|
||||
ifs["if_" + elems[0]].push_back([elems[1], elems[2]])
|
||||
else:
|
||||
ifs["if_true"].push_back(trim(flag))
|
||||
|
||||
if flag.find(":") >= 0:
|
||||
var pos = flag.substr(0, flag.find(":"))
|
||||
var inv = flag.substr(0, pos)
|
||||
inv = trim(inv)
|
||||
list.push_back(inv)
|
||||
flag = flag.substr(pos, flag.length() - pos)
|
||||
elif flag.find("inv-") == 0:
|
||||
flag = trim(flag).substr(4, flag.length()-1)
|
||||
list.push_back("i")
|
||||
else:
|
||||
list.push_back("g")
|
||||
|
||||
list.push_back(trim(flag))
|
||||
# printt("adding flag ", list)
|
||||
flags_list.push_back(list)
|
||||
if next == -1:
|
||||
return
|
||||
from = next+1
|
||||
|
||||
func read_dialog_option(state, level, errors):
|
||||
var tk_end = get_token(state.line, 0, state.line_count, errors)
|
||||
var tk = trim(state.line.substr(0, tk_end))
|
||||
if tk != "*" && tk != "-":
|
||||
errors.append("line "+str(state.line_count)+": Ivalid dialog option")
|
||||
read_line(state)
|
||||
return
|
||||
|
||||
# Remove inline comments
|
||||
var comment_idx = state.line.find("#")
|
||||
if comment_idx > -1:
|
||||
state.line = state.line.substr(0, comment_idx)
|
||||
|
||||
tk_end += 1
|
||||
# var c_start = state.line.find("\"", 0)
|
||||
var c_end = state.line.find_last("\"")
|
||||
var q_end = state.line.find("[", c_end)
|
||||
var q_flags = null
|
||||
#printt("flags before", q_flags)
|
||||
if q_end == -1:
|
||||
q_end = state.line.length()
|
||||
else:
|
||||
var f_end = state.line.find("]", q_end)
|
||||
if f_end == -1:
|
||||
errors.append("line "+str(state.line_count)+": unterminated flags")
|
||||
else:
|
||||
f_end += 1
|
||||
q_flags = state.line.substr(q_end, f_end - q_end)
|
||||
var question = trim(state.line.substr(tk_end, q_end - tk_end))
|
||||
var cmd = { "name": "*", "params": [question, []] }
|
||||
|
||||
if q_flags:
|
||||
var ifs = {
|
||||
"if_true": [], "if_false": [], "if_inv": [], "if_not_inv": [],
|
||||
"if_active": [], "if_not_active": [],
|
||||
"if_eq": [], "if_ne": [], # string and integer comparison
|
||||
"if_gt": [], "if_ge": [], "if_lt": [], "if_le": [] # integer comparison
|
||||
}
|
||||
var flag_list = []
|
||||
parse_flags(q_flags, flag_list, ifs)
|
||||
for key in ifs:
|
||||
if ifs[key].size():
|
||||
cmd[key] = ifs[key]
|
||||
if flag_list.size():
|
||||
cmd.flags = flag_list
|
||||
|
||||
state.indent += 1
|
||||
add_level(state, cmd.params[1], errors)
|
||||
state.indent -= 1
|
||||
|
||||
level.push_back(cmd)
|
||||
|
||||
func read_cmd(state, level, errors):
|
||||
var params = []
|
||||
var from = 0
|
||||
var tk_end = get_token(state.line, from, state.line_count, errors)
|
||||
var ifs = {
|
||||
"if_true": [], "if_false": [], "if_inv": [], "if_not_inv": [],
|
||||
"if_active": [], "if_not_active": [],
|
||||
"if_eq": [], "if_ne": [], # string and integer comparison
|
||||
"if_gt": [], "if_ge": [], "if_lt": [], "if_le": [] # integer comparison
|
||||
}
|
||||
var flags = []
|
||||
while tk_end != -1:
|
||||
var tk = trim(state.line.substr(from, tk_end - from))
|
||||
from = tk_end + 1
|
||||
if is_flags(tk):
|
||||
parse_flags(tk, flags, ifs)
|
||||
else:
|
||||
params.push_back(tk)
|
||||
tk_end = get_token(state.line, from, state.line_count, errors)
|
||||
|
||||
if params.size() == 0:
|
||||
errors.append("line "+str(state.line_count)+": Invalid command.")
|
||||
read_line(state)
|
||||
return
|
||||
|
||||
var cmd = {"name": params[0]}
|
||||
|
||||
if params[0] == ">":
|
||||
cmd.params = []
|
||||
state.indent += 1
|
||||
add_level(state, cmd.params, errors)
|
||||
state.indent -= 1
|
||||
elif params[0] == "?":
|
||||
params.remove(0)
|
||||
var dialog_params = []
|
||||
state.indent += 1
|
||||
add_dialog(state, dialog_params, errors)
|
||||
cmd.params = params
|
||||
cmd.params.insert(0, dialog_params)
|
||||
state.indent -= 1
|
||||
elif params[0] == "*":
|
||||
errors.push_back("line "+str(state.line_count)+": Invalid command: dialog option outside dialog")
|
||||
read_line(state)
|
||||
return
|
||||
else:
|
||||
params.remove(0)
|
||||
|
||||
# Remove inline comments
|
||||
var comment_idx = params.find("#")
|
||||
if comment_idx > -1:
|
||||
params.resize(comment_idx)
|
||||
|
||||
cmd.params = params
|
||||
read_line(state)
|
||||
|
||||
for key in ifs:
|
||||
if ifs[key].size():
|
||||
cmd[key] = ifs[key]
|
||||
if flags.size():
|
||||
cmd.flags = flags
|
||||
|
||||
var valid = check_command(cmd, state, errors)
|
||||
if valid:
|
||||
level.push_back(cmd)
|
||||
|
||||
func read_events(f, ret, errors):
|
||||
|
||||
var state = { "file": f, "line": _get_line(f), "indent": 0, "line_count": 0 }
|
||||
|
||||
while typeof(state.line) != typeof(null):
|
||||
if is_comment(state.line):
|
||||
read_line(state)
|
||||
continue
|
||||
var ev = is_event(state.line)
|
||||
if typeof(ev) != typeof(null):
|
||||
var level = []
|
||||
var abort = add_level(state, level, errors)
|
||||
var ev_flags = []
|
||||
if "|" in ev:
|
||||
var ev_split = ev.split("|", true, 1)
|
||||
ev = ev_split[0]
|
||||
ev = ev.strip_edges()
|
||||
if ev_split.size() > 1:
|
||||
ev_split[1] = ev_split[1].strip_edges()
|
||||
ev_flags = ev_split[1].split(" ")
|
||||
|
||||
ret[ev] = EscoriaEvent.new(ev, level, Array(ev_flags))
|
||||
if abort:
|
||||
return abort
|
||||
|
||||
func _get_line(f):
|
||||
if typeof(f) == typeof({}):
|
||||
if f.line >= f.lines.size():
|
||||
return null
|
||||
var line = f.lines[f.line]
|
||||
f.line += 1
|
||||
#printt("reading line ", line)
|
||||
return line
|
||||
else:
|
||||
return f.get_line()
|
||||
|
||||
func _eof_reached(f):
|
||||
if typeof(f) == typeof({}):
|
||||
return f.line >= f.lines.size()
|
||||
else:
|
||||
return f.eof_reached()
|
||||
|
||||
func compile_str(p_str, errors):
|
||||
var f = { "line": 0, "lines": p_str.split("\n") }
|
||||
|
||||
#printt("esc compile str ", f)
|
||||
|
||||
var ret = {}
|
||||
read_events(f, ret, errors)
|
||||
|
||||
#printt("returning ", p_fname, ret)
|
||||
return ret
|
||||
|
||||
func compile(p_fname, errors):
|
||||
var f = File.new()
|
||||
f.open(p_fname, File.READ)
|
||||
if !f.is_open():
|
||||
return {}
|
||||
|
||||
var ret = {}
|
||||
read_events(f, ret, errors)
|
||||
|
||||
#printt("returning ", p_fname, ret)
|
||||
return ret
|
15
device/globals/escoria_types.gd
Normal file
15
device/globals/escoria_types.gd
Normal file
|
@ -0,0 +1,15 @@
|
|||
extends Node
|
||||
|
||||
const ACTION_MENU = preload("res://globals/action_menu.gd")
|
||||
const INTERACTIVE = preload("res://globals/interactive.gd")
|
||||
const INVENTORY = preload("res://globals/inventory.gd")
|
||||
var ITEM = load("res://globals/item.gd")
|
||||
const NPC = preload("res://globals/npc.gd")
|
||||
const TRIGGER = preload("res://globals/trigger.gd")
|
||||
var EXIT = load("res://globals/exit.gd")
|
||||
const BACKGROUND = preload("res://globals/background.gd")
|
||||
const PLAYER = preload("res://globals/player.gd")
|
||||
const SCENE = preload("res://globals/scene.gd")
|
||||
const TARGET = preload("res://globals/target.gd")
|
||||
const TOOLTIP = preload("res://globals/tooltip.gd")
|
||||
|
16
device/globals/exit.gd
Normal file
16
device/globals/exit.gd
Normal file
|
@ -0,0 +1,16 @@
|
|||
extends "res://globals/trigger.gd"
|
||||
|
||||
func body_entered(body):
|
||||
# Entering an exit node runs the `:exit_scene` event
|
||||
if body is esc_type.PLAYER:
|
||||
if self.visible:
|
||||
run_event("exit_scene")
|
||||
|
||||
func body_exited(_body):
|
||||
# Do nothing when exiting an exit node. Usually this code should not be hit.
|
||||
return
|
||||
|
||||
func _ready():
|
||||
if not "exit_scene" in event_table:
|
||||
vm.report_errors("exit", [":exit_scene missing for " + self.name])
|
||||
|
790
device/globals/game.gd
Normal file
790
device/globals/game.gd
Normal file
|
@ -0,0 +1,790 @@
|
|||
extends Node
|
||||
|
||||
var player
|
||||
var mode = "default"
|
||||
export(String,FILE) var fallbacks_path = ""
|
||||
export var inventory_enabled = true setget set_inventory_enabled
|
||||
var fallbacks
|
||||
var check_joystick = false
|
||||
var joystick_mode = false
|
||||
var min_interact_dist = 50*50
|
||||
var last_obj = null
|
||||
|
||||
export var drag_margin_left = 0.2
|
||||
export var drag_margin_top = 0.2
|
||||
export var drag_margin_right = 0.2
|
||||
export var drag_margin_bottom = 0.2
|
||||
|
||||
var click
|
||||
var click_anim
|
||||
|
||||
var default_obj_action
|
||||
var obj_action_req_dblc
|
||||
|
||||
var camera
|
||||
export var camera_limits = Rect2()
|
||||
|
||||
var hud
|
||||
var action_menu
|
||||
|
||||
func set_mode(p_mode):
|
||||
mode = p_mode
|
||||
|
||||
func can_click():
|
||||
# Check certain global state to see if an object could be clicked
|
||||
|
||||
if !vm.can_interact():
|
||||
return false
|
||||
|
||||
if !player:
|
||||
return false
|
||||
|
||||
if mode != "default":
|
||||
return false
|
||||
|
||||
if vm.accept_input != vm.acceptable_inputs.INPUT_ALL:
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
func ev_left_click_on_bg(obj, pos, event):
|
||||
printt(obj.name, "left-clicked at", pos, "with", event, can_click())
|
||||
|
||||
if not can_click():
|
||||
return
|
||||
|
||||
if vm.action_menu:
|
||||
vm.action_menu.stop()
|
||||
|
||||
# If it's possible to click outside the inventory, don't walk but only close it
|
||||
if vm.inventory and vm.inventory.blocks_tooltip():
|
||||
vm.inventory.close()
|
||||
return
|
||||
|
||||
var walk_context = {"fast": event.doubleclick}
|
||||
|
||||
# Make it possible to abort an interaction before the player interacts
|
||||
if player.interact_status == player.interact_statuses.INTERACT_WALKING:
|
||||
# by overriding the interaction status
|
||||
player.interact_status = player.interact_statuses.INTERACT_NONE
|
||||
player.params_queue = null
|
||||
player.walk_to(pos, walk_context)
|
||||
return
|
||||
|
||||
if click:
|
||||
click.set_position(pos)
|
||||
if click_anim:
|
||||
click_anim.play("click")
|
||||
|
||||
player.walk_to(pos, walk_context)
|
||||
|
||||
if vm.tooltip:
|
||||
if not vm.current_tool:
|
||||
vm.tooltip.hide()
|
||||
|
||||
func ev_left_click_on_trigger(obj, pos, event):
|
||||
printt(obj.name, "left-clicked at", pos, "with", event, can_click())
|
||||
|
||||
if not can_click():
|
||||
return
|
||||
|
||||
# Do not allow input on triggers/exits with inventory open
|
||||
if vm.inventory and vm.inventory.blocks_tooltip():
|
||||
return
|
||||
|
||||
if vm.action_menu and vm.action_menu.is_visible():
|
||||
vm.action_menu.stop()
|
||||
|
||||
player.walk_to(pos)
|
||||
|
||||
func ev_left_dblclick_on_trigger(obj, pos, event):
|
||||
printt(obj.name, "left-dblclicked at", pos, "with", event, can_click())
|
||||
|
||||
if not can_click():
|
||||
return
|
||||
|
||||
if vm.action_menu and vm.action_menu.is_visible():
|
||||
vm.action_menu.stop()
|
||||
return
|
||||
|
||||
if obj.dblclick_teleport and obj.tooltip:
|
||||
player.set_position(pos)
|
||||
else:
|
||||
player.walk_to(pos, {"fast": true})
|
||||
|
||||
func ev_left_click_on_item(obj, pos, event):
|
||||
printt(obj.name, "left-clicked at", pos, "with", event, can_click())
|
||||
|
||||
if not can_click():
|
||||
return
|
||||
|
||||
if vm.action_menu and obj.use_action_menu:
|
||||
if !ProjectSettings.get_setting("escoria/ui/right_mouse_button_action_menu"):
|
||||
spawn_action_menu(obj)
|
||||
return
|
||||
elif vm.action_menu.is_visible():
|
||||
# XXX: Can't close action menu here or doubleclick would cause an action
|
||||
if obj == vm.hover_object:
|
||||
return
|
||||
else:
|
||||
vm.action_menu.stop()
|
||||
|
||||
if vm.inventory and vm.inventory.blocks_tooltip():
|
||||
vm.inventory.close()
|
||||
if vm.tooltip:
|
||||
vm.tooltip.update()
|
||||
return
|
||||
|
||||
# Many overlapping items can receive the click event,
|
||||
# but only the topmost is really `clicked`
|
||||
if not obj.clicked:
|
||||
return
|
||||
|
||||
var walk_context = {"fast": event.doubleclick}
|
||||
|
||||
# Make it possible to abort an interaction before the player interacts
|
||||
if player.interact_status == player.interact_statuses.INTERACT_WALKING:
|
||||
# by overriding the interaction status
|
||||
player.interact_status = player.interact_statuses.INTERACT_NONE
|
||||
player.params_queue = null
|
||||
player.walk_to(pos, walk_context)
|
||||
return
|
||||
|
||||
var obj_action = obj.get_action()
|
||||
var action = "walk"
|
||||
|
||||
# Start off by checking a non-doubleclick default action
|
||||
if not obj_action_req_dblc:
|
||||
if obj_action:
|
||||
action = obj_action
|
||||
elif default_obj_action:
|
||||
action = default_obj_action
|
||||
|
||||
if click:
|
||||
click.set_position(pos)
|
||||
if click_anim:
|
||||
click_anim.play("click")
|
||||
|
||||
if obj.has_method("get_interact_pos"):
|
||||
pos = obj.get_interact_pos()
|
||||
|
||||
player.walk_to(pos, walk_context)
|
||||
|
||||
# XXX: Interacting with current_tool etc should be a signal
|
||||
if action != "walk" or vm.current_action:
|
||||
if vm.inventory and not vm.inventory.blocks_tooltip():
|
||||
player.interact([obj, vm.current_action, vm.current_tool])
|
||||
|
||||
func ev_left_dblclick_on_item(obj, pos, event):
|
||||
printt(obj.name, "left-dblclicked at", pos, "with", event, can_click())
|
||||
|
||||
if not can_click():
|
||||
return
|
||||
|
||||
if vm.action_menu and vm.action_menu.is_visible():
|
||||
vm.action_menu.stop()
|
||||
return
|
||||
|
||||
var walk_context = {"fast": event.doubleclick}
|
||||
|
||||
# Make it possible to abort an interaction before the player interacts
|
||||
if player.interact_status == player.interact_statuses.INTERACT_WALKING:
|
||||
# by overriding the interaction status
|
||||
player.interact_status = player.interact_statuses.INTERACT_NONE
|
||||
player.params_queue = null
|
||||
player.walk_to(pos, walk_context)
|
||||
|
||||
var action = "walk"
|
||||
|
||||
# Cannot use the object action if an action and tool is selected,
|
||||
# because the event is probably not defined
|
||||
var obj_action
|
||||
if vm.current_action and vm.current_tool:
|
||||
obj_action = vm.current_action
|
||||
else:
|
||||
obj_action = obj.get_action()
|
||||
|
||||
# See if there's a doubleclick default action
|
||||
if obj_action_req_dblc:
|
||||
if obj_action:
|
||||
action = obj_action
|
||||
elif default_obj_action:
|
||||
action = default_obj_action
|
||||
|
||||
if click:
|
||||
click.set_position(pos)
|
||||
if click_anim:
|
||||
click_anim.play("click")
|
||||
|
||||
if action != "walk":
|
||||
# Resolve telekinesis
|
||||
if action in obj.event_table:
|
||||
if player.task == "walk":
|
||||
player.walk_stop(player.get_position())
|
||||
vm.clear_current_action()
|
||||
|
||||
player.interact([obj, action, vm.current_tool])
|
||||
else:
|
||||
player.walk_to(pos, walk_context)
|
||||
|
||||
func ev_right_click_on_item(obj, pos, event):
|
||||
printt(obj.name, "right-clicked at", pos, "with", event, can_click())
|
||||
|
||||
if not can_click():
|
||||
return
|
||||
|
||||
var inventory_open = vm.inventory and vm.inventory.blocks_tooltip()
|
||||
|
||||
if obj.use_action_menu and not inventory_open:
|
||||
if ProjectSettings.get_setting("escoria/ui/right_mouse_button_action_menu"):
|
||||
spawn_action_menu(obj)
|
||||
return
|
||||
elif inventory_open:
|
||||
vm.inventory.close()
|
||||
|
||||
func ev_left_click_on_inventory_item(obj, pos, event):
|
||||
printt(obj.name, "left-clicked at", pos, "with", event, can_click())
|
||||
|
||||
if not can_click():
|
||||
return
|
||||
|
||||
# Use and look are the only valid choices with an action menu
|
||||
if vm.action_menu:
|
||||
vm.set_current_action("use")
|
||||
|
||||
if vm.current_action == "use" and obj.use_combine:
|
||||
if not vm.current_tool:
|
||||
vm.set_current_tool(obj)
|
||||
elif vm.current_tool != obj:
|
||||
interact([obj, vm.current_action, vm.current_tool])
|
||||
|
||||
if vm.tooltip:
|
||||
vm.tooltip.update()
|
||||
|
||||
func ev_right_click_on_inventory_item(obj, pos, event):
|
||||
printt(obj.name, "right-clicked at", pos, "with", event, can_click())
|
||||
|
||||
if not can_click():
|
||||
return
|
||||
|
||||
if vm.current_tool:
|
||||
vm.clear_current_tool()
|
||||
if vm.tooltip:
|
||||
vm.tooltip.update()
|
||||
else:
|
||||
# Do not set `look` as permanent action
|
||||
interact([obj, "look"])
|
||||
# XXX: Moving the mouse during `:look` will cause the tooltip to disappear
|
||||
# so the following is a good-enough-for-now fix for it
|
||||
if vm.tooltip:
|
||||
vm.tooltip.update()
|
||||
|
||||
func ev_mouse_enter_item(obj):
|
||||
if obj.inventory:
|
||||
vm.report_errors("game", ["Mouse entered inventory non-inventory item: " + obj.global_id])
|
||||
|
||||
printt(obj.name, "mouse_enter_item")
|
||||
|
||||
vm.hover_push(obj)
|
||||
|
||||
if vm.tooltip:
|
||||
# XXX: The warning report may be removed if it turns out to be too annoying in practice
|
||||
if not obj.get_tooltip():
|
||||
# For a passive item, it's fine to set an empty tooltip, but if we have a passive and
|
||||
# an active esc_type.ITEM overlapping, say a window and a light that will move later,
|
||||
# the tooltip may be emptied by the light not having a tooltip. This is because the
|
||||
# `mouse_enter` events have no guaranteed order.
|
||||
vm.report_warnings("game", ["No tooltip for item " + obj.name])
|
||||
|
||||
func ev_mouse_enter_inventory_item(obj):
|
||||
if not vm.inventory:
|
||||
vm.report_errors("game", ["Mouse entered inventory item without inventory: " + obj.global_id])
|
||||
if not obj.inventory:
|
||||
vm.report_errors("game", ["Mouse entered non-inventory inventory item: " + obj.global_id])
|
||||
|
||||
printt(obj.name, "mouse_enter_inventory_item")
|
||||
|
||||
if vm.tooltip:
|
||||
# XXX: The warning report may be removed if it turns out to be too annoying in practice
|
||||
if not obj.get_tooltip():
|
||||
# For a passive item, it's fine to set an empty tooltip, but if we have a passive and
|
||||
# an active esc_type.ITEM overlapping, say a window and a light that will move later,
|
||||
# the tooltip may be emptied by the light not having a tooltip. This is because the
|
||||
# `mouse_enter` events have no guaranteed order.
|
||||
vm.report_warnings("game", ["No tooltip for item " + obj.name])
|
||||
|
||||
vm.hover_push(obj)
|
||||
|
||||
func ev_mouse_exit_item(obj):
|
||||
printt(obj.name, "mouse_exit_item")
|
||||
|
||||
vm.hover_pop(obj)
|
||||
|
||||
func ev_mouse_exit_inventory_item(obj):
|
||||
printt(obj.name, "mouse_exit_inventory_item")
|
||||
|
||||
vm.hover_pop(obj)
|
||||
|
||||
func ev_mouse_enter_trigger(obj):
|
||||
printt(obj.name, "mouse_enter_trigger")
|
||||
|
||||
vm.hover_push(obj)
|
||||
|
||||
if vm.tooltip:
|
||||
# XXX: The warning report may be removed if it turns out to be too annoying in practice
|
||||
if not obj.get_tooltip():
|
||||
# For a passive item, it's fine to set an empty tooltip, but if we have a passive and
|
||||
# an active esc_type.ITEM overlapping, say a window and a light that will move later,
|
||||
# the tooltip may be emptied by the light not having a tooltip. This is because the
|
||||
# `mouse_enter` events have no guaranteed order.
|
||||
vm.report_warnings("game", ["No tooltip for trigger " + obj.name])
|
||||
|
||||
func ev_mouse_exit_trigger(obj):
|
||||
printt(obj.name, "mouse_exit_trigger")
|
||||
|
||||
vm.hover_pop(obj)
|
||||
|
||||
func ev_mouse_enter_npc(obj):
|
||||
printt(obj.name, "mouse_enter_npc")
|
||||
|
||||
vm.hover_push(obj)
|
||||
|
||||
if vm.tooltip:
|
||||
# XXX: The warning report may be removed if it turns out to be too annoying in practice
|
||||
if not obj.get_tooltip():
|
||||
vm.report_warnings("game", ["No tooltip for npc " + obj.name])
|
||||
|
||||
func ev_left_click_on_npc(obj, pos, event):
|
||||
printt(obj.name, "left-clicked at", pos, "with", event, can_click())
|
||||
|
||||
if not can_click():
|
||||
return
|
||||
|
||||
if vm.action_menu and obj.use_action_menu:
|
||||
if !ProjectSettings.get_setting("escoria/ui/right_mouse_button_action_menu"):
|
||||
spawn_action_menu(obj)
|
||||
return
|
||||
elif vm.action_menu.is_visible():
|
||||
# XXX: Can't close action menu here or doubleclick would cause an action
|
||||
if obj == vm.hover_object:
|
||||
return
|
||||
else:
|
||||
vm.action_menu.stop()
|
||||
|
||||
if vm.inventory and vm.inventory.blocks_tooltip():
|
||||
vm.inventory.close()
|
||||
if vm.tooltip:
|
||||
vm.tooltip.update()
|
||||
return
|
||||
|
||||
# Many overlapping items can receive the click event,
|
||||
# but only the topmost is really `clicked`
|
||||
if not obj.clicked:
|
||||
return
|
||||
|
||||
var walk_context = {"fast": event.doubleclick}
|
||||
|
||||
# Make it possible to abort an interaction before the player interacts
|
||||
if player.interact_status == player.interact_statuses.INTERACT_WALKING:
|
||||
# by overriding the interaction status
|
||||
player.interact_status = player.interact_statuses.INTERACT_NONE
|
||||
player.params_queue = null
|
||||
player.walk_to(pos, walk_context)
|
||||
return
|
||||
|
||||
var obj_action = obj.get_action()
|
||||
var action = "walk"
|
||||
|
||||
# Start off by checking a non-doubleclick default action
|
||||
if not obj_action_req_dblc:
|
||||
if obj_action:
|
||||
action = obj_action
|
||||
elif default_obj_action:
|
||||
action = default_obj_action
|
||||
|
||||
if click:
|
||||
click.set_position(pos)
|
||||
if click_anim:
|
||||
click_anim.play("click")
|
||||
|
||||
if obj.has_method("get_interact_pos"):
|
||||
pos = obj.get_interact_pos()
|
||||
|
||||
player.walk_to(pos, walk_context)
|
||||
|
||||
# XXX: Interacting with current_tool etc should be a signal
|
||||
if action != "walk" or vm.current_action:
|
||||
if vm.inventory and not vm.inventory.blocks_tooltip():
|
||||
player.interact([obj, vm.current_action, vm.current_tool])
|
||||
|
||||
func ev_left_dblclick_on_npc(obj, pos, event):
|
||||
printt(obj.name, "left-dblclicked at", pos, "with", event, can_click())
|
||||
|
||||
if not can_click():
|
||||
return
|
||||
|
||||
if vm.action_menu and vm.action_menu.is_visible():
|
||||
vm.action_menu.stop()
|
||||
return
|
||||
|
||||
var walk_context = {"fast": event.doubleclick}
|
||||
|
||||
# Make it possible to abort an interaction before the player interacts
|
||||
if player.interact_status == player.interact_statuses.INTERACT_WALKING:
|
||||
# by overriding the interaction status
|
||||
player.interact_status = player.interact_statuses.INTERACT_NONE
|
||||
player.params_queue = null
|
||||
player.walk_to(pos, walk_context)
|
||||
|
||||
var action = "walk"
|
||||
|
||||
# Cannot use the object action if an action and tool is selected,
|
||||
# because the event is probably not defined
|
||||
var obj_action
|
||||
if vm.current_action and vm.current_tool:
|
||||
obj_action = vm.current_action
|
||||
else:
|
||||
obj_action = obj.get_action()
|
||||
|
||||
# See if there's a doubleclick default action
|
||||
if obj_action_req_dblc:
|
||||
if obj_action:
|
||||
action = obj_action
|
||||
elif default_obj_action:
|
||||
action = default_obj_action
|
||||
|
||||
if click:
|
||||
click.set_position(pos)
|
||||
if click_anim:
|
||||
click_anim.play("click")
|
||||
|
||||
if action != "walk":
|
||||
# Resolve telekinesis
|
||||
if action in obj.event_table:
|
||||
if player.task == "walk":
|
||||
player.walk_stop(player.get_position())
|
||||
vm.clear_current_action()
|
||||
|
||||
player.interact([obj, action, vm.current_tool])
|
||||
else:
|
||||
player.walk_to(pos, walk_context)
|
||||
|
||||
func ev_right_click_on_npc(obj, pos, event):
|
||||
printt(obj.name, "right-clicked at", pos, "with", event, can_click())
|
||||
|
||||
if not can_click():
|
||||
return
|
||||
|
||||
var inventory_open = vm.inventory and vm.inventory.blocks_tooltip()
|
||||
|
||||
if obj.use_action_menu and not inventory_open:
|
||||
if ProjectSettings.get_setting("escoria/ui/right_mouse_button_action_menu"):
|
||||
spawn_action_menu(obj)
|
||||
return
|
||||
elif inventory_open:
|
||||
vm.inventory.close()
|
||||
|
||||
func ev_mouse_exit_npc(obj):
|
||||
printt(obj.name, "mouse_exit_npc")
|
||||
|
||||
vm.hover_pop(obj)
|
||||
|
||||
func spawn_action_menu(obj):
|
||||
if vm.action_menu == null:
|
||||
return
|
||||
|
||||
if vm.tooltip and (vm.current_tool or vm.current_action):
|
||||
vm.tooltip.hide()
|
||||
vm.clear_current_tool()
|
||||
vm.clear_current_action()
|
||||
|
||||
if player:
|
||||
player.walk_stop(player.position)
|
||||
|
||||
var pos = get_viewport().get_mouse_position()
|
||||
vm.action_menu.set_position(pos)
|
||||
vm.action_menu.show()
|
||||
vm.action_menu.start(obj)
|
||||
|
||||
func action_menu_selected(obj, action):
|
||||
if action == "use" && obj.get_action() != "":
|
||||
action = obj.get_action()
|
||||
if player != null:
|
||||
player.interact([obj, action])
|
||||
else:
|
||||
interact([obj, action])
|
||||
vm.action_menu.stop()
|
||||
|
||||
func interact(p_params):
|
||||
if mode == "default":
|
||||
var obj = p_params[0]
|
||||
vm.clear_action()
|
||||
var action = p_params[1]
|
||||
if !action:
|
||||
action = obj.get_action()
|
||||
|
||||
if p_params.size() > 2:
|
||||
vm.clear_action()
|
||||
if obj == p_params[2]:
|
||||
return
|
||||
#inventory.close()
|
||||
activate(obj, action, p_params[2])
|
||||
else:
|
||||
#inventory.close()
|
||||
activate(obj, action)
|
||||
vm.clear_action()
|
||||
return
|
||||
|
||||
func activate(obj, action, param = null):
|
||||
if !vm.can_interact():
|
||||
#printt("******************** vm can't interact during activate")
|
||||
#print_stack()
|
||||
return
|
||||
|
||||
if !obj.activate(action, param):
|
||||
if param != null: # try opposite way
|
||||
if param.activate(action, obj):
|
||||
return
|
||||
fallback(obj, action, param)
|
||||
|
||||
func fallback(obj, action, param = null):
|
||||
if fallbacks == null:
|
||||
return
|
||||
var comb = action
|
||||
if typeof(param) != typeof(null):
|
||||
comb = action + " " + param.global_id
|
||||
if comb in fallbacks:
|
||||
vm.run_event(fallbacks[comb])
|
||||
return
|
||||
if action in fallbacks:
|
||||
if player:
|
||||
# Resolve the angle to look toward and call `walk_stop` to make magic happen
|
||||
player.resolve_angle_to(obj)
|
||||
player.walk_stop(player.position)
|
||||
|
||||
vm.run_event(fallbacks[action])
|
||||
return
|
||||
vm.report_errors(fallbacks_path, ["Invalid action " + comb + " in fallbacks."])
|
||||
|
||||
func scene_input(event):
|
||||
if event.is_action("quick_save") && event.is_pressed() && !event.is_echo():
|
||||
vm.request_autosave()
|
||||
if event is InputEventJoypadMotion:
|
||||
if event.axis == 0 || event.axis == 1:
|
||||
joystick_mode = true
|
||||
check_joystick = true
|
||||
set_process(true)
|
||||
|
||||
if event.is_action("menu_request") && event.is_pressed() && !event.is_echo():
|
||||
handle_menu_request()
|
||||
|
||||
func handle_menu_request():
|
||||
var menu_enabled = vm.menu_enabled()
|
||||
if vm.can_save() and vm.can_interact() and menu_enabled:
|
||||
# Do not display overlay menu with action menu or inventory, it looks silly and weird
|
||||
if vm.action_menu:
|
||||
if vm.action_menu.is_visible():
|
||||
vm.action_menu.stop()
|
||||
|
||||
# Forcibly close inventory without animation if collapsible and visible
|
||||
if vm.inventory and vm.inventory.blocks_tooltip():
|
||||
vm.inventory.force_close()
|
||||
|
||||
# Clear hover/tooltip state to be rebuilt when menu is closed
|
||||
vm.hover_teardown()
|
||||
|
||||
# Finally show the menu
|
||||
main.load_menu(ProjectSettings.get_setting("escoria/ui/in_game_menu"))
|
||||
elif not menu_enabled:
|
||||
get_tree().call_group("game", "ui_blocked")
|
||||
|
||||
func menu_closed():
|
||||
printt("menu_closed")
|
||||
## We may be hovering a HUD button which is over an item;
|
||||
# make it look like it would in gameplay
|
||||
if hud:
|
||||
# These are set up in hud.gd only if the corresponding button is available
|
||||
if hud.menu_toggle_rect:
|
||||
if hud.menu_toggle_rect.has_point($"/root".get_mouse_position()):
|
||||
hud.hud_button_entered()
|
||||
return
|
||||
|
||||
if hud.inv_toggle_rect:
|
||||
if hud.inv_toggle_rect.has_point($"/root".get_mouse_position()):
|
||||
hud.hud_button_entered()
|
||||
return
|
||||
|
||||
## Otherwise rebuild hover stack
|
||||
vm.hover_rebuild()
|
||||
|
||||
func _process(_delta):
|
||||
if !vm.can_interact():
|
||||
check_joystick = false
|
||||
return
|
||||
|
||||
if player == null || player == self:
|
||||
return
|
||||
if joystick_mode:
|
||||
var objs = vm.get_registered_objects()
|
||||
var mobj = null
|
||||
var mdist
|
||||
var pos = player.get_position()
|
||||
for key in objs:
|
||||
if key == "player":
|
||||
continue
|
||||
if !objs[key].is_type("Control"):
|
||||
continue
|
||||
if !objs[key].is_visible():
|
||||
continue
|
||||
if objs[key].tooltip == "":
|
||||
continue
|
||||
var objpos = objs[key].get_interact_pos()
|
||||
var odist = pos.distance_squared_to(objpos)
|
||||
if typeof(mobj) == typeof(null):
|
||||
mobj = objs[key]
|
||||
mdist = odist
|
||||
else:
|
||||
if odist < mdist:
|
||||
mdist = odist
|
||||
mobj = objs[key]
|
||||
if typeof(mobj) != typeof(null):
|
||||
if mdist < min_interact_dist:
|
||||
if mobj != last_obj:
|
||||
spawn_action_menu(mobj)
|
||||
# mouse_enter(mobj)
|
||||
last_obj = mobj
|
||||
else:
|
||||
# mouse_exit(mobj)
|
||||
last_obj = null
|
||||
|
||||
if !check_joystick:
|
||||
return
|
||||
|
||||
var dir = Vector2(Input.get_joy_axis(0, 0), Input.get_joy_axis(0, 1));
|
||||
if dir.length_squared() < 0.1:
|
||||
check_joystick = false
|
||||
return
|
||||
|
||||
player.walk_to(player.get_position() + dir * 20)
|
||||
|
||||
func set_inventory_enabled(p_enabled):
|
||||
inventory_enabled = p_enabled
|
||||
if !has_node("hud_layer/hud/inv_toggle"):
|
||||
return
|
||||
if inventory_enabled:
|
||||
$"hud_layer/hud/inv_toggle".show()
|
||||
else:
|
||||
$"hud_layer/hud/inv_toggle".hide()
|
||||
|
||||
func set_camera_limits():
|
||||
var limits = {}
|
||||
if camera_limits.size.x == 0 and camera_limits.size.y == 0:
|
||||
var area = Rect2()
|
||||
for child in get_parent().get_children():
|
||||
if child is esc_type.BACKGROUND:
|
||||
var pos = child.get_global_position()
|
||||
var size = child.get_texture().get_size()
|
||||
if child.global_scale.x != 1 or child.global_scale.y != 1:
|
||||
size.x *= child.global_scale.x
|
||||
size.y *= child.global_scale.y
|
||||
|
||||
area = area.expand(pos)
|
||||
area = area.expand(pos + size)
|
||||
|
||||
# if the background is smaller than the viewport, we want the camera to stick centered on the background
|
||||
if area.size.x == 0 or area.size.y == 0 or area.size < get_viewport().size:
|
||||
printt("No limit area! Using viewport")
|
||||
area.size = get_viewport().size
|
||||
|
||||
printt("setting camera limits from scene ", area)
|
||||
limits = {
|
||||
"limit_left": area.position.x,
|
||||
"limit_right": area.position.x + area.size.x,
|
||||
"limit_top": area.position.y,
|
||||
"limit_bottom": area.position.y + area.size.y,
|
||||
"set_default": true,
|
||||
}
|
||||
else:
|
||||
limits = {
|
||||
"limit_left": camera_limits.position.x,
|
||||
"limit_right": camera_limits.position.x + camera_limits.size.x,
|
||||
"limit_top": camera_limits.position.y,
|
||||
"limit_bottom": camera_limits.position.y + camera_limits.size.y + main.screen_ofs.y * 2,
|
||||
"set_default": true,
|
||||
}
|
||||
printt("setting camera limits from parameter ", camera_limits)
|
||||
|
||||
camera.set_limits(limits)
|
||||
camera.set_offset(main.screen_ofs * 2)
|
||||
|
||||
#vm.update_camera(0.000000001)
|
||||
|
||||
func load_hud():
|
||||
var hres = vm.res_cache.get_resource(vm.get_hud_scene())
|
||||
$"hud_layer/hud".replace_by_instance(hres)
|
||||
|
||||
hud = $"hud_layer/hud"
|
||||
|
||||
# Add inventory to hud layer, usually hud_minimal.tscn, if found in project settings
|
||||
# and not present in the `game` scene's hud.
|
||||
if inventory_enabled:
|
||||
if hud.has_node("inventory"):
|
||||
var inventory = hud.get_node("inventory")
|
||||
vm.register_inventory(inventory)
|
||||
printt("Found inventory in hud", hud)
|
||||
else:
|
||||
var inventory_scene = ProjectSettings.get_setting("escoria/ui/inventory")
|
||||
if inventory_scene:
|
||||
var inventory = load(inventory_scene).instance()
|
||||
if inventory and inventory is esc_type.INVENTORY:
|
||||
vm.register_inventory(inventory)
|
||||
if vm.inventory.is_collapsible:
|
||||
vm.inventory.hide()
|
||||
hud.add_child(vm.inventory)
|
||||
hud.move_child(vm.inventory, 0)
|
||||
printt("Added inventory to hud", hud)
|
||||
|
||||
if has_node("hud_layer/hud/inv_toggle"):
|
||||
if inventory_enabled:
|
||||
$"hud_layer/hud/inv_toggle".show()
|
||||
else:
|
||||
$"hud_layer/hud/inv_toggle".hide()
|
||||
|
||||
# Add action menu to hud layer if found in project settings
|
||||
if ProjectSettings.get_setting("escoria/ui/action_menu"):
|
||||
action_menu = load(ProjectSettings.get_setting("escoria/ui/action_menu")).instance()
|
||||
if action_menu:
|
||||
$"hud_layer".add_child(action_menu)
|
||||
|
||||
func _ready():
|
||||
add_to_group("game")
|
||||
if has_node("../player"):
|
||||
player = $"../player"
|
||||
|
||||
if fallbacks_path != "":
|
||||
fallbacks = vm.compile(fallbacks_path)
|
||||
|
||||
if has_node("click"):
|
||||
click = get_node("click")
|
||||
if has_node("click_anim"):
|
||||
click_anim = get_node("click_anim")
|
||||
|
||||
default_obj_action = ProjectSettings.get_setting("escoria/platform/default_object_action")
|
||||
obj_action_req_dblc = ProjectSettings.get_setting("escoria/platform/object_action_requires_doubleclick")
|
||||
|
||||
camera = get_node("camera")
|
||||
|
||||
camera.drag_margin_left = drag_margin_left
|
||||
camera.drag_margin_top = drag_margin_top
|
||||
camera.drag_margin_right = drag_margin_right
|
||||
camera.drag_margin_bottom = drag_margin_bottom
|
||||
|
||||
vm.set_camera(camera)
|
||||
|
||||
call_deferred("set_camera_limits")
|
||||
call_deferred("load_hud")
|
||||
|
||||
func _exit_tree():
|
||||
if action_menu:
|
||||
action_menu.free()
|
||||
|
29
device/globals/game.tscn
Normal file
29
device/globals/game.tscn
Normal file
|
@ -0,0 +1,29 @@
|
|||
[gd_scene load_steps=4 format=2]
|
||||
|
||||
[ext_resource path="res://globals/game.gd" type="Script" id=1]
|
||||
[ext_resource path="res://ui/dialog_player.tscn" type="PackedScene" id=2]
|
||||
[ext_resource path="res://globals/camera.gd" type="Script" id=3]
|
||||
|
||||
[node name="game" type="Node"]
|
||||
script = ExtResource( 1 )
|
||||
fallbacks_path = "res://fallbacks.esc"
|
||||
inventory_enabled = false
|
||||
|
||||
[node name="dialog_layer" type="CanvasLayer" parent="."]
|
||||
layer = 128
|
||||
|
||||
[node name="dialog_player" parent="dialog_layer" instance=ExtResource( 2 )]
|
||||
|
||||
[node name="hud_layer" type="CanvasLayer" parent="."]
|
||||
layer = 126
|
||||
|
||||
[node name="hud" parent="hud_layer" instance_placeholder="res://ui/hud.tscn"]
|
||||
visible = false
|
||||
|
||||
[node name="wait_timer" type="Timer" parent="."]
|
||||
|
||||
[node name="camera" type="Camera2D" parent="."]
|
||||
current = true
|
||||
script = ExtResource( 3 )
|
||||
|
||||
[node name="tween" type="Tween" parent="camera"]
|
80
device/globals/game_am.tscn
Normal file
80
device/globals/game_am.tscn
Normal file
|
@ -0,0 +1,80 @@
|
|||
[gd_scene load_steps=4 format=2]
|
||||
|
||||
[ext_resource path="res://globals/game.gd" type="Script" id=1]
|
||||
[ext_resource path="res://ui/dialog_player.tscn" type="PackedScene" id=2]
|
||||
[ext_resource path="res://globals/camera.gd" type="Script" id=3]
|
||||
|
||||
[node name="game" type="Node" index="0"]
|
||||
|
||||
script = ExtResource( 1 )
|
||||
fallbacks_path = "res://fallbacks.esc"
|
||||
inventory_enabled = true
|
||||
drag_margin_left = 0.2
|
||||
drag_margin_top = 0.2
|
||||
drag_margin_right = 0.2
|
||||
drag_margin_bottom = 0.2
|
||||
camera_limits = Rect2( 0, 0, 0, 0 )
|
||||
|
||||
[node name="dialog_layer" type="CanvasLayer" parent="." index="0"]
|
||||
|
||||
layer = 128
|
||||
offset = Vector2( 0, 0 )
|
||||
rotation = 0.0
|
||||
scale = Vector2( 1, 1 )
|
||||
transform = Transform2D( 1, 0, 0, 1, 0, 0 )
|
||||
|
||||
[node name="dialog_player" parent="dialog_layer" index="0" instance=ExtResource( 2 )]
|
||||
|
||||
[node name="hud_layer" type="CanvasLayer" parent="." index="1"]
|
||||
|
||||
layer = 126
|
||||
offset = Vector2( 0, 0 )
|
||||
rotation = 0.0
|
||||
scale = Vector2( 1, 1 )
|
||||
transform = Transform2D( 1, 0, 0, 1, 0, 0 )
|
||||
|
||||
[node name="hud" parent="hud_layer" index="0" instance_placeholder="res://ui/hud_minimal.tscn"]
|
||||
|
||||
[node name="wait_timer" type="Timer" parent="." index="2"]
|
||||
|
||||
process_mode = 1
|
||||
wait_time = 1.0
|
||||
one_shot = false
|
||||
autostart = false
|
||||
|
||||
[node name="camera" type="Camera2D" parent="." index="3"]
|
||||
|
||||
anchor_mode = 1
|
||||
rotating = false
|
||||
current = true
|
||||
zoom = Vector2( 1, 1 )
|
||||
limit_left = -10000000
|
||||
limit_top = -10000000
|
||||
limit_right = 10000000
|
||||
limit_bottom = 10000000
|
||||
limit_smoothed = false
|
||||
drag_margin_h_enabled = true
|
||||
drag_margin_v_enabled = true
|
||||
smoothing_enabled = false
|
||||
smoothing_speed = 5.0
|
||||
offset_v = 0.0
|
||||
offset_h = 0.0
|
||||
drag_margin_left = 0.2
|
||||
drag_margin_top = 0.2
|
||||
drag_margin_right = 0.2
|
||||
drag_margin_bottom = 0.2
|
||||
editor_draw_screen = true
|
||||
editor_draw_limits = false
|
||||
editor_draw_drag_margin = false
|
||||
script = ExtResource( 3 )
|
||||
|
||||
[node name="tween" type="Tween" parent="camera" index="0"]
|
||||
|
||||
repeat = false
|
||||
playback_process_mode = 1
|
||||
playback_speed = 1.0
|
||||
playback/active = false
|
||||
playback/repeat = false
|
||||
playback/speed = 1.0
|
||||
|
||||
|
1305
device/globals/global_vm.gd
Normal file
1305
device/globals/global_vm.gd
Normal file
File diff suppressed because it is too large
Load diff
90
device/globals/hud.gd
Normal file
90
device/globals/hud.gd
Normal file
|
@ -0,0 +1,90 @@
|
|||
extends Control
|
||||
|
||||
var background = null
|
||||
|
||||
var inv_toggle_rect = null
|
||||
var menu_toggle_rect = null
|
||||
|
||||
func inv_toggle():
|
||||
$"inventory".toggle()
|
||||
|
||||
func _on_inventory_toggle_visibility_changed():
|
||||
if $inv_toggle.is_hidden():
|
||||
$buttons.hide()
|
||||
else:
|
||||
$buttons.show()
|
||||
|
||||
func _on_hint_pressed():
|
||||
printt("hint pressed")
|
||||
if background != null:
|
||||
background.emit_right_click()
|
||||
|
||||
func _on_menu_pressed():
|
||||
printt("menu pressed")
|
||||
get_tree().call_group("game", "handle_menu_request")
|
||||
|
||||
func hud_button_entered():
|
||||
# printt("hud button mouse entered")
|
||||
vm.hover_teardown()
|
||||
|
||||
func hud_button_exited():
|
||||
# printt("hud button mouse exited")
|
||||
vm.hover_rebuild()
|
||||
|
||||
func menu_opened():
|
||||
hide()
|
||||
|
||||
func menu_closed():
|
||||
show()
|
||||
|
||||
func set_visible(p_visible):
|
||||
visible = p_visible
|
||||
|
||||
func setup_inv_toggle():
|
||||
var conn_err = $"inv_toggle".connect("pressed", self, "inv_toggle")
|
||||
if conn_err:
|
||||
vm.report_errors("hud", ["inv_toggle.pressed -> inv_toggle error: " + String(conn_err)])
|
||||
|
||||
conn_err = $"inv_toggle".connect("mouse_entered", self, "hud_button_entered")
|
||||
if conn_err:
|
||||
vm.report_errors("hud", ["inv_toggle.mouse_entered -> hud_button_entered error: " + String(conn_err)])
|
||||
|
||||
conn_err = $"inv_toggle".connect("mouse_exited", self, "hud_button_exited")
|
||||
if conn_err:
|
||||
vm.report_errors("hud", ["inv_toggle.mouse_exited -> hud_button_exited error: " + String(conn_err)])
|
||||
|
||||
$"inv_toggle".set_focus_mode(Control.FOCUS_NONE)
|
||||
|
||||
inv_toggle_rect = Rect2($"inv_toggle".rect_global_position, $"inv_toggle".rect_size)
|
||||
|
||||
func setup_menu_toggle():
|
||||
var conn_err = $"menu".connect("pressed", self, "_on_menu_pressed")
|
||||
if conn_err:
|
||||
vm.report_errors("hud", ["menu.pressed -> _on_menu_pressed error: " + String(conn_err)])
|
||||
|
||||
conn_err = $"menu".connect("mouse_entered", self, "hud_button_entered")
|
||||
if conn_err:
|
||||
vm.report_errors("hud", ["menu.mouse_entered -> hud_button_entered error: " + String(conn_err)])
|
||||
|
||||
conn_err = $"menu".connect("mouse_exited", self, "hud_button_exited")
|
||||
if conn_err:
|
||||
vm.report_errors("hud", ["menu.mouse_exited -> hud_button_exited error: " + String(conn_err)])
|
||||
|
||||
$"menu".set_focus_mode(Control.FOCUS_NONE)
|
||||
|
||||
menu_toggle_rect = Rect2($"menu".rect_global_position, $"menu".rect_size)
|
||||
|
||||
func _ready():
|
||||
add_to_group("hud")
|
||||
add_to_group("game")
|
||||
|
||||
if has_node("inv_toggle"):
|
||||
setup_inv_toggle()
|
||||
|
||||
if has_node("menu"):
|
||||
setup_menu_toggle()
|
||||
|
||||
# Hide verb menu if hud layer has an action menu
|
||||
if has_node("../action_menu"):
|
||||
$verb_menu.hide()
|
||||
|
93
device/globals/interactive.gd
Normal file
93
device/globals/interactive.gd
Normal file
|
@ -0,0 +1,93 @@
|
|||
extends Node2D
|
||||
|
||||
#warning-ignore:unused_class_variable
|
||||
export var global_id = "" # API property
|
||||
#warning-ignore:unused_class_variable
|
||||
export(String, FILE, ".esc") var events_path = "" # API property
|
||||
export var active = true setget set_active,get_active
|
||||
|
||||
var event_table = {}
|
||||
|
||||
# This'll contain a highlight tooltip if `tooltip_pos` is set as a child
|
||||
var highlight_tooltip
|
||||
|
||||
var width = float(ProjectSettings.get("display/window/size/width"))
|
||||
var height = float(ProjectSettings.get("display/window/size/height"))
|
||||
|
||||
func get_tooltip():
|
||||
pass
|
||||
|
||||
func show_highlight():
|
||||
if not self.visible:
|
||||
return
|
||||
|
||||
# This is essentially is_interactive, but triggers and exit don't have separate areas
|
||||
if "area" in self and self.area and not self.area.visible:
|
||||
return
|
||||
|
||||
if not has_node("tooltip_pos"):
|
||||
return
|
||||
|
||||
highlight_tooltip = vm.tooltip.duplicate()
|
||||
assert(highlight_tooltip != vm.tooltip)
|
||||
|
||||
var tt_pos = $tooltip_pos.global_position
|
||||
var tt_text = get_tooltip()
|
||||
|
||||
highlight_tooltip.highlight_only = true
|
||||
highlight_tooltip.follow_mouse = false
|
||||
highlight_tooltip.text = tt_text
|
||||
|
||||
tt_pos = vm.camera.zoom_transform.xform(tt_pos)
|
||||
|
||||
# Bail out if we're hopelessly out-of-view
|
||||
if tt_pos.x < 0 or tt_pos.x > width or tt_pos.y < 0 or tt_pos.y > height:
|
||||
highlight_tooltip.free()
|
||||
highlight_tooltip = null
|
||||
return
|
||||
|
||||
vm.tooltip.get_parent().add_child(highlight_tooltip)
|
||||
|
||||
highlight_tooltip.set_position(tt_pos)
|
||||
|
||||
highlight_tooltip.show()
|
||||
|
||||
func hide_highlight():
|
||||
if not highlight_tooltip:
|
||||
return
|
||||
|
||||
assert(highlight_tooltip.visible)
|
||||
|
||||
highlight_tooltip.hide()
|
||||
highlight_tooltip.free()
|
||||
highlight_tooltip = null
|
||||
|
||||
func run_event(p_ev):
|
||||
vm.emit_signal("run_event", p_ev)
|
||||
|
||||
func activate(p_action, p_param = null):
|
||||
if p_param != null:
|
||||
p_action = p_action + " " + p_param.global_id
|
||||
|
||||
if p_action in event_table:
|
||||
run_event(event_table[p_action])
|
||||
else:
|
||||
return false
|
||||
return true
|
||||
|
||||
func set_active(p_active):
|
||||
active = p_active
|
||||
if p_active:
|
||||
show()
|
||||
else:
|
||||
hide()
|
||||
|
||||
func set_interactive(p_interactive):
|
||||
self.area.visible = p_interactive
|
||||
|
||||
func get_active():
|
||||
return active
|
||||
|
||||
func _ready():
|
||||
add_to_group("highlight_tooltip")
|
||||
|
242
device/globals/inventory.gd
Normal file
242
device/globals/inventory.gd
Normal file
|
@ -0,0 +1,242 @@
|
|||
extends TextureRect
|
||||
|
||||
export(bool) var is_collapsible = false
|
||||
|
||||
var page = 0
|
||||
|
||||
var page_max
|
||||
var page_size
|
||||
var current_action = ""
|
||||
|
||||
func blocks_tooltip():
|
||||
return self.is_collapsible and self.is_visible()
|
||||
|
||||
func change_page(dir):
|
||||
page += dir
|
||||
if page < 0:
|
||||
page = 0
|
||||
if page > page_max:
|
||||
page = page_max
|
||||
sort_items()
|
||||
|
||||
func open():
|
||||
if is_visible():
|
||||
return
|
||||
|
||||
if vm.action_menu:
|
||||
# `false` is for show_tooltip=false
|
||||
vm.action_menu.stop(false)
|
||||
|
||||
sort_items()
|
||||
|
||||
show()
|
||||
|
||||
print("inventory open")
|
||||
|
||||
if has_node("animation"):
|
||||
get_node("animation").play("show")
|
||||
|
||||
func show():
|
||||
.show()
|
||||
|
||||
if vm.tooltip:
|
||||
vm.tooltip.update()
|
||||
|
||||
func close():
|
||||
if !is_visible():
|
||||
return
|
||||
|
||||
if vm.tooltip:
|
||||
# If we are closing while hovering ...
|
||||
if vm.hover_object:
|
||||
# ... an inventory item ...
|
||||
if vm.hover_object is esc_type.ITEM and vm.hover_object.inventory:
|
||||
# ... we must exit it to sort out the tooltip
|
||||
vm.hover_object.emit_signal("mouse_exit_inventory_item", vm.hover_object)
|
||||
|
||||
if has_node("animation"):
|
||||
if $"animation".is_playing():
|
||||
return
|
||||
$"animation".play("hide")
|
||||
|
||||
# XXX: What is this `look` node? A verb menu thing?
|
||||
if has_node("look"):
|
||||
get_node("look").set_pressed(false)
|
||||
|
||||
current_action = ""
|
||||
hide()
|
||||
print("inventory close")
|
||||
|
||||
func hide():
|
||||
.hide()
|
||||
|
||||
if vm.tooltip:
|
||||
vm.tooltip.update()
|
||||
|
||||
func force_close():
|
||||
if !is_visible():
|
||||
return
|
||||
|
||||
if vm.tooltip:
|
||||
vm.tooltip.hide()
|
||||
hide()
|
||||
printt("inventory force_close")
|
||||
|
||||
|
||||
func toggle():
|
||||
if is_visible():
|
||||
close()
|
||||
else:
|
||||
open()
|
||||
|
||||
func anim_finished(name):
|
||||
if name == "hide":
|
||||
hide()
|
||||
|
||||
func sort_items():
|
||||
var items = get_node("items")
|
||||
var slots = get_node("slots")
|
||||
var count = 0
|
||||
var focus = false
|
||||
for i in range(0, items.get_child_count()):
|
||||
var c = items.get_child(i)
|
||||
if !vm.inventory_has(c.global_id):
|
||||
c.hide()
|
||||
continue
|
||||
if count >= page_size * (page+1):
|
||||
#printt("past page", count, page_size, page)
|
||||
c.hide()
|
||||
elif count >= page_size * page:
|
||||
var slot = count - page_size * page
|
||||
c.show()
|
||||
printt("showing item", c.global_id, slots.get_child(slot).get_global_position())
|
||||
|
||||
c.set_global_position(slots.get_child(slot).get_global_position())
|
||||
c.update_rect() # To see if we're hovered when in-game menu closes
|
||||
|
||||
if !focus:
|
||||
focus = true
|
||||
else:
|
||||
c.hide()
|
||||
count += 1
|
||||
page_max = int(count / page_size)
|
||||
|
||||
if count > 0 && page > page_max:
|
||||
page -= 1
|
||||
sort_items()
|
||||
|
||||
func inventory_changed():
|
||||
printt("** inventory changed")
|
||||
if is_visible():
|
||||
printt("sorting")
|
||||
sort_items()
|
||||
|
||||
func global_changed(name):
|
||||
if name.find("i/") != 0:
|
||||
return
|
||||
inventory_changed()
|
||||
|
||||
func input(event):
|
||||
if event.type == InputEvent.MOUSE_BUTTON && event.pressed:
|
||||
toggle()
|
||||
|
||||
func get_action():
|
||||
return current_action
|
||||
|
||||
func look_toggled(pressed):
|
||||
if pressed:
|
||||
current_action = "look"
|
||||
else:
|
||||
current_action = "use"
|
||||
get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFAULT, "game", "clear_action")
|
||||
|
||||
func _input(event):
|
||||
if !vm.can_interact():
|
||||
return
|
||||
if event.is_pressed() and event.is_action("inventory_toggle") and is_collapsible:
|
||||
toggle()
|
||||
|
||||
func log_button_pressed():
|
||||
close()
|
||||
get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFAULT, "game", "open_log")
|
||||
|
||||
func _on_open_inventory_signal(open):
|
||||
if (open):
|
||||
open()
|
||||
else:
|
||||
close()
|
||||
|
||||
func mouse_entered():
|
||||
# Must tear down the hovers or we might get side-effects when something is under
|
||||
# a non-blocking inventory
|
||||
vm.hover_teardown()
|
||||
|
||||
func mouse_exited():
|
||||
# Restore the expected state if it was reset when entering
|
||||
vm.hover_rebuild()
|
||||
|
||||
func _ready():
|
||||
var conn_err
|
||||
|
||||
conn_err = vm.connect("inventory_changed", self, "inventory_changed")
|
||||
if conn_err:
|
||||
vm.report_errors("inventory", ["inventory_changed -> inventory_changed error: " + String(conn_err)])
|
||||
|
||||
conn_err = vm.connect("open_inventory", self, "_on_open_inventory_signal")
|
||||
if conn_err:
|
||||
vm.report_errors("inventory", ["open_inventory -> _on_open_inventory_signal error: " + String(conn_err)])
|
||||
|
||||
conn_err = vm.connect("global_changed", self, "global_changed")
|
||||
if conn_err:
|
||||
vm.report_errors("inventory", ["global_changed -> global_changed error: " + String(conn_err)])
|
||||
|
||||
page_size = get_node("slots").get_child_count()
|
||||
|
||||
# XXX: Not sure why, but sorting a collapsible inventory here causes textures to disappear
|
||||
if not self.is_collapsible:
|
||||
sort_items()
|
||||
|
||||
if has_node("arrow_prev"):
|
||||
conn_err = $"arrow_prev".connect("pressed", self, "change_page", [-1])
|
||||
if conn_err:
|
||||
vm.report_errors("inventory", ["arrow_prev.pressed -> change_page -1 error: " + String(conn_err)])
|
||||
|
||||
if has_node("arrow_next"):
|
||||
conn_err = $"arrow_next".connect("pressed", self, "change_page", [1])
|
||||
if conn_err:
|
||||
vm.report_errors("inventory", ["arrow_next.pressed -> change_page +1 error: " + String(conn_err)])
|
||||
|
||||
# Block problems when tooltip follows mouse and open inventory overlaps with exits
|
||||
if ProjectSettings.get_setting("escoria/ui/tooltip_follows_mouse") and self is TextureRect:
|
||||
conn_err = connect("mouse_entered", self, "mouse_entered")
|
||||
if conn_err:
|
||||
vm.report_errors("inventory", ["mouse_entered -> mouse_entered error: " + String(conn_err)])
|
||||
|
||||
conn_err = connect("mouse_exited", self, "mouse_exited")
|
||||
if conn_err:
|
||||
vm.report_errors("inventory", ["mouse_exited -> mouse_exited error: " + String(conn_err)])
|
||||
|
||||
|
||||
var items = get_node("items")
|
||||
for i in range(0, items.get_child_count()):
|
||||
var c = items.get_child(i)
|
||||
#printt("c path is ", c.get_name(), c.get_filename())
|
||||
c.inventory = true
|
||||
c.use_action_menu = false
|
||||
c.hide()
|
||||
|
||||
items.show()
|
||||
set_focus_mode(Control.FOCUS_NONE)
|
||||
#get_node("mask").set_focus_mode(Control.FOCUS_NONE)
|
||||
set_process_input(true)
|
||||
|
||||
#get_node("log_button").connect("pressed", self, "log_button_pressed")
|
||||
|
||||
if has_node("animation"):
|
||||
conn_err = $"animation".connect("animation_finished", self, "anim_finished")
|
||||
if conn_err:
|
||||
vm.report_errors("inventory", ["animation_finished -> anim_finished error: " + String(conn_err)])
|
||||
|
||||
add_to_group("game")
|
||||
|
||||
call_deferred("sort_items")
|
718
device/globals/item.gd
Normal file
718
device/globals/item.gd
Normal file
|
@ -0,0 +1,718 @@
|
|||
extends "res://globals/interactive.gd"
|
||||
|
||||
signal left_click_on_item
|
||||
signal left_dblclick_on_item
|
||||
signal left_click_on_inventory_item
|
||||
signal right_click_on_item
|
||||
signal right_click_on_inventory_item
|
||||
signal mouse_enter_item
|
||||
signal mouse_enter_inventory_item
|
||||
signal mouse_exit_item
|
||||
signal mouse_exit_inventory_item
|
||||
|
||||
var area
|
||||
|
||||
# For inventory items, when in-game menu is closed, look at this to see if we're hovered
|
||||
var rect
|
||||
|
||||
export var tooltip = ""
|
||||
export var action = ""
|
||||
|
||||
export(NodePath) var interact_position = null
|
||||
#warning-ignore:unused_class_variable
|
||||
export var use_combine = false # game.gd
|
||||
export var inventory = false
|
||||
#warning-ignore:unused_class_variable
|
||||
export var use_action_menu = true # game.gd
|
||||
|
||||
#warning-ignore:unused_class_variable
|
||||
export(int, -1, 360) var interact_angle = -1
|
||||
#warning-ignore:unused_class_variable
|
||||
export(Color) var dialog_color = null
|
||||
export(Script) var animations
|
||||
export var talk_animation = "talk"
|
||||
export var placeholders = {}
|
||||
export var dynamic_z_index = true
|
||||
export var speed = 300
|
||||
export var scale_on_map = false
|
||||
export var light_on_map = false setget set_light_on_map
|
||||
export var is_interactive = true
|
||||
|
||||
var orig_speed
|
||||
|
||||
var anim_notify = null
|
||||
var anim_scale_override = null
|
||||
var ui_anim = null
|
||||
|
||||
var clicked = false
|
||||
var interact_pos
|
||||
var camera_pos
|
||||
var terrain
|
||||
var terrain_is_scalenodes
|
||||
var walk_path
|
||||
var walk_context
|
||||
var moved
|
||||
var last_scale = Vector2(1, 1)
|
||||
var last_deg = null
|
||||
var last_dir = 0
|
||||
var animation
|
||||
var state = ""
|
||||
var walk_destination
|
||||
var path_ofs
|
||||
var pose_scale = 1
|
||||
var task
|
||||
var sprites = []
|
||||
var audio
|
||||
|
||||
# Godot doesn't do doubleclicks so we must
|
||||
var last_lmb_dt = 0
|
||||
var waiting_dblclick = false
|
||||
|
||||
func get_dialog_pos():
|
||||
if has_node("dialog_pos"):
|
||||
return $"dialog_pos".global_position
|
||||
|
||||
return global_position
|
||||
|
||||
func get_interact_pos():
|
||||
if interact_pos:
|
||||
return interact_pos.global_position
|
||||
|
||||
return global_position
|
||||
|
||||
func get_camera_pos():
|
||||
if camera_pos:
|
||||
return camera_pos.global_position
|
||||
|
||||
return global_position
|
||||
|
||||
#warning-ignore:unused_argument
|
||||
func anim_finished(anim_name):
|
||||
# TODO use parameter here?
|
||||
if anim_notify != null:
|
||||
vm.finished(anim_notify)
|
||||
anim_notify = null
|
||||
|
||||
if anim_scale_override != null:
|
||||
set_scale(get_scale() * anim_scale_override)
|
||||
anim_scale_override = null
|
||||
|
||||
# Although states are permanent until changed, the underlying animations are not,
|
||||
# so we must re-set the state for the appearance of permanence
|
||||
set_state(state, true)
|
||||
|
||||
if animations and "idles" in animations:
|
||||
pose_scale = animations.idles[last_dir + 1]
|
||||
_update_terrain()
|
||||
|
||||
|
||||
#return is_visible()
|
||||
|
||||
func get_action():
|
||||
return action
|
||||
|
||||
func mouse_enter():
|
||||
_check_focus(true, false)
|
||||
if self.inventory:
|
||||
emit_signal("mouse_enter_inventory_item", self)
|
||||
else:
|
||||
emit_signal("mouse_enter_item", self)
|
||||
|
||||
func mouse_exit():
|
||||
_check_focus(false, false)
|
||||
if self.inventory:
|
||||
emit_signal("mouse_exit_inventory_item", self)
|
||||
else:
|
||||
emit_signal("mouse_exit_item", self)
|
||||
|
||||
func area_input(_viewport, event, _shape_idx):
|
||||
input(event)
|
||||
|
||||
func input(event):
|
||||
# TODO: Expand this for other input events than mouse
|
||||
if event is InputEventMouseButton || event.is_action("ui_accept"):
|
||||
if event.is_pressed():
|
||||
clicked = vm.hover_stack.front() == self
|
||||
|
||||
var ev_pos = get_global_mouse_position()
|
||||
if event.is_action("game_general"):
|
||||
if self.inventory:
|
||||
emit_signal("left_click_on_inventory_item", self, ev_pos, event)
|
||||
last_lmb_dt = 0
|
||||
waiting_dblclick = null
|
||||
elif last_lmb_dt <= vm.DOUBLECLICK_TIMEOUT:
|
||||
emit_signal("left_dblclick_on_item", self, ev_pos, event)
|
||||
last_lmb_dt = 0
|
||||
waiting_dblclick = null
|
||||
else:
|
||||
last_lmb_dt = 0
|
||||
waiting_dblclick = [ev_pos, event]
|
||||
|
||||
elif event.is_action("game_rmb"):
|
||||
if self.inventory:
|
||||
emit_signal("right_click_on_inventory_item", self, ev_pos, event)
|
||||
else:
|
||||
emit_signal("right_click_on_item", self, ev_pos, event)
|
||||
_check_focus(true, true)
|
||||
|
||||
func _check_focus(focus, pressed):
|
||||
if has_node("_focus_in"):
|
||||
if focus:
|
||||
get_node("_focus_in").show()
|
||||
else:
|
||||
get_node("_focus_in").hide()
|
||||
|
||||
if has_node("_pressed"):
|
||||
if pressed:
|
||||
get_node("_pressed").show()
|
||||
else:
|
||||
get_node("_pressed").hide()
|
||||
|
||||
func get_tooltip(hint=null):
|
||||
if not tooltip:
|
||||
return null
|
||||
|
||||
# if `development_lang` matches `text_lang`, don't translate
|
||||
if TranslationServer.get_locale() == ProjectSettings.get_setting("escoria/platform/development_lang"):
|
||||
if not global_id and ProjectSettings.get_setting("escoria/platform/force_tooltip_global_id"):
|
||||
vm.report_errors("item", ["Missing global_id in item with tooltip '" + tooltip + "'"])
|
||||
return tooltip
|
||||
|
||||
# Otherwise try to return the translated tooltip
|
||||
var tooltip_identifier = global_id + ".tooltip"
|
||||
if hint:
|
||||
tooltip_identifier += "." + hint
|
||||
|
||||
var translated = tr(tooltip_identifier)
|
||||
|
||||
# Try again if there's no translation for this hint
|
||||
if translated == "\"":
|
||||
tooltip_identifier = global_id + ".tooltip"
|
||||
translated = tr(tooltip_identifier)
|
||||
|
||||
# But if translation isn't found, ensure it can be translated and return placeholder
|
||||
if translated == tooltip_identifier:
|
||||
if not global_id and ProjectSettings.get_setting("escoria/platform/force_tooltip_global_id"):
|
||||
vm.report_errors("item", ["Missing global_id in item with tooltip '" + tooltip + "'"])
|
||||
|
||||
return tooltip_identifier
|
||||
|
||||
return translated
|
||||
|
||||
func global_changed(name):
|
||||
var ev = "global_changed "+name
|
||||
if ev in event_table:
|
||||
run_event(event_table[ev])
|
||||
elif "global_changed" in event_table:
|
||||
run_event(event_table.global_changed)
|
||||
|
||||
func anim_get_ph_paths(p_anim):
|
||||
if !(p_anim in placeholders):
|
||||
return null
|
||||
|
||||
var ret = []
|
||||
for p in placeholders[p_anim]:
|
||||
var n = get_node(p)
|
||||
if !(n is InstancePlaceholder):
|
||||
continue
|
||||
ret.push_back(n.get_instance_path())
|
||||
return ret
|
||||
|
||||
func play_anim(p_anim, p_notify = null, p_reverse = false, p_flip = null):
|
||||
if p_notify == null and (!animation or !animation.has_animation(p_anim)):
|
||||
print("skipping cut scene '", p_anim, "'")
|
||||
vm.finished(p_notify)
|
||||
#_debug_states()
|
||||
return
|
||||
|
||||
if p_anim in placeholders:
|
||||
for npath in placeholders[p_anim]:
|
||||
var node = get_node(npath)
|
||||
if !node is InstancePlaceholder:
|
||||
continue
|
||||
var path = node.get_instance_path()
|
||||
var res = vm.res_cache.get_resource(path)
|
||||
node.replace_by_instance(res)
|
||||
_find_sprites(get_node(npath))
|
||||
if p_flip != null:
|
||||
var s = get_scale()
|
||||
set_scale(s * p_flip)
|
||||
anim_scale_override = p_flip
|
||||
else:
|
||||
anim_scale_override = null
|
||||
|
||||
if p_reverse:
|
||||
animation.play(p_anim, -1, -1, true)
|
||||
else:
|
||||
animation.play(p_anim)
|
||||
anim_notify = p_notify
|
||||
|
||||
#_debug_states()
|
||||
|
||||
func play_snd(p_snd, p_loop=false):
|
||||
if !audio:
|
||||
vm.report_errors("item", ["play_snd called with no audio node"])
|
||||
return
|
||||
|
||||
var resource = load(p_snd)
|
||||
if !resource:
|
||||
vm.report_errors("item", ["play_snd resource not found " + p_snd])
|
||||
return
|
||||
|
||||
audio.stream = resource
|
||||
audio.stream.set_loop(p_loop)
|
||||
audio.play()
|
||||
|
||||
#_debug_states()
|
||||
|
||||
func set_speaking(p_speaking):
|
||||
printt("item set speaking! ", global_id, p_speaking, state)
|
||||
#print_stack()
|
||||
if !animation:
|
||||
return
|
||||
if talk_animation == "":
|
||||
return
|
||||
if p_speaking:
|
||||
if animation.has_animation(talk_animation):
|
||||
animation.play(talk_animation)
|
||||
animation.seek(0, true)
|
||||
else:
|
||||
set_state(state, true)
|
||||
if animations and "idles" in animations:
|
||||
pose_scale = animations.idles[last_dir + 1]
|
||||
_update_terrain()
|
||||
|
||||
func set_state(p_state, p_force = false):
|
||||
if state == p_state && !p_force:
|
||||
return
|
||||
|
||||
# printt("set state ", "global_id: ", global_id, "state: ", state, "p_state: ", p_state, "p_force: ", p_force)
|
||||
|
||||
state = p_state
|
||||
|
||||
if animation != null:
|
||||
# Though calling `.play()` probably stops the animation, be safe.
|
||||
animation.stop()
|
||||
if animation.has_animation(p_state):
|
||||
animation.play(p_state)
|
||||
|
||||
func teleport(obj, angle=null):
|
||||
set_position(obj.global_position)
|
||||
if angle:
|
||||
set_angle(angle)
|
||||
moved = true
|
||||
_update_terrain(true)
|
||||
|
||||
func teleport_pos(x, y, angle=null):
|
||||
set_position(Vector2(x, y))
|
||||
if angle:
|
||||
set_angle(angle)
|
||||
moved = true
|
||||
_update_terrain(true)
|
||||
|
||||
func _update_terrain(need_z_update=false):
|
||||
var pos = get_position()
|
||||
|
||||
if dynamic_z_index and need_z_update:
|
||||
z_index = pos.y if pos.y <= VisualServer.CANVAS_ITEM_Z_MAX else VisualServer.CANVAS_ITEM_Z_MAX
|
||||
|
||||
if !scale_on_map && !light_on_map:
|
||||
return
|
||||
|
||||
# Items in the scene tree will issue errors unless this is conditional
|
||||
if not terrain:
|
||||
return
|
||||
|
||||
var scale_range
|
||||
if terrain_is_scalenodes:
|
||||
scale_range = terrain.get_terrain(pos)
|
||||
else:
|
||||
var color = terrain.get_terrain(pos)
|
||||
scale_range = terrain.get_scale_range(color.b)
|
||||
|
||||
# The item's - as the player's - `animations` define the direction
|
||||
# as 1 or -1. This is stored as `pose_scale` and the easiest way
|
||||
# to flip a node is multiply its x-axis scale.
|
||||
scale_range.x *= pose_scale
|
||||
|
||||
if scale_on_map and scale_range != get_scale():
|
||||
# Check if `interact_pos` is a child of ours, and if so,
|
||||
# take a backup of the global position, because it will be affected by scaling.
|
||||
var interact_global_position
|
||||
if has_node("interact_pos"):
|
||||
interact_global_position = interact_pos.get_global_position()
|
||||
|
||||
# Same for camera_pos
|
||||
var camera_global_position
|
||||
if has_node("camera_pos"):
|
||||
camera_global_position = camera_pos.get_global_position()
|
||||
|
||||
self.scale = scale_range
|
||||
|
||||
# If `interact_pos` is a child, it was affected by scaling, so reset it
|
||||
# to the expected location.
|
||||
if interact_global_position:
|
||||
interact_pos.global_position = interact_global_position
|
||||
|
||||
# And camera position
|
||||
if camera_global_position:
|
||||
camera_pos.global_position = camera_global_position
|
||||
|
||||
if light_on_map:
|
||||
var c = terrain.get_light(pos)
|
||||
if c:
|
||||
modulate(c)
|
||||
|
||||
func _check_bounds():
|
||||
#printt("checking bouds for pos ", get_position(), terrain.is_solid(get_position()))
|
||||
if !scale_on_map:
|
||||
return
|
||||
if !Engine.is_editor_hint():
|
||||
return
|
||||
if terrain.is_solid(get_position()):
|
||||
if has_node("terrain_icon"):
|
||||
get_node("terrain_icon").hide()
|
||||
else:
|
||||
if !has_node("terrain_icon"):
|
||||
var node = Sprite.new()
|
||||
var tex = load("res://globals/terrain.png")
|
||||
node.set_texture(tex)
|
||||
add_child(node)
|
||||
node.set_name("terrain_icon")
|
||||
get_node("terrain_icon").show()
|
||||
|
||||
func _notification(what):
|
||||
if !is_inside_tree() || !Engine.is_editor_hint():
|
||||
return
|
||||
if what == Node2D.NOTIFICATION_TRANSFORM_CHANGED:
|
||||
_update_terrain()
|
||||
_check_bounds()
|
||||
|
||||
func hint_request():
|
||||
if !get_active():
|
||||
return
|
||||
if !vm.can_interact():
|
||||
return
|
||||
|
||||
if ui_anim == null:
|
||||
return
|
||||
|
||||
if ui_anim.is_playing():
|
||||
return
|
||||
|
||||
ui_anim.play("hint")
|
||||
|
||||
func setup_ui_anim():
|
||||
if has_node("ui_anims"):
|
||||
ui_anim = get_node("ui_anims")
|
||||
|
||||
for bg in get_tree().get_nodes_in_group("background"):
|
||||
bg.connect("right_click_on_bg", self, "hint_request")
|
||||
|
||||
var conn_err = vm.connect("global_changed", self, "global_changed")
|
||||
if conn_err:
|
||||
vm.report_errors("item", ["global_changed -> global_changed error: " + String(conn_err)])
|
||||
|
||||
func set_light_on_map(p_light):
|
||||
light_on_map = p_light
|
||||
if light_on_map:
|
||||
_update_terrain()
|
||||
else:
|
||||
modulate(Color(1, 1, 1, 1))
|
||||
|
||||
func slide_to(pos, context = null):
|
||||
# Assume a straight line, and leverage some walk functionality
|
||||
walk_path = [get_position(), pos]
|
||||
walk_context = context
|
||||
if walk_path.size() == 0:
|
||||
walk_stop(get_position())
|
||||
set_process(false)
|
||||
task = null
|
||||
return
|
||||
|
||||
moved = true
|
||||
|
||||
walk_destination = walk_path[walk_path.size()-1]
|
||||
|
||||
path_ofs = 0.0
|
||||
task = "slide"
|
||||
set_process(true)
|
||||
|
||||
func slide(pos, p_speed, context = null):
|
||||
if p_speed:
|
||||
orig_speed = speed
|
||||
speed = p_speed
|
||||
slide_to(pos, context)
|
||||
|
||||
func walk_stop(pos):
|
||||
set_position(pos)
|
||||
walk_path = []
|
||||
|
||||
if orig_speed:
|
||||
speed = orig_speed
|
||||
orig_speed = null
|
||||
|
||||
# Walking is not a state, but we must re-set our previous state to reset the animation
|
||||
set_state(state)
|
||||
|
||||
if task == "walk":
|
||||
if "idles" in animations:
|
||||
pose_scale = animations.idles[last_dir + 1]
|
||||
_update_terrain(true)
|
||||
|
||||
task = null
|
||||
|
||||
if walk_context != null:
|
||||
vm.finished(walk_context)
|
||||
walk_context = null
|
||||
|
||||
func walk_to(pos, context = null):
|
||||
walk_path = terrain.get_terrain_path(get_position(), pos)
|
||||
walk_context = context
|
||||
if walk_path.size() == 0:
|
||||
walk_stop(get_position())
|
||||
set_process(false)
|
||||
task = null
|
||||
return
|
||||
moved = true
|
||||
walk_destination = walk_path[walk_path.size()-1]
|
||||
if terrain.is_solid(pos):
|
||||
walk_destination = walk_path[walk_path.size()-1]
|
||||
path_ofs = 0.0
|
||||
task = "walk"
|
||||
set_process(true)
|
||||
|
||||
func walk(pos, p_speed, context = null):
|
||||
if p_speed:
|
||||
orig_speed = speed
|
||||
speed = p_speed
|
||||
walk_to(pos, context)
|
||||
|
||||
func modulate(color):
|
||||
for s in sprites:
|
||||
s.set_modulate(color)
|
||||
|
||||
func _physics_process(dt):
|
||||
last_lmb_dt += dt
|
||||
|
||||
if waiting_dblclick and last_lmb_dt > vm.DOUBLECLICK_TIMEOUT:
|
||||
emit_signal("left_click_on_item", self, waiting_dblclick[0], waiting_dblclick[1])
|
||||
last_lmb_dt = 0
|
||||
waiting_dblclick = null
|
||||
|
||||
func _process(time):
|
||||
if task == "walk" or task == "slide":
|
||||
var to_walk = speed * last_scale.x * time
|
||||
var pos = get_position()
|
||||
var old_pos = pos
|
||||
if walk_path.size() > 0:
|
||||
while to_walk > 0:
|
||||
var next
|
||||
if walk_path.size() > 1:
|
||||
next = walk_path[path_ofs + 1]
|
||||
else:
|
||||
next = walk_path[path_ofs]
|
||||
|
||||
var dist = pos.distance_to(next)
|
||||
|
||||
if dist > to_walk:
|
||||
var n = (next - pos).normalized()
|
||||
pos = pos + n * to_walk
|
||||
break
|
||||
pos = next
|
||||
to_walk -= dist
|
||||
path_ofs += 1
|
||||
if path_ofs >= walk_path.size() - 1:
|
||||
walk_stop(walk_destination)
|
||||
set_process(false)
|
||||
return
|
||||
|
||||
var angle = (old_pos.angle_to_point(pos)) * -1
|
||||
|
||||
set_position(pos)
|
||||
|
||||
if task == "walk":
|
||||
last_deg = vm._get_deg_from_rad(angle)
|
||||
last_dir = vm._get_dir_deg(last_deg, animations)
|
||||
|
||||
if animation:
|
||||
if animation.get_current_animation() != animations.directions[last_dir]:
|
||||
animation.play(animations.directions[last_dir])
|
||||
pose_scale = animations.directions[last_dir+1]
|
||||
|
||||
# If a z-indexed item is moved, forcibly update its z index
|
||||
_update_terrain(true)
|
||||
|
||||
func turn_to(deg):
|
||||
if deg < 0 or deg > 360:
|
||||
vm.report_errors("interactive", ["Invalid degree to turn to " + str(deg)])
|
||||
|
||||
moved = true
|
||||
|
||||
last_deg = deg
|
||||
last_dir = vm._get_dir_deg(deg, animations)
|
||||
|
||||
if animation and animations and "directions" in animations:
|
||||
if !animation.get_current_animation() or animation.get_current_animation() != animations.directions[last_dir]:
|
||||
# XXX: This requires manually scripting a wait
|
||||
# and setting the correct idle animation
|
||||
animation.play(animations.directions[last_dir])
|
||||
pose_scale = animations.directions[last_dir + 1]
|
||||
_update_terrain()
|
||||
|
||||
func set_angle(deg):
|
||||
if deg < 0 or deg > 360:
|
||||
# Compensate for savegame files during a broken version of Escoria
|
||||
if vm.loading_game:
|
||||
vm.report_warnings("interactive", ["Reset invalid degree " + str(deg)])
|
||||
deg = 0
|
||||
else:
|
||||
vm.report_errors("interactive", ["Invalid degree to turn to " + str(deg)])
|
||||
|
||||
moved = true
|
||||
|
||||
last_deg = deg
|
||||
last_dir = vm._get_dir_deg(deg, animations)
|
||||
|
||||
if animation and animations and "idles" in animations:
|
||||
pose_scale = animations.idles[last_dir + 1]
|
||||
_update_terrain()
|
||||
|
||||
func _find_sprites(p = null):
|
||||
if p is CanvasItem:
|
||||
sprites.push_back(p)
|
||||
for i in range(0, p.get_child_count()):
|
||||
_find_sprites(p.get_child(i))
|
||||
|
||||
func update_rect():
|
||||
# Called from inventory.gd when items get sorted
|
||||
assert(self.inventory)
|
||||
assert(area is TextureRect)
|
||||
|
||||
# Now that we know we are TextureRect, create a Rect2 so we can check `.has_point()` when closing the menu
|
||||
rect = Rect2(area.rect_global_position, area.rect_size)
|
||||
|
||||
func _ready():
|
||||
add_to_group("item")
|
||||
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
var conn_err
|
||||
|
||||
# {{{ Check for interaction area and connect signals only if the item is interactive
|
||||
if is_interactive:
|
||||
if has_node("area"):
|
||||
area = get_node("area")
|
||||
# XXX: Inventory items as Area2D did not work. z-index?
|
||||
if not self.inventory and not area is Area2D:
|
||||
vm.report_errors("item", ["Child area is not Area2D in " + self.global_id])
|
||||
elif self.inventory and not area is TextureRect:
|
||||
vm.report_errors("inventory item", ["Child area is not TextureRect in " + self.global_id])
|
||||
else:
|
||||
area = self
|
||||
if area is Position2D:
|
||||
vm.report_warnings("item", ["The Position2D node named " + self.global_id + " is probably erroneously marked as interactive."])
|
||||
elif not area is Area2D and not area is Position2D:
|
||||
vm.report_errors("item", ["Background item area is not Area2D nor Position2D in " + self.global_id])
|
||||
|
||||
if ClassDB.class_has_signal(area.get_class(), "input_event"):
|
||||
conn_err = area.connect("input_event", self, "area_input")
|
||||
if conn_err:
|
||||
vm.report_errors("item", ["area.input_event -> area_input error: " + String(conn_err)])
|
||||
elif ClassDB.class_has_signal(area.get_class(), "gui_input"):
|
||||
conn_err = area.connect("gui_input", self, "input")
|
||||
if conn_err:
|
||||
vm.report_errors("item", ["area.gui_input -> input error: " + String(conn_err)])
|
||||
else:
|
||||
vm.report_warnings("item", ["No input events possible for global_id " + global_id])
|
||||
|
||||
# These signals proxy the proper signals for regular and inventory items
|
||||
if ClassDB.class_has_signal(area.get_class(), "mouse_entered"):
|
||||
conn_err = area.connect("mouse_entered", self, "mouse_enter")
|
||||
if conn_err:
|
||||
vm.report_errors("item", ["mouse_entered -> mouse_enter error: " + String(conn_err)])
|
||||
|
||||
conn_err = area.connect("mouse_exited", self, "mouse_exit")
|
||||
if conn_err:
|
||||
vm.report_errors("item", ["mouse_exited -> mouse_exit error: " + String(conn_err)])
|
||||
|
||||
conn_err = connect("left_click_on_item", $"/root/scene/game", "ev_left_click_on_item")
|
||||
if conn_err:
|
||||
vm.report_errors("item", ["left_click_on_item -> ev_left_click_on_item error: " + String(conn_err)])
|
||||
|
||||
conn_err = connect("left_dblclick_on_item", $"/root/scene/game", "ev_left_dblclick_on_item")
|
||||
if conn_err:
|
||||
vm.report_errors("item", ["left_dblclick_on_item -> ev_left_dblclick_on_item error: " + String(conn_err)])
|
||||
|
||||
conn_err = connect("left_click_on_inventory_item", $"/root/scene/game", "ev_left_click_on_inventory_item")
|
||||
if conn_err:
|
||||
vm.report_errors("item", ["left_click_on_inventory_item -> ev_left_click_on_inventory_item error: " + String(conn_err)])
|
||||
|
||||
conn_err = connect("right_click_on_item", $"/root/scene/game", "ev_right_click_on_item")
|
||||
if conn_err:
|
||||
vm.report_errors("item", ["right_click_on_item -> ev_right_click_on_item error: " + String(conn_err)])
|
||||
|
||||
conn_err = connect("right_click_on_inventory_item", $"/root/scene/game", "ev_right_click_on_inventory_item")
|
||||
if conn_err:
|
||||
vm.report_errors("item", ["right_click_on_inventory_item -> ev_right_click_on_inventory_item error: " + String(conn_err)])
|
||||
|
||||
|
||||
conn_err = connect("mouse_enter_item", $"/root/scene/game", "ev_mouse_enter_item")
|
||||
if conn_err:
|
||||
vm.report_errors("item", ["mouse_enter_item -> ev_mouse_enter_item error: " + String(conn_err)])
|
||||
|
||||
conn_err = connect("mouse_enter_inventory_item", $"/root/scene/game", "ev_mouse_enter_inventory_item")
|
||||
if conn_err:
|
||||
vm.report_errors("item", ["mouse_enter_inventory_item -> ev_mouse_enter_inventory_item error: " + String(conn_err)])
|
||||
|
||||
conn_err = connect("mouse_exit_item", $"/root/scene/game", "ev_mouse_exit_item")
|
||||
if conn_err:
|
||||
vm.report_errors("item", ["mouse_exit_item -> ev_mouse_exit_item error: " + String(conn_err)])
|
||||
|
||||
conn_err = connect("mouse_exit_inventory_item", $"/root/scene/game", "ev_mouse_exit_inventory_item")
|
||||
if conn_err:
|
||||
vm.report_errors("item", ["mouse_exit_inventory_item -> ev_mouse_exit_inventory_item error: " + String(conn_err)])
|
||||
|
||||
if interact_position:
|
||||
interact_pos = get_node(interact_position)
|
||||
elif has_node("interact_pos"):
|
||||
interact_pos = $"interact_pos"
|
||||
# }}}
|
||||
|
||||
if has_node("camera_pos"):
|
||||
camera_pos = $"camera_pos"
|
||||
|
||||
if events_path != "":
|
||||
event_table = vm.compile(events_path)
|
||||
|
||||
# Forbit pipe because it's used to separate flags from actions, like in `:use item | TK`. And space for good measure.
|
||||
for c in ["|", " "]:
|
||||
if c in global_id:
|
||||
vm.report_errors("item", ["Forbidden character '" + c + "' in global_id: " + global_id])
|
||||
|
||||
if has_node("animation"):
|
||||
animation = $"animation"
|
||||
conn_err = animation.connect("animation_finished", self, "anim_finished")
|
||||
if conn_err:
|
||||
vm.report_errors("item", ["animation_finished -> anim_finished error: " + String(conn_err)])
|
||||
|
||||
if has_node("audio"):
|
||||
audio = $"audio"
|
||||
audio.set_bus("SFX")
|
||||
|
||||
_check_focus(false, false)
|
||||
|
||||
if has_node("../terrain"):
|
||||
terrain = $"../terrain"
|
||||
terrain_is_scalenodes = terrain is preload("terrain_scalenodes.gd")
|
||||
|
||||
_find_sprites(self)
|
||||
|
||||
# Initialize Node2D items' terrain status like z-index.
|
||||
# Stationary items will be set up correctly and
|
||||
# if an item moves, it will handle this in its _process() loop
|
||||
_update_terrain(true)
|
||||
|
||||
vm.register_object(global_id, self)
|
||||
|
36
device/globals/lightmap_area.gd
Normal file
36
device/globals/lightmap_area.gd
Normal file
|
@ -0,0 +1,36 @@
|
|||
extends Area2D
|
||||
|
||||
export var global_id = ""
|
||||
export var active = true setget set_active,get_active
|
||||
|
||||
func set_active(p_active):
|
||||
active = p_active
|
||||
if p_active:
|
||||
show()
|
||||
else:
|
||||
hide()
|
||||
|
||||
func get_active():
|
||||
return active
|
||||
|
||||
func body_entered(body):
|
||||
if "check_maps" in body:
|
||||
body.check_maps = true
|
||||
|
||||
func body_exited(body):
|
||||
if "check_maps" in body:
|
||||
body.check_maps = false
|
||||
|
||||
func _ready():
|
||||
var conn_err
|
||||
|
||||
conn_err = connect("body_entered", self, "body_entered")
|
||||
if conn_err:
|
||||
vm.report_errors("lightmap_area", ["body_entered -> body_entered error: " + String(conn_err)])
|
||||
|
||||
conn_err = connect("body_exited", self, "body_exited")
|
||||
if conn_err:
|
||||
vm.report_errors("item", ["body_exited -> body_exited error: " + String(conn_err)])
|
||||
|
||||
vm.register_object(global_id, self)
|
||||
|
217
device/globals/main.gd
Normal file
217
device/globals/main.gd
Normal file
|
@ -0,0 +1,217 @@
|
|||
extends Node
|
||||
|
||||
var telon
|
||||
var menu_layer
|
||||
var wait_timer
|
||||
var wait_level
|
||||
var current
|
||||
var menu_stack = []
|
||||
var game_size
|
||||
|
||||
#warning-ignore:unused_class_variable
|
||||
var screen_ofs = Vector2(0, 0) # game.gd
|
||||
|
||||
func clear_scene():
|
||||
if current == null:
|
||||
return
|
||||
|
||||
vm.clear_current_action()
|
||||
vm.clear_current_tool()
|
||||
vm.hover_clear_stack()
|
||||
vm.clear_inventory()
|
||||
vm.clear_tooltip()
|
||||
vm.clear_action_menu()
|
||||
|
||||
get_node("/root").remove_child(current)
|
||||
current.free()
|
||||
current = null
|
||||
|
||||
func set_scene(p_scene, run_events=true):
|
||||
if not p_scene:
|
||||
vm.report_errors("main", ["Trying to set empty scene"])
|
||||
|
||||
# Ensure we don't have a regular event running when changing scenes
|
||||
if vm.running_event:
|
||||
assert(vm.running_event.ev_name == "load")
|
||||
|
||||
if "events_path" in p_scene and p_scene.events_path and run_events:
|
||||
vm.load_file(p_scene.events_path)
|
||||
|
||||
# :setup is pretty much required in the code, but fortunately
|
||||
# we can help out with cases where one isn't necessary otherwise
|
||||
if not "setup" in vm.game:
|
||||
var fake_setup = vm.compile_str(":setup\n")
|
||||
vm.game["setup"] = fake_setup["setup"]
|
||||
|
||||
vm.run_event(vm.game["setup"])
|
||||
|
||||
if current != null:
|
||||
clear_scene()
|
||||
|
||||
get_node("/root").add_child(p_scene)
|
||||
|
||||
set_current_scene(p_scene, run_events)
|
||||
|
||||
func get_current_scene():
|
||||
return current
|
||||
|
||||
func menu_open(menu):
|
||||
menu_stack.push_back(menu)
|
||||
if menu_stack.size() == 1:
|
||||
get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFAULT, "game", "menu_opened")
|
||||
vm.set_pause(true)
|
||||
#get_tree().set_pause(true)
|
||||
pass
|
||||
|
||||
func menu_close(p_menu):
|
||||
if menu_stack.size() == 0 || menu_stack[menu_stack.size()-1] != p_menu:
|
||||
print("***** warning! closing unknown menu?")
|
||||
menu_stack.remove(menu_stack.size() - 1)
|
||||
|
||||
if menu_stack.size() == 0:
|
||||
vm.set_pause(false)
|
||||
get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFAULT, "game", "menu_closed")
|
||||
#get_tree().set_pause(false)
|
||||
pass
|
||||
|
||||
func load_menu(path):
|
||||
if path == "":
|
||||
printt("error: loading empty menu")
|
||||
return
|
||||
var menu = load(path)
|
||||
if menu == null:
|
||||
printt("error loading menu ", path)
|
||||
return
|
||||
printt("************* loding menu ", path, menu)
|
||||
menu = menu.instance()
|
||||
menu_layer.add_child(menu)
|
||||
return menu
|
||||
|
||||
func menu_collapse():
|
||||
var i = menu_stack.size()
|
||||
while i > 0:
|
||||
i -= 1
|
||||
menu_stack[i].menu_collapsed()
|
||||
|
||||
func set_current_scene(p_scene, run_events=true):
|
||||
#print_stack()
|
||||
|
||||
current = p_scene
|
||||
$"/root".move_child(current, 0)
|
||||
|
||||
# Loading a save game must set the scene but not run events
|
||||
if "events_path" in current and current.events_path and run_events:
|
||||
if vm.game:
|
||||
# Having a game with `:setup` means we must wait for it to finish
|
||||
if "setup" in vm.game:
|
||||
if not vm.running_event:
|
||||
vm.report_errors("main", ["vm.game has setup but no running_event"])
|
||||
|
||||
if vm.running_event.ev_name != "setup":
|
||||
vm.report_errors("main", ["vm.game has setup but it is not running: " + vm.running_event.ev_name])
|
||||
|
||||
yield(vm, "event_done")
|
||||
else:
|
||||
vm.load_file(current.events_path)
|
||||
# For a new game, we must run `:setup` if available
|
||||
# and wait for it to finish
|
||||
if "setup" in vm.game:
|
||||
vm.run_event(vm.game["setup"])
|
||||
yield(vm, "event_done")
|
||||
|
||||
# Because 1) changing a scene and 2) having a scene become ready
|
||||
# both call `set_current_scene`, we don't want to duplicate thing
|
||||
if not vm.running_event:
|
||||
vm.run_game()
|
||||
|
||||
vm.register_object("_scene", p_scene, true) # Force overwrite of global
|
||||
|
||||
func wait_finished():
|
||||
vm.finished(wait_level)
|
||||
|
||||
func wait(time, level):
|
||||
wait_level = level
|
||||
wait_timer.set_wait_time(time)
|
||||
wait_timer.set_one_shot(true)
|
||||
wait_timer.start()
|
||||
|
||||
func _input(event):
|
||||
match vm.accept_input:
|
||||
vm.acceptable_inputs.INPUT_NONE:
|
||||
# The only acceptable input here is releasing the MMB
|
||||
if event is InputEventMouseButton:
|
||||
if not event.pressed:
|
||||
if event.is_action("game_highlight"):
|
||||
vm.accept_input = vm.acceptable_inputs.INPUT_ALL
|
||||
if vm.tooltip:
|
||||
get_tree().call_group_flags(SceneTree.GROUP_CALL_REALTIME, "highlight_tooltip", "hide_highlight")
|
||||
vm.tooltip.force_tooltip_visible(true)
|
||||
vm.tooltip.update()
|
||||
return
|
||||
vm.acceptable_inputs.INPUT_SKIP:
|
||||
if event is InputEventMouseButton and event.pressed and event.is_action("game_general"):
|
||||
get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFAULT, "events", "skipped")
|
||||
return
|
||||
vm.acceptable_inputs.INPUT_ALL:
|
||||
if event is InputEventMouseButton:
|
||||
if event.pressed:
|
||||
if event.is_action("game_general"):
|
||||
get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFAULT, "events", "skipped")
|
||||
elif event.is_action("game_highlight"):
|
||||
# Deny all input, like walking around with the highlights open
|
||||
vm.accept_input = vm.acceptable_inputs.INPUT_NONE
|
||||
get_tree().call_group_flags(SceneTree.GROUP_CALL_REALTIME, "player", "halt")
|
||||
if vm.tooltip:
|
||||
vm.tooltip.force_tooltip_visible(false)
|
||||
get_tree().call_group_flags(SceneTree.GROUP_CALL_REALTIME, "highlight_tooltip", "show_highlight")
|
||||
|
||||
if event is InputEventKey and event.pressed:
|
||||
match(event.scancode):
|
||||
KEY_F12:
|
||||
OS.print_all_textures_by_size()
|
||||
|
||||
KEY_ESCAPE:
|
||||
get_tree().quit()
|
||||
|
||||
KEY_F2:
|
||||
vm.load_file(ProjectSettings.get_setting("escoria/platform/game_start_script"))
|
||||
vm.run_game()
|
||||
|
||||
KEY_F9:
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||
|
||||
KEY_F10:
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
|
||||
|
||||
KEY_F11:
|
||||
OS.window_fullscreen = !OS.window_fullscreen
|
||||
|
||||
if menu_stack.size() > 0:
|
||||
menu_stack[menu_stack.size() - 1].input(event)
|
||||
elif current != null:
|
||||
if current.has_node("game"):
|
||||
current.get_node("game").scene_input(event)
|
||||
|
||||
func load_telon():
|
||||
var tpath = ProjectSettings.get_setting("escoria/platform/telon")
|
||||
var tres = vm.res_cache.get_resource(tpath)
|
||||
|
||||
get_node("layers/telon/telon").replace_by_instance(tres)
|
||||
telon = get_node("layers/telon/telon")
|
||||
|
||||
func _ready():
|
||||
printt("main ready")
|
||||
# get_node("/root").set_render_target_clear_on_new_frame(true)
|
||||
|
||||
game_size = get_viewport().size
|
||||
|
||||
wait_timer = get_node("layers/wait_timer")
|
||||
if wait_timer != null:
|
||||
wait_timer.connect("timeout", self, "wait_finished")
|
||||
add_to_group("game")
|
||||
menu_layer = get_node("layers/menu")
|
||||
set_process_input(true)
|
||||
set_process(true)
|
||||
|
||||
call_deferred("load_telon")
|
||||
|
55
device/globals/main.tscn
Normal file
55
device/globals/main.tscn
Normal file
|
@ -0,0 +1,55 @@
|
|||
[gd_scene load_steps=5 format=2]
|
||||
|
||||
[ext_resource path="res://globals/main.gd" type="Script" id=1]
|
||||
[ext_resource path="res://globals/bg_music.tscn" type="PackedScene" id=2]
|
||||
[ext_resource path="res://globals/bg_snd.tscn" type="PackedScene" id=3]
|
||||
[ext_resource path="res://ui/dd_player.tscn" type="PackedScene" id=4]
|
||||
|
||||
[node name="main" type="Node"]
|
||||
|
||||
pause_mode = 2
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="layers" type="Node" parent="." index="0"]
|
||||
|
||||
[node name="telon" type="CanvasLayer" parent="layers" index="0"]
|
||||
|
||||
layer = 127
|
||||
offset = Vector2( 0, 0 )
|
||||
rotation = 0.0
|
||||
scale = Vector2( 1, 1 )
|
||||
transform = Transform2D( 1, 0, 0, 1, 0, 0 )
|
||||
|
||||
[node name="bg_music" parent="layers/telon" index="0" instance=ExtResource( 2 )]
|
||||
|
||||
[node name="telon" parent="layers/telon" index="1" instance_placeholder="res://globals/telon.tscn"]
|
||||
|
||||
[node name="bg_snd" parent="layers/telon" index="2" instance=ExtResource( 3 )]
|
||||
|
||||
[node name="dialog" type="CanvasLayer" parent="layers" index="1"]
|
||||
|
||||
layer = 128
|
||||
offset = Vector2( 0, 0 )
|
||||
rotation = 0.0
|
||||
scale = Vector2( 1, 1 )
|
||||
transform = Transform2D( 1, 0, 0, 1, 0, 0 )
|
||||
|
||||
[node name="dd_player" parent="layers/dialog" index="0" instance=ExtResource( 4 )]
|
||||
|
||||
[node name="menu" type="CanvasLayer" parent="layers" index="2"]
|
||||
|
||||
layer = 2
|
||||
offset = Vector2( 0, 0 )
|
||||
rotation = 0.0
|
||||
scale = Vector2( 1, 1 )
|
||||
transform = Transform2D( 1, 0, 0, 1, 0, 0 )
|
||||
|
||||
[node name="wait_timer" type="Timer" parent="layers" index="3"]
|
||||
|
||||
editor/display_folded = true
|
||||
process_mode = 1
|
||||
wait_time = 1.0
|
||||
one_shot = false
|
||||
autostart = false
|
||||
|
||||
|
33
device/globals/mask_light2d.gd
Normal file
33
device/globals/mask_light2d.gd
Normal file
|
@ -0,0 +1,33 @@
|
|||
tool
|
||||
|
||||
export(Vector2) var front_pos
|
||||
var pos2DZ
|
||||
|
||||
func init_mask():
|
||||
_update_terrain()
|
||||
update()
|
||||
|
||||
func get_front_position():
|
||||
if (has_node("front_pos")):
|
||||
front_pos = get_node("front_pos").get_global_position()
|
||||
return front_pos
|
||||
|
||||
|
||||
func _update_terrain():
|
||||
var pos = get_front_position()
|
||||
set_z_index(pos.y)
|
||||
set_z_range_min( 1 )
|
||||
set_z_range_max( pos.y )
|
||||
|
||||
func debug_print_z():
|
||||
printt("MASKS node Z : ", get_z_index())
|
||||
printt("node", "name", "Z", "Z_range_min", "Z_range_max")
|
||||
printt("node", get_name(), get_z_index(), get_z_range_min(), get_z_range_max())
|
||||
print("\n")
|
||||
|
||||
func _ready():
|
||||
init_mask()
|
||||
#debug_print_z()
|
||||
pass
|
||||
|
||||
|
15
device/globals/nav_poly_instance.gd
Normal file
15
device/globals/nav_poly_instance.gd
Normal file
|
@ -0,0 +1,15 @@
|
|||
extends NavigationPolygonInstance
|
||||
|
||||
export(String) var global_id
|
||||
|
||||
func set_active(p_active):
|
||||
enabled = p_active
|
||||
|
||||
if enabled:
|
||||
show()
|
||||
else:
|
||||
hide()
|
||||
|
||||
func _ready():
|
||||
vm.register_object(global_id, self)
|
||||
|
757
device/globals/npc.gd
Normal file
757
device/globals/npc.gd
Normal file
|
@ -0,0 +1,757 @@
|
|||
tool
|
||||
|
||||
extends KinematicBody2D
|
||||
|
||||
signal left_click_on_npc
|
||||
signal left_dblclick_on_npc
|
||||
signal right_click_on_npc
|
||||
signal mouse_enter_npc
|
||||
signal mouse_exit_npc
|
||||
|
||||
#warning-ignore:unused_class_variable
|
||||
export var global_id = "" # API property
|
||||
#warning-ignore:unused_class_variable
|
||||
export(String, FILE, ".esc") var events_path = "" # API property
|
||||
export var active = true setget set_active,get_active
|
||||
|
||||
export var tooltip = ""
|
||||
export var action = ""
|
||||
|
||||
export(NodePath) var interact_position = null
|
||||
#warning-ignore:unused_class_variable
|
||||
export var use_combine = false # game.gd
|
||||
#warning-ignore:unused_class_variable
|
||||
export var use_action_menu = true # game.gd
|
||||
|
||||
#warning-ignore:unused_class_variable
|
||||
export(int, -1, 360) var interact_angle = -1
|
||||
#warning-ignore:unused_class_variable
|
||||
export(Color) var dialog_color = null
|
||||
export(Script) var animations
|
||||
export var talk_animation = "talk"
|
||||
export var placeholders = {}
|
||||
export var dynamic_z_index = true
|
||||
export var speed = 300
|
||||
export var scale_on_map = false
|
||||
export var light_on_map = false setget set_light_on_map
|
||||
|
||||
var check_maps # set by lightmap_area.gd
|
||||
|
||||
var vm # A tool script cannot refer to singletons in Godot
|
||||
|
||||
var area
|
||||
|
||||
var orig_speed
|
||||
|
||||
var anim_notify = null
|
||||
var anim_scale_override = null
|
||||
var ui_anim = null
|
||||
|
||||
var clicked = false
|
||||
var interact_pos
|
||||
var camera_pos
|
||||
var terrain
|
||||
var terrain_is_scalenodes
|
||||
var walk_path
|
||||
var walk_context
|
||||
var moved
|
||||
var last_scale = Vector2(1, 1)
|
||||
var last_deg = null
|
||||
var last_dir = 0
|
||||
var animation
|
||||
var state = ""
|
||||
var walk_destination
|
||||
var path_ofs
|
||||
var pose_scale = 1
|
||||
var task
|
||||
var sprites = []
|
||||
var audio
|
||||
|
||||
# Godot doesn't do doubleclicks so we must
|
||||
var last_lmb_dt = 0
|
||||
var waiting_dblclick = false
|
||||
|
||||
# This'll contain a highlight tooltip if `tooltip_pos` is set as a child
|
||||
var highlight_tooltip
|
||||
|
||||
var event_table = {}
|
||||
|
||||
var width = float(ProjectSettings.get("display/window/size/width"))
|
||||
var height = float(ProjectSettings.get("display/window/size/height"))
|
||||
|
||||
func run_event(p_ev):
|
||||
vm.emit_signal("run_event", p_ev)
|
||||
|
||||
func activate(p_action, p_param = null):
|
||||
if p_param != null:
|
||||
p_action = p_action + " " + p_param.global_id
|
||||
|
||||
if p_action in event_table:
|
||||
run_event(event_table[p_action])
|
||||
else:
|
||||
return false
|
||||
return true
|
||||
|
||||
func set_active(p_active):
|
||||
active = p_active
|
||||
if p_active:
|
||||
show()
|
||||
else:
|
||||
hide()
|
||||
|
||||
func set_interactive(p_interactive):
|
||||
self.area.visible = p_interactive
|
||||
|
||||
func get_active():
|
||||
return active
|
||||
|
||||
func get_dialog_pos():
|
||||
if has_node("dialog_pos"):
|
||||
return $"dialog_pos".global_position
|
||||
|
||||
return global_position
|
||||
|
||||
func get_interact_pos():
|
||||
if interact_pos:
|
||||
return interact_pos.global_position
|
||||
|
||||
return global_position
|
||||
|
||||
func get_camera_pos():
|
||||
if camera_pos:
|
||||
return camera_pos.global_position
|
||||
|
||||
return global_position
|
||||
|
||||
#warning-ignore:unused_argument
|
||||
func anim_finished(anim_name):
|
||||
# TODO use parameter here?
|
||||
if anim_notify != null:
|
||||
vm.finished(anim_notify)
|
||||
anim_notify = null
|
||||
|
||||
if anim_scale_override != null:
|
||||
set_scale(get_scale() * anim_scale_override)
|
||||
anim_scale_override = null
|
||||
|
||||
# Although states are permanent until changed, the underlying animations are not,
|
||||
# so we must re-set the state for the appearance of permanence
|
||||
set_state(state, true)
|
||||
|
||||
if animations and "idles" in animations:
|
||||
pose_scale = animations.idles[last_dir + 1]
|
||||
_update_terrain()
|
||||
|
||||
|
||||
#return is_visible()
|
||||
|
||||
func get_action():
|
||||
return action
|
||||
|
||||
func mouse_enter():
|
||||
_check_focus(true, false)
|
||||
emit_signal("mouse_enter_npc", self)
|
||||
|
||||
func mouse_exit():
|
||||
_check_focus(false, false)
|
||||
emit_signal("mouse_exit_npc", self)
|
||||
|
||||
func area_input(_viewport, event, _shape_idx):
|
||||
# TODO: Expand this for other input events than mouse
|
||||
if event is InputEventMouseButton || event.is_action("ui_accept"):
|
||||
if event.is_pressed():
|
||||
clicked = vm.hover_stack.front() == self
|
||||
|
||||
var ev_pos = get_global_mouse_position()
|
||||
if event.is_action("game_general"):
|
||||
if last_lmb_dt <= vm.DOUBLECLICK_TIMEOUT:
|
||||
emit_signal("left_dblclick_on_npc", self, ev_pos, event)
|
||||
last_lmb_dt = 0
|
||||
waiting_dblclick = null
|
||||
else:
|
||||
last_lmb_dt = 0
|
||||
waiting_dblclick = [ev_pos, event]
|
||||
|
||||
elif event.is_action("game_rmb"):
|
||||
emit_signal("right_click_on_npc", self, ev_pos, event)
|
||||
_check_focus(true, true)
|
||||
|
||||
func _check_focus(focus, pressed):
|
||||
if has_node("_focus_in"):
|
||||
if focus:
|
||||
get_node("_focus_in").show()
|
||||
else:
|
||||
get_node("_focus_in").hide()
|
||||
|
||||
if has_node("_pressed"):
|
||||
if pressed:
|
||||
get_node("_pressed").show()
|
||||
else:
|
||||
get_node("_pressed").hide()
|
||||
|
||||
func get_tooltip(hint=null):
|
||||
if not tooltip:
|
||||
return null
|
||||
|
||||
# if `development_lang` matches `text_lang`, don't translate
|
||||
if TranslationServer.get_locale() == ProjectSettings.get_setting("escoria/platform/development_lang"):
|
||||
if not global_id and ProjectSettings.get_setting("escoria/platform/force_tooltip_global_id"):
|
||||
vm.report_errors("npc", ["Missing global_id in npc with tooltip '" + tooltip + "'"])
|
||||
return tooltip
|
||||
|
||||
# Otherwise try to return the translated tooltip
|
||||
var tooltip_identifier = global_id + ".tooltip"
|
||||
if hint:
|
||||
tooltip_identifier += "." + hint
|
||||
|
||||
var translated = tr(tooltip_identifier)
|
||||
|
||||
# Try again if there's no translation for this hint
|
||||
if translated == "\"":
|
||||
tooltip_identifier = global_id + ".tooltip"
|
||||
translated = tr(tooltip_identifier)
|
||||
|
||||
# But if translation isn't found, ensure it can be translated and return placeholder
|
||||
if translated == tooltip_identifier:
|
||||
if not global_id and ProjectSettings.get_setting("escoria/platform/force_tooltip_global_id"):
|
||||
vm.report_errors("npc", ["Missing global_id in npc with tooltip '" + tooltip + "'"])
|
||||
|
||||
return tooltip_identifier
|
||||
|
||||
return translated
|
||||
|
||||
func show_highlight():
|
||||
if not self.visible:
|
||||
return
|
||||
|
||||
if not self.area.visible:
|
||||
return
|
||||
|
||||
if not has_node("tooltip_pos"):
|
||||
return
|
||||
|
||||
highlight_tooltip = vm.tooltip.duplicate()
|
||||
assert(highlight_tooltip != vm.tooltip)
|
||||
|
||||
var tt_pos = $"tooltip_pos".global_position
|
||||
var tt_text = get_tooltip()
|
||||
|
||||
highlight_tooltip.highlight_only = true
|
||||
highlight_tooltip.follow_mouse = false
|
||||
highlight_tooltip.text = tt_text
|
||||
|
||||
tt_pos = vm.camera.zoom_transform.xform(tt_pos)
|
||||
|
||||
# Bail out if we're hopelessly out-of-view
|
||||
if tt_pos.x < 0 or tt_pos.x > width or tt_pos.y < 0 or tt_pos.y > height:
|
||||
highlight_tooltip.free()
|
||||
highlight_tooltip = null
|
||||
return
|
||||
|
||||
vm.tooltip.get_parent().add_child(highlight_tooltip)
|
||||
|
||||
highlight_tooltip.set_position(tt_pos)
|
||||
|
||||
highlight_tooltip.show()
|
||||
|
||||
func hide_highlight():
|
||||
if not highlight_tooltip:
|
||||
return
|
||||
|
||||
assert(highlight_tooltip.visible)
|
||||
|
||||
highlight_tooltip.hide()
|
||||
highlight_tooltip.free()
|
||||
highlight_tooltip = null
|
||||
|
||||
func global_changed(name):
|
||||
var ev = "global_changed "+name
|
||||
if ev in event_table:
|
||||
run_event(event_table[ev])
|
||||
elif "global_changed" in event_table:
|
||||
run_event(event_table.global_changed)
|
||||
|
||||
func anim_get_ph_paths(p_anim):
|
||||
if !(p_anim in placeholders):
|
||||
return null
|
||||
|
||||
var ret = []
|
||||
for p in placeholders[p_anim]:
|
||||
var n = get_node(p)
|
||||
if !(n is InstancePlaceholder):
|
||||
continue
|
||||
ret.push_back(n.get_instance_path())
|
||||
return ret
|
||||
|
||||
func play_anim(p_anim, p_notify = null, p_reverse = false, p_flip = null):
|
||||
if p_notify == null and (!animation or !animation.has_animation(p_anim)):
|
||||
print("skipping cut scene '", p_anim, "'")
|
||||
vm.finished(p_notify)
|
||||
#_debug_states()
|
||||
return
|
||||
|
||||
if p_anim in placeholders:
|
||||
for npath in placeholders[p_anim]:
|
||||
var node = get_node(npath)
|
||||
if !node is InstancePlaceholder:
|
||||
continue
|
||||
var path = node.get_instance_path()
|
||||
var res = vm.res_cache.get_resource(path)
|
||||
node.replace_by_instance(res)
|
||||
_find_sprites(get_node(npath))
|
||||
if p_flip != null:
|
||||
var s = get_scale()
|
||||
set_scale(s * p_flip)
|
||||
anim_scale_override = p_flip
|
||||
else:
|
||||
anim_scale_override = null
|
||||
|
||||
if p_reverse:
|
||||
animation.play(p_anim, -1, -1, true)
|
||||
else:
|
||||
animation.play(p_anim)
|
||||
anim_notify = p_notify
|
||||
|
||||
#_debug_states()
|
||||
|
||||
func play_snd(p_snd, p_loop=false):
|
||||
if !audio:
|
||||
vm.report_errors("npc", ["play_snd called with no audio node"])
|
||||
return
|
||||
|
||||
var resource = load(p_snd)
|
||||
if !resource:
|
||||
vm.report_errors("npc", ["play_snd resource not found " + p_snd])
|
||||
return
|
||||
|
||||
audio.stream = resource
|
||||
audio.stream.set_loop(p_loop)
|
||||
audio.play()
|
||||
|
||||
#_debug_states()
|
||||
|
||||
func set_speaking(p_speaking):
|
||||
printt("npc set speaking! ", global_id, p_speaking, state)
|
||||
#print_stack()
|
||||
if !animation:
|
||||
return
|
||||
if talk_animation == "":
|
||||
return
|
||||
if p_speaking:
|
||||
if animation.has_animation(talk_animation):
|
||||
animation.play(talk_animation)
|
||||
animation.seek(0, true)
|
||||
else:
|
||||
set_state(state, true)
|
||||
if animations and "idles" in animations:
|
||||
pose_scale = animations.idles[last_dir + 1]
|
||||
_update_terrain()
|
||||
|
||||
func set_state(p_state, p_force = false):
|
||||
if state == p_state && !p_force:
|
||||
return
|
||||
|
||||
# printt("set state ", "global_id: ", global_id, "state: ", state, "p_state: ", p_state, "p_force: ", p_force)
|
||||
|
||||
state = p_state
|
||||
|
||||
if animation != null:
|
||||
# Though calling `.play()` probably stops the animation, be safe.
|
||||
animation.stop()
|
||||
if animation.has_animation(p_state):
|
||||
animation.play(p_state)
|
||||
|
||||
func teleport(obj, angle=null):
|
||||
set_position(obj.global_position)
|
||||
if angle:
|
||||
set_angle(angle)
|
||||
moved = true
|
||||
_update_terrain(true)
|
||||
|
||||
func teleport_pos(x, y, angle=null):
|
||||
set_position(Vector2(x, y))
|
||||
if angle:
|
||||
set_angle(angle)
|
||||
moved = true
|
||||
_update_terrain(true)
|
||||
|
||||
func _update_terrain(need_z_update=false):
|
||||
# Objects in the scene tree will issue errors unless this is conditional
|
||||
if not terrain:
|
||||
return
|
||||
|
||||
var pos = get_position()
|
||||
|
||||
if dynamic_z_index and need_z_update:
|
||||
z_index = pos.y if pos.y <= VisualServer.CANVAS_ITEM_Z_MAX else VisualServer.CANVAS_ITEM_Z_MAX
|
||||
|
||||
if !scale_on_map && !light_on_map:
|
||||
return
|
||||
|
||||
var scale_range
|
||||
if terrain_is_scalenodes:
|
||||
scale_range = terrain.get_terrain(pos)
|
||||
elif check_maps:
|
||||
var color = terrain.get_terrain(pos)
|
||||
scale_range = terrain.get_scale_range(color.b)
|
||||
|
||||
if scale_on_map and scale_range != get_scale():
|
||||
# Check if `interact_pos` is a child of ours, and if so,
|
||||
# take a backup of the global position, because it will be affected by scaling.
|
||||
var interact_global_position
|
||||
if has_node("interact_pos"):
|
||||
interact_global_position = interact_pos.get_global_position()
|
||||
|
||||
# Same for camera_pos
|
||||
var camera_global_position
|
||||
if has_node("camera_pos"):
|
||||
camera_global_position = camera_pos.get_global_position()
|
||||
|
||||
self.scale = scale_range
|
||||
|
||||
# If `interact_pos` is a child, it was affected by scaling, so reset it
|
||||
# to the expected location.
|
||||
if interact_global_position:
|
||||
interact_pos.global_position = interact_global_position
|
||||
|
||||
# And camera position
|
||||
if camera_global_position:
|
||||
camera_pos.global_position = camera_global_position
|
||||
|
||||
if light_on_map and check_maps:
|
||||
var c = terrain.get_light(pos)
|
||||
if c:
|
||||
modulate(c)
|
||||
|
||||
# The npc's - as the player's - `animations` define the direction
|
||||
# as 1 or -1. This is stored as `pose_scale` and the easiest way
|
||||
# to flip a node is multiply its x-axis scale.
|
||||
if pose_scale == -1 and $"sprite".scale.x > 0:
|
||||
$"sprite".scale.x *= pose_scale
|
||||
$"area".scale.x *= pose_scale
|
||||
elif pose_scale == 1 and $"sprite".scale.x < 0:
|
||||
$"sprite".scale.x *= -1
|
||||
$"area".scale.x *= -1
|
||||
|
||||
func _check_bounds():
|
||||
printt("_check_bounds", terrain)
|
||||
#printt("checking bouds for pos ", get_position(), terrain.is_solid(get_position()))
|
||||
if !scale_on_map:
|
||||
return
|
||||
if !Engine.is_editor_hint():
|
||||
return
|
||||
if !terrain:
|
||||
return
|
||||
if terrain.is_solid(get_position()):
|
||||
if has_node("terrain_icon"):
|
||||
get_node("terrain_icon").hide()
|
||||
else:
|
||||
if !has_node("terrain_icon"):
|
||||
var node = Sprite.new()
|
||||
var tex = load("res://globals/terrain.png")
|
||||
node.set_texture(tex)
|
||||
add_child(node)
|
||||
node.set_name("terrain_icon")
|
||||
get_node("terrain_icon").show()
|
||||
|
||||
func _notification(what):
|
||||
if !is_inside_tree() || !Engine.is_editor_hint():
|
||||
return
|
||||
if what == CanvasItem.NOTIFICATION_TRANSFORM_CHANGED:
|
||||
call_deferred("_editor_transform_changed")
|
||||
|
||||
func _editor_transform_changed():
|
||||
_update_terrain()
|
||||
_check_bounds()
|
||||
|
||||
func hint_request():
|
||||
if !get_active():
|
||||
return
|
||||
if !vm.can_interact():
|
||||
return
|
||||
|
||||
if ui_anim == null:
|
||||
return
|
||||
|
||||
if ui_anim.is_playing():
|
||||
return
|
||||
|
||||
ui_anim.play("hint")
|
||||
|
||||
func setup_ui_anim():
|
||||
if has_node("ui_anims"):
|
||||
ui_anim = get_node("ui_anims")
|
||||
|
||||
for bg in get_tree().get_nodes_in_group("background"):
|
||||
bg.connect("right_click_on_bg", self, "hint_request")
|
||||
|
||||
var conn_err = vm.connect("global_changed", self, "global_changed")
|
||||
if conn_err:
|
||||
vm.report_errors("npc", ["global_changed -> global_changed error: " + String(conn_err)])
|
||||
|
||||
func set_light_on_map(p_light):
|
||||
light_on_map = p_light
|
||||
if light_on_map:
|
||||
_update_terrain()
|
||||
else:
|
||||
modulate(Color(1, 1, 1, 1))
|
||||
|
||||
func slide_to(pos, context = null):
|
||||
# Assume a straight line, and leverage some walk functionality
|
||||
walk_path = [get_position(), pos]
|
||||
walk_context = context
|
||||
if walk_path.size() == 0:
|
||||
walk_stop(get_position())
|
||||
set_process(false)
|
||||
task = null
|
||||
return
|
||||
|
||||
moved = true
|
||||
|
||||
walk_destination = walk_path[walk_path.size()-1]
|
||||
|
||||
path_ofs = 0.0
|
||||
task = "slide"
|
||||
set_process(true)
|
||||
|
||||
func slide(pos, p_speed, context = null):
|
||||
if p_speed:
|
||||
orig_speed = speed
|
||||
speed = p_speed
|
||||
slide_to(pos, context)
|
||||
|
||||
func walk_stop(pos):
|
||||
set_position(pos)
|
||||
walk_path = []
|
||||
|
||||
if orig_speed:
|
||||
speed = orig_speed
|
||||
orig_speed = null
|
||||
|
||||
# Walking is not a state, but we must re-set our previous state to reset the animation
|
||||
set_state(state)
|
||||
|
||||
if task == "walk":
|
||||
if "idles" in animations:
|
||||
pose_scale = animations.idles[last_dir + 1]
|
||||
_update_terrain(true)
|
||||
|
||||
task = null
|
||||
|
||||
if walk_context != null:
|
||||
vm.finished(walk_context)
|
||||
walk_context = null
|
||||
|
||||
func walk_to(pos, context = null):
|
||||
walk_path = terrain.get_terrain_path(get_position(), pos)
|
||||
walk_context = context
|
||||
if walk_path.size() == 0:
|
||||
walk_stop(get_position())
|
||||
set_process(false)
|
||||
task = null
|
||||
return
|
||||
moved = true
|
||||
walk_destination = walk_path[walk_path.size()-1]
|
||||
if terrain.is_solid(pos):
|
||||
walk_destination = walk_path[walk_path.size()-1]
|
||||
path_ofs = 0.0
|
||||
task = "walk"
|
||||
set_process(true)
|
||||
|
||||
func walk(pos, p_speed, context = null):
|
||||
if p_speed:
|
||||
orig_speed = speed
|
||||
speed = p_speed
|
||||
walk_to(pos, context)
|
||||
|
||||
func modulate(color):
|
||||
for s in sprites:
|
||||
s.set_modulate(color)
|
||||
|
||||
func _physics_process(dt):
|
||||
last_lmb_dt += dt
|
||||
|
||||
if waiting_dblclick and last_lmb_dt > vm.DOUBLECLICK_TIMEOUT:
|
||||
emit_signal("left_click_on_npc", self, waiting_dblclick[0], waiting_dblclick[1])
|
||||
last_lmb_dt = 0
|
||||
waiting_dblclick = null
|
||||
|
||||
func _process(time):
|
||||
if task == "walk" or task == "slide":
|
||||
var to_walk = speed * last_scale.x * time
|
||||
var pos = get_position()
|
||||
var old_pos = pos
|
||||
if walk_path.size() > 0:
|
||||
while to_walk > 0:
|
||||
var next
|
||||
if walk_path.size() > 1:
|
||||
next = walk_path[path_ofs + 1]
|
||||
else:
|
||||
next = walk_path[path_ofs]
|
||||
|
||||
var dist = pos.distance_to(next)
|
||||
|
||||
if dist > to_walk:
|
||||
var n = (next - pos).normalized()
|
||||
pos = pos + n * to_walk
|
||||
break
|
||||
pos = next
|
||||
to_walk -= dist
|
||||
path_ofs += 1
|
||||
if path_ofs >= walk_path.size() - 1:
|
||||
walk_stop(walk_destination)
|
||||
set_process(false)
|
||||
return
|
||||
|
||||
var angle = (old_pos.angle_to_point(pos)) * -1
|
||||
|
||||
set_position(pos)
|
||||
|
||||
if task == "walk":
|
||||
last_deg = vm._get_deg_from_rad(angle)
|
||||
last_dir = vm._get_dir_deg(last_deg, animations)
|
||||
|
||||
if animation:
|
||||
if animation.get_current_animation() != animations.directions[last_dir]:
|
||||
animation.play(animations.directions[last_dir])
|
||||
pose_scale = animations.directions[last_dir+1]
|
||||
|
||||
# If a z-indexed npc is moved, forcibly update its z index
|
||||
_update_terrain(true)
|
||||
|
||||
func turn_to(deg):
|
||||
if deg < 0 or deg > 360:
|
||||
vm.report_errors("interactive", ["Invalid degree to turn to " + str(deg)])
|
||||
|
||||
moved = true
|
||||
|
||||
last_deg = deg
|
||||
last_dir = vm._get_dir_deg(deg, animations)
|
||||
|
||||
if animation and animations and "directions" in animations:
|
||||
if !animation.get_current_animation() or animation.get_current_animation() != animations.directions[last_dir]:
|
||||
# XXX: This requires manually scripting a wait
|
||||
# and setting the correct idle animation
|
||||
animation.play(animations.directions[last_dir])
|
||||
pose_scale = animations.directions[last_dir + 1]
|
||||
_update_terrain()
|
||||
|
||||
func set_angle(deg):
|
||||
if deg < 0 or deg > 360:
|
||||
# Compensate for savegame files during a broken version of Escoria
|
||||
if vm.loading_game:
|
||||
vm.report_warnings("interactive", ["Reset invalid degree " + str(deg)])
|
||||
deg = 0
|
||||
else:
|
||||
vm.report_errors("interactive", ["Invalid degree to turn to " + str(deg)])
|
||||
|
||||
moved = true
|
||||
|
||||
last_deg = deg
|
||||
last_dir = vm._get_dir_deg(deg, animations)
|
||||
|
||||
if animation and animations and "idles" in animations:
|
||||
pose_scale = animations.idles[last_dir + 1]
|
||||
_update_terrain()
|
||||
|
||||
func _find_sprites(p = null):
|
||||
if p is CanvasItem:
|
||||
sprites.push_back(p)
|
||||
for i in range(0, p.get_child_count()):
|
||||
_find_sprites(p.get_child(i))
|
||||
|
||||
func _ready():
|
||||
add_to_group("npc")
|
||||
add_to_group("highlight_tooltip")
|
||||
|
||||
if has_node("../terrain"):
|
||||
terrain = $"../terrain"
|
||||
terrain_is_scalenodes = terrain is preload("terrain_scalenodes.gd")
|
||||
|
||||
if interact_position:
|
||||
interact_pos = get_node(interact_position)
|
||||
elif has_node("interact_pos"):
|
||||
interact_pos = $"interact_pos"
|
||||
|
||||
if has_node("camera_pos"):
|
||||
camera_pos = $"camera_pos"
|
||||
|
||||
_find_sprites(self)
|
||||
|
||||
# Initialize Node2D items' terrain status like z-index.
|
||||
# Stationary items will be set up correctly and
|
||||
# if an npc moves, it will handle this in its _process() loop
|
||||
_update_terrain(true)
|
||||
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
vm = $"/root/vm"
|
||||
|
||||
var conn_err
|
||||
|
||||
# {{{ Check for interaction area and connect signals only if the npc is interactive
|
||||
area = get_node("area")
|
||||
|
||||
conn_err = area.connect("input_event", self, "area_input")
|
||||
if conn_err:
|
||||
vm.report_errors("npc", ["area.input_event -> area_input error: " + String(conn_err)])
|
||||
|
||||
conn_err = area.connect("mouse_entered", self, "mouse_enter")
|
||||
if conn_err:
|
||||
vm.report_errors("npc", ["mouse_entered -> mouse_enter error: " + String(conn_err)])
|
||||
|
||||
conn_err = area.connect("mouse_exited", self, "mouse_exit")
|
||||
if conn_err:
|
||||
vm.report_errors("npc", ["mouse_exited -> mouse_exit error: " + String(conn_err)])
|
||||
|
||||
conn_err = connect("left_click_on_npc", $"/root/scene/game", "ev_left_click_on_npc")
|
||||
if conn_err:
|
||||
vm.report_errors("npc", ["left_click_on_npc -> ev_left_click_on_npc error: " + String(conn_err)])
|
||||
|
||||
conn_err = connect("left_dblclick_on_npc", $"/root/scene/game", "ev_left_dblclick_on_npc")
|
||||
if conn_err:
|
||||
vm.report_errors("npc", ["left_dblclick_on_npc -> ev_left_dblclick_on_npc error: " + String(conn_err)])
|
||||
|
||||
conn_err = connect("right_click_on_npc", $"/root/scene/game", "ev_right_click_on_npc")
|
||||
if conn_err:
|
||||
vm.report_errors("npc", ["right_click_on_npc -> ev_right_click_on_npc error: " + String(conn_err)])
|
||||
|
||||
conn_err = connect("mouse_enter_npc", $"/root/scene/game", "ev_mouse_enter_npc")
|
||||
if conn_err:
|
||||
vm.report_errors("npc", ["mouse_enter_npc -> ev_mouse_enter_npc error: " + String(conn_err)])
|
||||
|
||||
conn_err = connect("mouse_exit_npc", $"/root/scene/game", "ev_mouse_exit_npc")
|
||||
if conn_err:
|
||||
vm.report_errors("npc", ["mouse_exit_npc -> ev_mouse_exit_npc error: " + String(conn_err)])
|
||||
# }}}
|
||||
|
||||
if events_path != "":
|
||||
event_table = vm.compile(events_path)
|
||||
|
||||
# Forbit pipe because it's used to separate flags from actions, like in `:use item | TK`. And space for good measure.
|
||||
for c in ["|", " "]:
|
||||
if c in global_id:
|
||||
vm.report_errors("npc", ["Forbidden character '" + c + "' in global_id: " + global_id])
|
||||
|
||||
if not has_node("sprite"):
|
||||
vm.report_errors("npc", ["No sprite node set"])
|
||||
|
||||
if not has_node("collision"):
|
||||
vm.report_errors("npc", ["No collision node set"])
|
||||
|
||||
if has_node("animation"):
|
||||
animation = $"animation"
|
||||
conn_err = animation.connect("animation_finished", self, "anim_finished")
|
||||
if conn_err:
|
||||
vm.report_errors("npc", ["animation_finished -> anim_finished error: " + String(conn_err)])
|
||||
|
||||
if has_node("audio"):
|
||||
audio = $"audio"
|
||||
audio.set_bus("SFX")
|
||||
|
||||
_check_focus(false, false)
|
||||
|
||||
vm.register_object(global_id, self)
|
||||
|
||||
|
542
device/globals/player.gd
Normal file
542
device/globals/player.gd
Normal file
|
@ -0,0 +1,542 @@
|
|||
tool
|
||||
|
||||
extends KinematicBody2D
|
||||
|
||||
var task
|
||||
var walk_destination
|
||||
var animation
|
||||
var vm # A tool script cannot refer to singletons in Godot
|
||||
var terrain
|
||||
var terrain_is_scalenodes
|
||||
var walk_path
|
||||
var walk_context
|
||||
var moved
|
||||
var path_ofs
|
||||
export var speed = 300
|
||||
export var v_speed_damp = 1.0
|
||||
export(Script) var animations
|
||||
#warning-ignore:unused_class_variable
|
||||
export(Color) var dialog_color = null
|
||||
var last_deg = null
|
||||
var last_dir = 0
|
||||
var last_scale
|
||||
var pose_scale = 1
|
||||
var params_queue
|
||||
|
||||
export var telekinetic = false
|
||||
|
||||
var check_maps # set by lightmap_area.gd
|
||||
|
||||
var orig_speed
|
||||
|
||||
var anim_notify = null
|
||||
var anim_scale_override = null
|
||||
var sprites = []
|
||||
export var placeholders = {}
|
||||
|
||||
# Use `interact_status` to prevent player from moving past target when interacting.
|
||||
# This appears to happen because Godot doesn't discern a click from a double click
|
||||
# until the second click happens, by which time the player is already underway to
|
||||
# an invalid position.
|
||||
var interact_status = 0
|
||||
enum interact_statuses {INTERACT_NONE, INTERACT_STARTED, INTERACT_WALKING}
|
||||
|
||||
func get_camera_pos():
|
||||
if has_node("camera_pos"):
|
||||
return $"camera_pos".global_position
|
||||
|
||||
return global_position
|
||||
|
||||
func get_dialog_pos():
|
||||
if has_node("dialog_pos"):
|
||||
return $"dialog_pos".global_position
|
||||
|
||||
return global_position
|
||||
|
||||
func resolve_angle_to(obj):
|
||||
# Set `last_deg` and `last_dir` as they are globals
|
||||
var angle = self.position.angle_to_point(obj.position) * -1
|
||||
last_deg = vm._get_deg_from_rad(angle)
|
||||
# printt("Resolve angle from ", self.position, " to ", obj.position, ":", last_deg)
|
||||
last_dir = vm._get_dir_deg(last_deg, animations)
|
||||
|
||||
func set_active(p_active):
|
||||
if p_active:
|
||||
show()
|
||||
else:
|
||||
hide()
|
||||
|
||||
func walk_to(pos, context = null):
|
||||
if not terrain:
|
||||
return walk_stop(get_position())
|
||||
|
||||
if interact_status == interact_statuses.INTERACT_WALKING:
|
||||
return
|
||||
if interact_status == interact_statuses.INTERACT_STARTED:
|
||||
interact_status = interact_statuses.INTERACT_WALKING
|
||||
walk_path = terrain.get_terrain_path(get_position(), pos)
|
||||
walk_context = context
|
||||
if walk_path.size() == 0:
|
||||
task = null
|
||||
walk_stop(get_position())
|
||||
set_process(false)
|
||||
return
|
||||
moved = true
|
||||
walk_destination = walk_path[walk_path.size()-1]
|
||||
if terrain.is_solid(pos):
|
||||
walk_destination = walk_path[walk_path.size()-1]
|
||||
path_ofs = 0.0
|
||||
task = "walk"
|
||||
set_process(true)
|
||||
|
||||
func walk(pos, p_speed, context = null):
|
||||
if p_speed:
|
||||
orig_speed = speed
|
||||
speed = p_speed
|
||||
walk_to(pos, context)
|
||||
|
||||
func anim_finished(anim_name):
|
||||
# Ignore the signal if the previous animation finished while we're actually playing another one
|
||||
if animation.get_current_animation() and anim_name != animation.get_current_animation():
|
||||
# printt("player: anim_finished: ignore", anim_name, animation.get_current_animation())
|
||||
return
|
||||
|
||||
if anim_notify != null:
|
||||
vm.finished(anim_notify)
|
||||
anim_notify = null
|
||||
|
||||
if anim_scale_override != null:
|
||||
.set_scale(.get_scale() * anim_scale_override)
|
||||
anim_scale_override = null
|
||||
|
||||
animation.play(animations.idles[last_dir])
|
||||
pose_scale = animations.idles[last_dir + 1]
|
||||
_update_terrain()
|
||||
|
||||
func set_speaking(p_speaking):
|
||||
if p_speaking:
|
||||
animation.play(animations.speaks[last_dir])
|
||||
pose_scale = animations.speaks[last_dir + 1]
|
||||
else:
|
||||
animation.play(animations.idles[last_dir])
|
||||
pose_scale = animations.idles[last_dir + 1]
|
||||
_update_terrain()
|
||||
|
||||
func _find(p_val, p_array, p_flip):
|
||||
var i = 0
|
||||
for v in p_array:
|
||||
if typeof(v) == typeof(p_val) && v == p_val:
|
||||
if p_flip == null:
|
||||
return i
|
||||
else:
|
||||
if p_array[i+1] == p_flip:
|
||||
return i
|
||||
|
||||
i += 1
|
||||
return -1
|
||||
|
||||
func anim_get_ph_paths(p_anim):
|
||||
if !(p_anim in placeholders):
|
||||
return null
|
||||
|
||||
var ret = []
|
||||
for p in placeholders[p_anim]:
|
||||
var n = get_node(p)
|
||||
if !(n is InstancePlaceholder):
|
||||
continue
|
||||
ret.push_back(n.get_instance_path())
|
||||
return ret
|
||||
|
||||
func play_anim(p_anim, p_notify = null, p_reverse = false, p_flip = null):
|
||||
if not animation:
|
||||
vm.report_errors("player", ["Animation not found for play_anim"])
|
||||
if not animation.has_animation(p_anim):
|
||||
vm.report_errors("player", [animation.name + " does not contain " + p_anim])
|
||||
|
||||
if p_notify != null && (!animation || !animation.has_animation(p_anim)):
|
||||
vm.finished(p_notify)
|
||||
return
|
||||
|
||||
if p_anim in placeholders:
|
||||
for npath in placeholders[p_anim]:
|
||||
var node = get_node(npath)
|
||||
if !(node is InstancePlaceholder):
|
||||
continue
|
||||
var path = node.get_instance_path()
|
||||
var res = vm.res_cache.get_resource(path)
|
||||
node.replace_by_instance(res)
|
||||
_find_sprites(get_node(npath))
|
||||
|
||||
|
||||
pose_scale = 1
|
||||
_update_terrain()
|
||||
if p_flip != null:
|
||||
var s = .get_scale()
|
||||
.set_scale(s * p_flip)
|
||||
anim_scale_override = p_flip
|
||||
else:
|
||||
anim_scale_override = null
|
||||
|
||||
if p_reverse:
|
||||
animation.play(p_anim, -1, -1, true)
|
||||
else:
|
||||
animation.play(p_anim)
|
||||
|
||||
anim_notify = p_notify
|
||||
var dir = _find(p_anim, animations.directions, p_flip.x)
|
||||
if dir == -1:
|
||||
dir = _find(p_anim, animations.idles, p_flip.x)
|
||||
if dir != -1:
|
||||
last_dir = dir
|
||||
|
||||
set_process(false)
|
||||
|
||||
func interact(p_params):
|
||||
interact_status = interact_statuses.INTERACT_STARTED
|
||||
var obj = p_params[0]
|
||||
var action = p_params[1]
|
||||
var pos
|
||||
if obj.has_method("get_interact_pos"):
|
||||
pos = obj.get_interact_pos()
|
||||
else:
|
||||
pos = obj.get_global_position()
|
||||
|
||||
# Check if we are using an item with another, lest we fall back to fallbacks' :use
|
||||
# so this reworks action to match `:use inv_ice_cream`
|
||||
if p_params.size() > 2 and p_params[2]:
|
||||
var target = p_params[2].global_id
|
||||
action += " " + target
|
||||
|
||||
# It's safe to assume false, because it most likely gets reset
|
||||
# or we pass control over to fallbacks in game.interact()
|
||||
var do_walk = false
|
||||
var telekinetic_action = false
|
||||
if action in obj.event_table:
|
||||
var ev_flags = obj.event_table[action]["ev_flags"]
|
||||
|
||||
# Triggering a telekinetic default action by double-clicking will set
|
||||
# the player walking, so stop as immediately as possible. It would be
|
||||
# better if Godot waited a while to see if the first click of a double-click
|
||||
# is a click or should it be handled as a double-click.
|
||||
if not "TK" in ev_flags:
|
||||
do_walk = true
|
||||
else:
|
||||
telekinetic_action = true
|
||||
if walk_path:
|
||||
walk_stop(walk_path[0])
|
||||
else:
|
||||
walk_stop(get_position())
|
||||
|
||||
if not walk_context:
|
||||
walk_context = {"fast": true}
|
||||
else:
|
||||
walk_context["fast"] = true
|
||||
do_walk = true
|
||||
|
||||
if (not telekinetic and do_walk) and get_global_position().distance_to(pos) > 10:
|
||||
# It's important to set the queue before walking, so it
|
||||
# is in effect until walk_stop() has to reset the queue.
|
||||
params_queue = p_params
|
||||
walk_to(pos, walk_context)
|
||||
else:
|
||||
if animations.dir_angles.size() > 0:
|
||||
if not telekinetic_action and obj.interact_angle != -1:
|
||||
last_dir = vm._get_dir_deg(obj.interact_angle, animations)
|
||||
else:
|
||||
resolve_angle_to(obj)
|
||||
animation.play(animations.idles[last_dir])
|
||||
pose_scale = animations.idles[last_dir + 1]
|
||||
_update_terrain()
|
||||
get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFAULT, "game", "interact", p_params)
|
||||
|
||||
func slide_to(pos, context = null):
|
||||
# Assume a straight line, and leverage some walk functionality
|
||||
walk_path = [get_position(), pos]
|
||||
walk_context = context
|
||||
if walk_path.size() == 0:
|
||||
walk_stop(get_position())
|
||||
set_process(false)
|
||||
task = null
|
||||
return
|
||||
|
||||
moved = true
|
||||
|
||||
walk_destination = walk_path[walk_path.size()-1]
|
||||
|
||||
path_ofs = 0.0
|
||||
task = "slide"
|
||||
set_process(true)
|
||||
|
||||
func slide(pos, p_speed, context = null):
|
||||
if p_speed:
|
||||
orig_speed = speed
|
||||
speed = p_speed
|
||||
slide_to(pos, context)
|
||||
|
||||
func halt():
|
||||
return walk_stop(self.global_position)
|
||||
|
||||
func walk_stop(pos):
|
||||
set_position(pos)
|
||||
interact_status = interact_statuses.INTERACT_NONE
|
||||
walk_path = []
|
||||
|
||||
if orig_speed:
|
||||
speed = orig_speed
|
||||
orig_speed = null
|
||||
|
||||
task = null
|
||||
moved = false
|
||||
set_process(false)
|
||||
if params_queue != null:
|
||||
if animations.dir_angles.size() > 0:
|
||||
if params_queue[0].interact_angle == -1:
|
||||
resolve_angle_to(params_queue[0])
|
||||
else:
|
||||
last_dir = vm._get_dir_deg(params_queue[0].interact_angle, animations)
|
||||
animation.play(animations.idles[last_dir])
|
||||
pose_scale = animations.idles[last_dir + 1]
|
||||
_update_terrain()
|
||||
else:
|
||||
animation.play(animations.idles[last_dir])
|
||||
pose_scale = animations.idles[last_dir + 1]
|
||||
get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFAULT, "game", "interact", params_queue)
|
||||
# Clear params queue to prevent the same action from being triggered again
|
||||
params_queue = null
|
||||
else:
|
||||
animation.play(animations.idles[last_dir])
|
||||
pose_scale = animations.idles[last_dir + 1]
|
||||
_update_terrain()
|
||||
if walk_context != null:
|
||||
vm.finished(walk_context)
|
||||
walk_context = null
|
||||
|
||||
|
||||
func _notification(what):
|
||||
if !is_inside_tree() || !Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
if what == CanvasItem.NOTIFICATION_TRANSFORM_CHANGED:
|
||||
call_deferred("_editor_transform_changed")
|
||||
|
||||
func _editor_transform_changed():
|
||||
_update_terrain()
|
||||
_check_bounds()
|
||||
|
||||
func _check_bounds():
|
||||
if !terrain:
|
||||
return
|
||||
|
||||
#printt("checking bouds for pos ", get_position(), terrain.is_solid(get_position()))
|
||||
if terrain.is_solid(get_position()):
|
||||
if has_node("terrain_icon"):
|
||||
get_node("terrain_icon").hide()
|
||||
else:
|
||||
if !has_node("terrain_icon"):
|
||||
var node = Sprite.new()
|
||||
var tex = load("res://globals/terrain.png")
|
||||
node.set_texture(tex)
|
||||
add_child(node)
|
||||
node.set_name("terrain_icon")
|
||||
get_node("terrain_icon").show()
|
||||
|
||||
func _update_terrain():
|
||||
if !terrain:
|
||||
return
|
||||
|
||||
var pos = get_position()
|
||||
z_index = pos.y if pos.y <= VisualServer.CANVAS_ITEM_Z_MAX else VisualServer.CANVAS_ITEM_Z_MAX
|
||||
|
||||
var color
|
||||
if terrain_is_scalenodes:
|
||||
last_scale = terrain.get_terrain(pos)
|
||||
self.scale = last_scale
|
||||
elif check_maps:
|
||||
color = terrain.get_terrain(pos)
|
||||
var scal = terrain.get_scale_range(color.b)
|
||||
if scal != get_scale():
|
||||
last_scale = scal
|
||||
self.scale = last_scale
|
||||
|
||||
# Do not flip the entire player character, because that would conflict
|
||||
# with shadows that expect to be siblings of $"sprite"
|
||||
if pose_scale == -1 and $"sprite".scale.x > 0:
|
||||
$"sprite".scale.x *= pose_scale
|
||||
$"collision".scale.x *= pose_scale
|
||||
elif pose_scale == 1 and $"sprite".scale.x < 0:
|
||||
$"sprite".scale.x *= -1
|
||||
$"collision".scale.x *= -1
|
||||
|
||||
if check_maps:
|
||||
color = terrain.get_light(pos)
|
||||
|
||||
if color:
|
||||
for s in sprites:
|
||||
s.set_modulate(color)
|
||||
|
||||
func _process(time):
|
||||
if task == "walk" or task == "slide":
|
||||
var pos = get_position()
|
||||
var old_pos = pos
|
||||
var next
|
||||
if walk_path.size() > 1:
|
||||
next = walk_path[path_ofs + 1]
|
||||
else:
|
||||
next = walk_path[path_ofs]
|
||||
|
||||
var dist = speed * time * pow(last_scale.x, 2) * terrain.player_speed_multiplier
|
||||
if walk_context and "fast" in walk_context and walk_context.fast:
|
||||
dist *= terrain.player_doubleclick_speed_multiplier
|
||||
var dir = (next - pos).normalized()
|
||||
|
||||
# assume that x^2 + y^2 == 1, apply v_speed_damp the y axis
|
||||
#printt("dir before", dir)
|
||||
dir = dir * (dir.x * dir.x + dir.y * dir.y * v_speed_damp)
|
||||
#printt("dir after", dir, dist)
|
||||
|
||||
var new_pos
|
||||
if pos.distance_to(next) < dist:
|
||||
new_pos = next
|
||||
path_ofs += 1
|
||||
else:
|
||||
new_pos = pos + dir * dist
|
||||
|
||||
if path_ofs >= walk_path.size() - 1:
|
||||
walk_stop(walk_destination)
|
||||
return
|
||||
|
||||
pos = new_pos
|
||||
|
||||
var angle = (old_pos.angle_to_point(pos)) * -1
|
||||
set_position(pos)
|
||||
|
||||
if task == "walk":
|
||||
last_deg = vm._get_deg_from_rad(angle)
|
||||
last_dir = vm._get_dir_deg(last_deg, animations)
|
||||
|
||||
if animation.get_current_animation() != animations.directions[last_dir]:
|
||||
animation.play(animations.directions[last_dir])
|
||||
pose_scale = animations.directions[last_dir+1]
|
||||
|
||||
_update_terrain()
|
||||
else:
|
||||
moved = false
|
||||
|
||||
|
||||
func teleport(obj, angle=null):
|
||||
if animations.dir_angles.size() > 0 and not angle:
|
||||
if "interact_angle" in obj and obj.interact_angle != -1:
|
||||
last_deg = obj.interact_angle
|
||||
last_dir = vm._get_dir_deg(obj.interact_angle, animations)
|
||||
animation.play(animations.idles[last_dir])
|
||||
pose_scale = animations.idles[last_dir + 1]
|
||||
elif angle:
|
||||
set_angle(angle)
|
||||
|
||||
var pos
|
||||
if obj.has_method("get_interact_pos"):
|
||||
pos = obj.get_interact_pos()
|
||||
else:
|
||||
pos = obj.get_global_position()
|
||||
|
||||
set_position(pos)
|
||||
moved = true
|
||||
_update_terrain()
|
||||
|
||||
func set_state(costume):
|
||||
# Set a costume-state by changing the AnimationPlayer.
|
||||
if has_node(costume):
|
||||
animation = get_node(costume)
|
||||
else:
|
||||
vm.report_errors("player", ["Costume AnimationPlayer '" + costume + "' not found"])
|
||||
|
||||
animation.play(animations.idles[last_dir])
|
||||
|
||||
func teleport_pos(x, y, angle=null):
|
||||
set_position(Vector2(x, y))
|
||||
if angle:
|
||||
set_angle(angle)
|
||||
moved = true
|
||||
_update_terrain()
|
||||
|
||||
func turn_to(deg):
|
||||
if deg < 0 or deg > 360:
|
||||
vm.report_errors("player", ["Invalid degree to turn to " + str(deg)])
|
||||
|
||||
moved = true
|
||||
|
||||
last_deg = deg
|
||||
last_dir = vm._get_dir_deg(deg, animations)
|
||||
|
||||
if animation.get_current_animation() != animations.directions[last_dir]:
|
||||
animation.play(animations.directions[last_dir])
|
||||
pose_scale = animations.directions[last_dir+1]
|
||||
_update_terrain()
|
||||
|
||||
func set_angle(deg):
|
||||
if deg < 0 or deg > 360:
|
||||
# Compensate for savegame files during a broken version of Escoria
|
||||
if vm.loading_game:
|
||||
vm.report_warnings("player", ["Reset invalid degree " + str(deg)])
|
||||
deg = 0
|
||||
else:
|
||||
vm.report_errors("player", ["Invalid degree to turn to " + str(deg)])
|
||||
|
||||
moved = true
|
||||
|
||||
last_deg = deg
|
||||
last_dir = vm._get_dir_deg(deg, animations)
|
||||
|
||||
# The player may have a state animation from before, which would be
|
||||
# resumed, so we immediately force the correct idle animation
|
||||
if animation.get_current_animation() != animations.idles[last_dir]:
|
||||
animation.play(animations.idles[last_dir])
|
||||
pose_scale = animations.idles[last_dir+1]
|
||||
_update_terrain()
|
||||
|
||||
|
||||
func _find_sprites(p = null):
|
||||
if p is CanvasItem:
|
||||
sprites.push_back(p)
|
||||
for i in range(0, p.get_child_count()):
|
||||
_find_sprites(p.get_child(i))
|
||||
|
||||
func _ready():
|
||||
add_to_group("player")
|
||||
|
||||
if has_node("../terrain"):
|
||||
terrain = $"../terrain"
|
||||
terrain_is_scalenodes = terrain is preload("terrain_scalenodes.gd")
|
||||
|
||||
_find_sprites(self)
|
||||
|
||||
# Set the player up for z-index, scale, light etc, update later when moving
|
||||
_update_terrain()
|
||||
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
vm = $"/root/vm"
|
||||
|
||||
if not has_node("sprite"):
|
||||
vm.report_errors("player", ["No sprite node set"])
|
||||
|
||||
if not has_node("collision"):
|
||||
vm.report_errors("player", ["No collision node set"])
|
||||
|
||||
if has_node("default"):
|
||||
animation = $"default"
|
||||
|
||||
if not animations:
|
||||
vm.report_errors("player", ["Animations not set for player."])
|
||||
else:
|
||||
vm.report_errors("player", ["No default AnimationPlayer found"])
|
||||
|
||||
for child in get_children():
|
||||
if child is AnimationPlayer:
|
||||
child.connect("animation_finished", self, "anim_finished")
|
||||
|
||||
vm.register_object("player", self)
|
||||
|
||||
last_scale = .get_scale()
|
||||
set_process(true)
|
190
device/globals/resource_queue.gd
Normal file
190
device/globals/resource_queue.gd
Normal file
|
@ -0,0 +1,190 @@
|
|||
var thread
|
||||
var mutex
|
||||
var sem
|
||||
|
||||
#warning-ignore:unused_class_variable
|
||||
var time_max = 100 # msec
|
||||
|
||||
var queue = []
|
||||
var pending = {}
|
||||
|
||||
#warning-ignore:unused_argument
|
||||
func _lock(caller):
|
||||
mutex.lock()
|
||||
|
||||
#warning-ignore:unused_argument
|
||||
func _unlock(caller):
|
||||
mutex.unlock()
|
||||
|
||||
#warning-ignore:unused_argument
|
||||
func _post(caller):
|
||||
sem.post()
|
||||
|
||||
#warning-ignore:unused_argument
|
||||
func _wait(caller):
|
||||
sem.wait()
|
||||
|
||||
|
||||
func queue_resource(path, p_in_front = false, p_permanent = false):
|
||||
_lock("queue_resource")
|
||||
if path in pending:
|
||||
_unlock("queue_resource")
|
||||
return
|
||||
|
||||
elif ResourceLoader.has(path):
|
||||
var res = ResourceLoader.load(path)
|
||||
pending[path] = { "res": res, "permanent": p_permanent }
|
||||
_unlock("queue_resource")
|
||||
return
|
||||
else:
|
||||
var res = ResourceLoader.load_interactive(path)
|
||||
res.set_meta("path", path)
|
||||
if p_in_front:
|
||||
queue.insert(0, res)
|
||||
else:
|
||||
queue.push_back(res)
|
||||
pending[path] = { "res": res, "permanent": p_permanent }
|
||||
_post("queue_resource")
|
||||
_unlock("queue_resource")
|
||||
return
|
||||
|
||||
func cancel_resource(path):
|
||||
_lock("cancel_resource")
|
||||
if path in pending:
|
||||
if pending[path].res is ResourceInteractiveLoader:
|
||||
queue.erase(pending[path].res)
|
||||
pending.erase(path)
|
||||
_unlock("cancel_resource")
|
||||
|
||||
func clear():
|
||||
_lock("clear")
|
||||
|
||||
for p in pending.keys():
|
||||
if pending[p].permanent:
|
||||
continue
|
||||
cancel_resource(p)
|
||||
#queue = []
|
||||
#pending = {}
|
||||
|
||||
_unlock("clear")
|
||||
|
||||
|
||||
func get_progress(path):
|
||||
_lock("get_progress")
|
||||
var ret = -1
|
||||
if path in pending:
|
||||
if pending[path].res is ResourceInteractiveLoader:
|
||||
ret = float(pending[path].res.get_stage()) / float(pending[path].res.get_stage_count())
|
||||
else:
|
||||
ret = 1.0
|
||||
emit_signal("resource_loading_done", path)
|
||||
emit_signal("resource_loading_progress", path, ret)
|
||||
_unlock("get_progress")
|
||||
|
||||
return ret
|
||||
|
||||
func is_ready(path):
|
||||
var ret
|
||||
_lock("is_ready")
|
||||
if path in pending:
|
||||
ret = !(pending[path].res is ResourceInteractiveLoader)
|
||||
else:
|
||||
ret = false
|
||||
|
||||
_unlock("is_ready")
|
||||
|
||||
return ret
|
||||
|
||||
func _wait_for_resource(res, path):
|
||||
_unlock("wait_for_resource")
|
||||
while true:
|
||||
#VisualServer.call("sync") # workaround because sync is a keyword
|
||||
VisualServer.force_sync()
|
||||
OS.delay_usec(16000) # wait 1 frame
|
||||
_lock("wait_for_resource")
|
||||
if queue.size() == 0 || queue[0] != res:
|
||||
return pending[path].res
|
||||
_unlock("wait_for_resource")
|
||||
|
||||
|
||||
func get_resource(path):
|
||||
_lock("get_resource")
|
||||
if path in pending:
|
||||
if pending[path].res is ResourceInteractiveLoader:
|
||||
var res = pending[path].res
|
||||
if res != queue[0]:
|
||||
var pos = queue.find(res)
|
||||
queue.remove(pos)
|
||||
queue.insert(0, res)
|
||||
|
||||
res = _wait_for_resource(res, path)
|
||||
|
||||
if !pending[path].permanent:
|
||||
pending.erase(path)
|
||||
_unlock("return")
|
||||
return res
|
||||
|
||||
else:
|
||||
var res = pending[path].res
|
||||
if !pending[path].permanent:
|
||||
pending.erase(path)
|
||||
_unlock("return")
|
||||
return res
|
||||
else:
|
||||
_unlock("return")
|
||||
return ResourceLoader.load(path)
|
||||
|
||||
func thread_process():
|
||||
_wait("thread_process")
|
||||
|
||||
_lock("process")
|
||||
|
||||
while queue.size() > 0:
|
||||
var res = queue[0]
|
||||
|
||||
_unlock("process_poll")
|
||||
var ret = res.poll()
|
||||
_lock("process_check_queue")
|
||||
|
||||
var path = res.get_meta("path")
|
||||
if ret == ERR_FILE_EOF || ret != OK:
|
||||
printt("finished loading ", path)
|
||||
if path in pending: # else it was already retrieved
|
||||
pending[res.get_meta("path")].res = res.get_resource()
|
||||
|
||||
queue.erase(res) # something might have been put at the front of the queue while we polled, so use erase instead of remove
|
||||
emit_signal("resource_queue_progress", queue.size())
|
||||
|
||||
get_progress(path)
|
||||
|
||||
_unlock("process")
|
||||
|
||||
#warning-ignore:unused_argument
|
||||
func thread_func(u):
|
||||
while true:
|
||||
thread_process()
|
||||
|
||||
func print_progress(p_path, p_progress):
|
||||
printt(p_path, "loading", round(p_progress * 100), "%")
|
||||
|
||||
func res_loaded(p_path):
|
||||
printt("loaded resource", p_path)
|
||||
|
||||
func print_queue_progress(p_queue_size):
|
||||
printt("queue size:", p_queue_size)
|
||||
|
||||
func start():
|
||||
mutex = Mutex.new()
|
||||
sem = Semaphore.new()
|
||||
thread = Thread.new()
|
||||
thread.start(self, "thread_func", 0)
|
||||
|
||||
add_user_signal("resource_loading_progress", ["path", "progress"])
|
||||
add_user_signal("resource_loading_done", ["path"])
|
||||
add_user_signal("resource_queue_progress", ["queue_size"])
|
||||
|
||||
## Uncomment these for debug, or wait for someone to implement log levels
|
||||
# connect("resource_loading_progress", self, "print_progress")
|
||||
# connect("resource_loading_done", self, "res_loaded")
|
||||
# connect("resource_queue_progress", self, "print_queue_progress")
|
||||
|
202
device/globals/save_data.gd
Normal file
202
device/globals/save_data.gd
Normal file
|
@ -0,0 +1,202 @@
|
|||
|
||||
const DATA_STRING = 0
|
||||
const DATA_STRING_ARRAY = 1
|
||||
const DATA_VARIANT = 2
|
||||
|
||||
var base = "user://esc_saves"
|
||||
var slots = {}
|
||||
var max_slots = 3
|
||||
var settings
|
||||
|
||||
func save_settings(p_data, p_callback):
|
||||
var f = File.new()
|
||||
f.open("user://settings.bin", File.WRITE)
|
||||
f.store_var(p_data)
|
||||
f.close()
|
||||
|
||||
if typeof(p_callback) != typeof(null):
|
||||
p_callback[0].call_deferred(p_callback[1], OK)
|
||||
|
||||
return OK
|
||||
|
||||
func load_settings(p_callback):
|
||||
var f = File.new()
|
||||
f.open("user://settings.bin", File.READ)
|
||||
if !f.is_open():
|
||||
if typeof(p_callback) != typeof(null):
|
||||
p_callback[0].call_deferred(p_callback[1], null)
|
||||
return FAILED
|
||||
|
||||
settings = f.get_var()
|
||||
f.close()
|
||||
|
||||
if typeof(p_callback) != typeof(null):
|
||||
p_callback[0].call_deferred(p_callback[1], settings)
|
||||
|
||||
return OK
|
||||
|
||||
func _get_fname(p_slot):
|
||||
|
||||
var date = OS.get_date()
|
||||
var time = OS.get_time()
|
||||
|
||||
var day = str(date.day)
|
||||
if date.day < 10:
|
||||
day = "0"+day
|
||||
|
||||
|
||||
var hour = str(time.hour)
|
||||
if time.hour < 10:
|
||||
hour = "0"+hour
|
||||
|
||||
var minute = str(time.minute)
|
||||
if time.minute < 10:
|
||||
minute = "0"+minute
|
||||
|
||||
var second = str(time.second)
|
||||
if time.second < 10:
|
||||
second = "0"+second
|
||||
|
||||
var fname = str(p_slot) + "-"
|
||||
fname = fname + day + "-" + str(date.month) + "-" + str(date.year) + " " + hour+"."+minute+"."+second+".esc"
|
||||
|
||||
return fname
|
||||
|
||||
|
||||
func save_game(p_data, p_slot, p_callback):
|
||||
|
||||
if p_slot < 0 || p_slot >= max_slots:
|
||||
return FAILED
|
||||
|
||||
var fname = _get_fname(p_slot)
|
||||
var ret = _do_save(base + "/" + fname, p_data)
|
||||
if ret != OK:
|
||||
if typeof(p_callback) != typeof(null):
|
||||
p_callback[0].call_deferred(p_callback[1], FAILED)
|
||||
return FAILED
|
||||
|
||||
if p_slot in slots:
|
||||
var old_fname = slots[p_slot].fname
|
||||
var d = Directory.new()
|
||||
d.open(base)
|
||||
d.remove(old_fname)
|
||||
|
||||
if typeof(p_callback) != typeof(null):
|
||||
p_callback[0].call_deferred(p_callback[1], OK)
|
||||
return OK
|
||||
|
||||
func _do_save(fname, p_data):
|
||||
var f = File.new()
|
||||
var ret = f.open(fname, File.WRITE)
|
||||
if ret or not f.is_open():
|
||||
print("Unable to open file for save ", fname)
|
||||
return FAILED
|
||||
|
||||
if typeof(p_data) == typeof([]):
|
||||
for s in p_data:
|
||||
f.store_string(s)
|
||||
else:
|
||||
f.store_string(p_data)
|
||||
|
||||
f.close()
|
||||
printt("Saved game to " + fname)
|
||||
|
||||
return OK
|
||||
|
||||
func load_slot(p_slot, p_callback):
|
||||
if p_callback == null:
|
||||
return FAILED
|
||||
|
||||
if !(p_slot in slots):
|
||||
return FAILED
|
||||
|
||||
var data = _do_load(slots[p_slot].fname)
|
||||
if !data:
|
||||
return FAILED
|
||||
|
||||
p_callback[0].call_deferred(p_callback[1], data)
|
||||
|
||||
return OK
|
||||
|
||||
func load_autosave(p_callback):
|
||||
if p_callback == null:
|
||||
return FAILED
|
||||
|
||||
var data = _do_load("user://quick_save.esc")
|
||||
if data == null:
|
||||
return FAILED
|
||||
|
||||
p_callback[0].call_deferred(p_callback[1], data)
|
||||
|
||||
return OK
|
||||
|
||||
|
||||
func _do_load(fname):
|
||||
|
||||
var f = File.new()
|
||||
if !f.file_exists(fname):
|
||||
return null
|
||||
|
||||
f.open(fname, File.READ)
|
||||
var data = f.get_as_text()
|
||||
f.close()
|
||||
|
||||
return data
|
||||
|
||||
|
||||
func autosave(p_data, p_callback):
|
||||
var err = _do_save("user://quick_save.esc", p_data)
|
||||
|
||||
if typeof(p_callback) != typeof(null):
|
||||
p_callback[0].call_deferred(p_callback[1], err)
|
||||
|
||||
return err
|
||||
|
||||
func get_slots_available(p_callback):
|
||||
|
||||
if p_callback == null:
|
||||
return FAILED
|
||||
|
||||
var d = Directory.new()
|
||||
d.open("user://")
|
||||
if !d.dir_exists(base):
|
||||
d.make_dir(base)
|
||||
d.open(base)
|
||||
|
||||
|
||||
d.list_dir_begin()
|
||||
var f = d.get_next()
|
||||
while f != "":
|
||||
|
||||
if f.find(".esc") < 0 || f.find("-") < 0:
|
||||
f = d.get_next()
|
||||
continue
|
||||
|
||||
var sep = f.find("-")
|
||||
var n = int(f.substr(0, sep))
|
||||
if n >= max_slots:
|
||||
f = d.get_next()
|
||||
continue
|
||||
|
||||
var t = f.replace(".esc", "")
|
||||
t = t.substr(2, t.length()-2)
|
||||
var l = t.split(" ")
|
||||
var h = l[1]
|
||||
var date = l[0]
|
||||
|
||||
slots[n] = { "n": n, "fname": base + "/" + f, "date": date, "hour": h }
|
||||
|
||||
f = d.get_next()
|
||||
|
||||
d.list_dir_end()
|
||||
|
||||
p_callback[0].call_deferred(p_callback[1], slots)
|
||||
|
||||
return OK
|
||||
|
||||
func autosave_available():
|
||||
var f = File.new()
|
||||
return f.file_exists("user://quick_save.esc")
|
||||
|
||||
func start():
|
||||
pass
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue