Compare commits
27 Commits
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
Binary file not shown.
|
@ -0,0 +1,93 @@
|
|||
Copyright 2020 The Yusei Magic Project Authors (https://github.com/tanukifont/YuseiMagic)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -28,6 +28,7 @@ process/fix_alpha_border=true
|
|||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
|
|
|
@ -28,6 +28,7 @@ process/fix_alpha_border=true
|
|||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
[gd_resource type="AtlasTexture" load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://assets/spritesheet-2048.png" type="Texture" id=1]
|
||||
|
||||
[resource]
|
||||
flags = 4
|
||||
atlas = ExtResource( 1 )
|
||||
region = Rect2( 0, 1024, 512, 512 )
|
BIN
default.theme
BIN
default.theme
Binary file not shown.
|
@ -8,21 +8,20 @@ export_filter="all_resources"
|
|||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="../../Rhythm.x86_64"
|
||||
patch_list=PoolStringArray( )
|
||||
script_export_mode=1
|
||||
script_encryption_key=""
|
||||
|
||||
[preset.0.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
binary_format/architecture="x86_64"
|
||||
binary_format/embed_pck=false
|
||||
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=false
|
||||
custom_template/release=""
|
||||
custom_template/debug=""
|
||||
|
||||
[preset.1]
|
||||
|
||||
|
@ -34,50 +33,55 @@ export_filter="all_resources"
|
|||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="../../Rhythm.apk"
|
||||
patch_list=PoolStringArray( )
|
||||
script_export_mode=1
|
||||
script_encryption_key=""
|
||||
|
||||
[preset.1.options]
|
||||
|
||||
graphics/32_bits_framebuffer=true
|
||||
xr_features/xr_mode=0
|
||||
xr_features/degrees_of_freedom=0
|
||||
xr_features/hand_tracking=0
|
||||
one_click_deploy/clear_previous_install=false
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
custom_template/use_custom_build=false
|
||||
command_line/extra_args=""
|
||||
version/code=1
|
||||
version/name="1.0"
|
||||
package/unique_name="au.ufeff.rhythmgame"
|
||||
package/name="Rhythm Game"
|
||||
package/signed=true
|
||||
screen/immersive_mode=true
|
||||
screen/orientation=0
|
||||
screen/support_small=true
|
||||
screen/support_normal=true
|
||||
screen/support_large=true
|
||||
screen/support_xlarge=true
|
||||
screen/opengl_debug=false
|
||||
launcher_icons/main_192x192=""
|
||||
launcher_icons/adaptive_foreground_432x432=""
|
||||
launcher_icons/adaptive_background_432x432=""
|
||||
custom_build/use_custom_build=false
|
||||
custom_build/export_format=0
|
||||
custom_build/min_sdk=""
|
||||
custom_build/target_sdk=""
|
||||
architectures/armeabi-v7a=true
|
||||
architectures/arm64-v8a=true
|
||||
architectures/x86=false
|
||||
architectures/x86_64=false
|
||||
keystore/debug=""
|
||||
keystore/debug_user=""
|
||||
keystore/debug_password=""
|
||||
keystore/release=""
|
||||
keystore/release_user=""
|
||||
keystore/release_password=""
|
||||
one_click_deploy/clear_previous_install=false
|
||||
version/code=1
|
||||
version/name="1.0"
|
||||
package/unique_name="au.ufeff.rhythmgame"
|
||||
package/name="Rhythm Game"
|
||||
package/signed=true
|
||||
package/classify_as_game=true
|
||||
package/retain_data_on_uninstall=false
|
||||
package/exclude_from_recents=false
|
||||
launcher_icons/main_192x192=""
|
||||
launcher_icons/adaptive_foreground_432x432=""
|
||||
launcher_icons/adaptive_background_432x432=""
|
||||
graphics/opengl_debug=false
|
||||
xr_features/xr_mode=0
|
||||
xr_features/hand_tracking=0
|
||||
xr_features/hand_tracking_frequency=0
|
||||
xr_features/passthrough=0
|
||||
screen/immersive_mode=true
|
||||
screen/support_small=true
|
||||
screen/support_normal=true
|
||||
screen/support_large=true
|
||||
screen/support_xlarge=true
|
||||
user_data_backup/allow=false
|
||||
command_line/extra_args=""
|
||||
apk_expansion/enable=false
|
||||
apk_expansion/SALT=""
|
||||
apk_expansion/public_key=""
|
||||
architectures/armeabi-v7a=true
|
||||
architectures/arm64-v8a=true
|
||||
architectures/x86=false
|
||||
architectures/x86_64=false
|
||||
permissions/custom_permissions=PoolStringArray( )
|
||||
permissions/custom_permissions=PoolStringArray( "MANAGE_EXTERNAL_STORAGE" )
|
||||
permissions/access_checkin_properties=false
|
||||
permissions/access_coarse_location=false
|
||||
permissions/access_fine_location=false
|
||||
|
@ -150,6 +154,7 @@ permissions/location_hardware=false
|
|||
permissions/manage_accounts=false
|
||||
permissions/manage_app_tokens=false
|
||||
permissions/manage_documents=false
|
||||
permissions/manage_external_storage=true
|
||||
permissions/master_clear=false
|
||||
permissions/media_content_control=false
|
||||
permissions/modify_audio_settings=false
|
||||
|
@ -158,6 +163,7 @@ permissions/mount_format_filesystems=false
|
|||
permissions/mount_unmount_filesystems=false
|
||||
permissions/nfc=false
|
||||
permissions/persistent_activity=false
|
||||
permissions/post_notifications=false
|
||||
permissions/process_outgoing_calls=false
|
||||
permissions/read_calendar=false
|
||||
permissions/read_call_log=false
|
||||
|
@ -234,15 +240,27 @@ export_filter="all_resources"
|
|||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path=""
|
||||
patch_list=PoolStringArray( )
|
||||
script_export_mode=1
|
||||
script_encryption_key=""
|
||||
|
||||
[preset.2.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
variant/export_type=0
|
||||
vram_texture_compression/for_desktop=true
|
||||
vram_texture_compression/for_mobile=false
|
||||
html/export_icon=true
|
||||
html/custom_html_shell=""
|
||||
html/head_include=""
|
||||
custom_template/release=""
|
||||
custom_template/debug=""
|
||||
html/canvas_resize_policy=2
|
||||
html/focus_canvas_on_start=true
|
||||
html/experimental_virtual_keyboard=false
|
||||
progressive_web_app/enabled=false
|
||||
progressive_web_app/offline_page=""
|
||||
progressive_web_app/display=1
|
||||
progressive_web_app/orientation=0
|
||||
progressive_web_app/icon_144x144=""
|
||||
progressive_web_app/icon_180x180=""
|
||||
progressive_web_app/icon_512x512=""
|
||||
progressive_web_app/background_color=Color( 0, 0, 0, 1 )
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
extends Node
|
||||
# RhythmGameText formats
|
||||
# .rgts - simplified format cutting out redundant data, should be easy to write charts in
|
||||
# .rgtx - a lossless representation of MM in-memory format
|
||||
# .rgtm - a collection of rgts charts, with a [title] at the start of each one
|
||||
enum Format{RGTS, RGTX, RGTM}
|
||||
|
||||
const EXTENSIONS = {
|
||||
'rgts': Format.RGTS,
|
||||
'rgtx': Format.RGTX,
|
||||
'rgtm': Format.RGTM,
|
||||
}
|
||||
|
||||
const NOTE_TYPES = {
|
||||
't': Note.NOTE_TAP,
|
||||
'h': Note.NOTE_HOLD,
|
||||
's': Note.NOTE_STAR,
|
||||
'e': Note.NOTE_SLIDE,
|
||||
'b': Note.NOTE_TAP, # Break
|
||||
'x': Note.NOTE_STAR # Break star
|
||||
}
|
||||
|
||||
const SLIDE_TYPES = {
|
||||
'0': null, # Seems to be used for stars without slides attached
|
||||
'1': Note.SlideType.CHORD,
|
||||
'2': Note.SlideType.ARC_ACW,
|
||||
'3': Note.SlideType.ARC_CW,
|
||||
'4': Note.SlideType.COMPLEX, # Orbit around center ACW on the way
|
||||
'5': Note.SlideType.COMPLEX, # CW of above
|
||||
'6': Note.SlideType.COMPLEX, # S zigzag through center
|
||||
'7': Note.SlideType.COMPLEX, # Z zigzag through center
|
||||
'8': Note.SlideType.COMPLEX, # V into center
|
||||
'9': Note.SlideType.COMPLEX, # Go to center then orbit off to the side ACW
|
||||
'a': Note.SlideType.COMPLEX, # CW of above
|
||||
'b': Note.SlideType.COMPLEX, # V into column 2 places ACW
|
||||
'c': Note.SlideType.COMPLEX, # V into column 2 places CW
|
||||
'd': Note.SlideType.CHORD_TRIPLE, # Triple cone. Spreads out to the adjacent receptors of the target.
|
||||
'e': Note.SlideType.CHORD, # Not used in any of our charts
|
||||
'f': Note.SlideType.CHORD, # Not used in any of our charts
|
||||
}
|
||||
const SLIDE_IN_R := sin(PI/8) # Circle radius circumscribed by chords 0-3, 1-4, 2-5 etc.
|
||||
|
||||
|
||||
static func load_file(filename: String):
|
||||
var extension = filename.rsplit('.', false, 1)[1]
|
||||
if not EXTENSIONS.has(extension):
|
||||
return -1
|
||||
var format = EXTENSIONS[extension]
|
||||
var file := File.new()
|
||||
var err := file.open(filename, File.READ)
|
||||
if err != OK:
|
||||
print(err)
|
||||
return err
|
||||
var length = file.get_len()
|
||||
var chart_ids = []
|
||||
var lines = [[]]
|
||||
# This loop will segment the lines as if the file were RGTM
|
||||
while (file.get_position() < (length-1)): # Could probably replace this with file.eof_reached()
|
||||
var line : String = file.get_line()
|
||||
if line.begins_with('['): # Split to a new list for each chart definition
|
||||
chart_ids.append(line.lstrip('[').rstrip(']'))
|
||||
lines.append([])
|
||||
elif !line.empty():
|
||||
lines[-1].push_back(line)
|
||||
file.close()
|
||||
print('Parsing chart: ', filename)
|
||||
|
||||
match format:
|
||||
Format.RGTS:
|
||||
var metadata_and_notes = parse_rgts(lines[0])
|
||||
return metadata_and_notes
|
||||
Format.RGTX:
|
||||
var metadata_and_notes = parse_rgtx(lines[0])
|
||||
return metadata_and_notes
|
||||
Format.RGTM:
|
||||
lines.pop_front() # Anything before the first [header] is meaningless
|
||||
var charts = {}
|
||||
for i in len(lines):
|
||||
charts[chart_ids[i]] = parse_rgts(lines[i])
|
||||
return charts
|
||||
return format
|
||||
|
||||
|
||||
static func parse_rgtx(lines: PoolStringArray):
|
||||
return [] # To be implemented later
|
||||
|
||||
|
||||
const beats_per_measure = 4.0 # TODO: Bit of an ugly hack, need to revisit this later
|
||||
static func parse_rgts(lines: PoolStringArray):
|
||||
var metadata := {}
|
||||
var num_taps := 0
|
||||
var num_holds := 0
|
||||
var num_slides := 0
|
||||
var notes := []
|
||||
var slide_ids := {}
|
||||
var slide_stars := {} # Multiple stars might link to one star. We only care about linking for the spin speed.
|
||||
var last_star := []
|
||||
for i in Rules.COLS:
|
||||
last_star.append(null)
|
||||
|
||||
for line in lines:
|
||||
if len(line) < 4: # shortest legal line would be like '1:1t'
|
||||
continue
|
||||
var s = line.split(':')
|
||||
var time := float(s[0]) * beats_per_measure
|
||||
var note_hits := []
|
||||
var note_nonhits := []
|
||||
for i in range(1, len(s)):
|
||||
var n = s[i]
|
||||
var column := int(n[0])
|
||||
var ntype = n[1]
|
||||
n = n.substr(2)
|
||||
|
||||
match ntype:
|
||||
't', 'b': # tap
|
||||
note_hits.append(Note.NoteTap.new(time, column, ntype=='b'))
|
||||
num_taps += 1
|
||||
'h': # hold
|
||||
var duration = float(n) * beats_per_measure
|
||||
note_hits.append(Note.NoteHold.new(time, column, duration))
|
||||
num_holds += 1
|
||||
's', 'x': # slide star
|
||||
var star = Note.NoteStar.new(time, column, ntype=='z')
|
||||
note_hits.append(star)
|
||||
num_slides += 1
|
||||
last_star[column] = star
|
||||
if len(n) > 1: # Not all stars have proper slide info
|
||||
var slide_type = n[0] # hex digit
|
||||
var slide_id = int(n.substr(1))
|
||||
if slide_id > 0:
|
||||
slide_stars[slide_id] = star
|
||||
var slide = Note.NoteSlide.new(time, column)
|
||||
slide_ids[slide_id] = slide
|
||||
note_nonhits.append(slide)
|
||||
'e': # slide end
|
||||
var slide_type = n[0] # numeric digit, left as str just in case
|
||||
var slide_id = int(n.substr(1))
|
||||
if slide_id in slide_ids: # Classic slide end
|
||||
slide_ids[slide_id].time_release = time
|
||||
if slide_id in slide_stars:
|
||||
slide_stars[slide_id].duration = slide_ids[slide_id].duration # Should probably recalc in case start time is different but w/e
|
||||
slide_ids[slide_id].column_release = column
|
||||
slide_ids[slide_id].slide_type = SLIDE_TYPES[slide_type]
|
||||
slide_ids[slide_id].update_slide_variables()
|
||||
if SLIDE_TYPES[slide_type] == Note.SlideType.COMPLEX:
|
||||
var col_hit = slide_ids[slide_id].column
|
||||
var RUV = GameTheme.RADIAL_UNIT_VECTORS
|
||||
var RCA = GameTheme.RADIAL_COL_ANGLES
|
||||
slide_ids[slide_id].values.curve2d.add_point(RUV[col_hit]) # Start col
|
||||
match slide_type:
|
||||
'4': # Orbit ACW around center. Size of loop is roughly inscribed in chords of 0-3, 1-4, 2-5... NB: doesn't loop if directly opposite col
|
||||
Note.curve2d_make_orbit(slide_ids[slide_id].values.curve2d, RCA[col_hit], RCA[column], true)
|
||||
'5': # CW of above
|
||||
Note.curve2d_make_orbit(slide_ids[slide_id].values.curve2d, RCA[col_hit], RCA[column], false)
|
||||
'6': # S zigzag through center
|
||||
slide_ids[slide_id].values.curve2d.add_point(RUV[posmod(col_hit-2, Rules.COLS)] * SLIDE_IN_R)
|
||||
slide_ids[slide_id].values.curve2d.add_point(RUV[posmod(col_hit+2, Rules.COLS)] * SLIDE_IN_R)
|
||||
'7': # Z zigzag through center
|
||||
slide_ids[slide_id].values.curve2d.add_point(RUV[posmod(col_hit+2, Rules.COLS)] * SLIDE_IN_R)
|
||||
slide_ids[slide_id].values.curve2d.add_point(RUV[posmod(col_hit-2, Rules.COLS)] * SLIDE_IN_R)
|
||||
'8': # V into center
|
||||
slide_ids[slide_id].values.curve2d.add_point(Vector2.ZERO)
|
||||
'9': # Orbit off-center ACW
|
||||
Note.curve2d_make_sideorbit(slide_ids[slide_id].values.curve2d, RCA[col_hit], RCA[column], true)
|
||||
'a': # CW of above
|
||||
Note.curve2d_make_sideorbit(slide_ids[slide_id].values.curve2d, RCA[col_hit], RCA[column], false)
|
||||
'b': # V into column 2 places ACW
|
||||
slide_ids[slide_id].values.curve2d.add_point(RUV[posmod(col_hit-2, Rules.COLS)])
|
||||
'c': # V into column 2 places CW
|
||||
slide_ids[slide_id].values.curve2d.add_point(RUV[posmod(col_hit+2, Rules.COLS)])
|
||||
slide_ids[slide_id].values.curve2d.add_point(RUV[column]) # End col
|
||||
else: # Naked slide start
|
||||
if last_star[column] != null:
|
||||
slide_stars[slide_id] = last_star[column]
|
||||
else:
|
||||
print_debug('Naked slide with no prior star in column!')
|
||||
var note = Note.NoteSlide.new(time, column)
|
||||
slide_ids[slide_id] = note
|
||||
note_nonhits.append(note)
|
||||
'_':
|
||||
print_debug('Unknown note type: ', ntype)
|
||||
|
||||
if len(note_hits) > 1:
|
||||
for note in note_hits: # Set multihit on each one
|
||||
note.double_hit = true
|
||||
notes += note_hits + note_nonhits
|
||||
metadata['num_taps'] = num_taps
|
||||
metadata['num_holds'] = num_holds
|
||||
metadata['num_slides'] = num_slides
|
||||
return [metadata, notes]
|
|
@ -0,0 +1,167 @@
|
|||
extends Node
|
||||
# Stepmania simfile
|
||||
|
||||
|
||||
const NOTE_VALUES = {
|
||||
'0': 'None',
|
||||
'1': 'Tap',
|
||||
'2': 'HoldStart',
|
||||
'3': 'HoldRollEnd',
|
||||
'4': 'RollStart',
|
||||
'M': 'Mine',
|
||||
# These three are less likely to show up anywhere, no need to implement
|
||||
'K': 'Keysound',
|
||||
'L': 'Lift',
|
||||
'F': 'Fake',
|
||||
}
|
||||
|
||||
|
||||
const CHART_DIFFICULTIES = {
|
||||
'Beginner': 0,
|
||||
'Easy': 1,
|
||||
'Medium': 2,
|
||||
'Hard': 3,
|
||||
'Challenge': 4,
|
||||
'Edit': 5,
|
||||
# Some will just write whatever for special difficulties, but we should at least color-code these standard ones
|
||||
}
|
||||
|
||||
|
||||
const TAG_TRANSLATIONS = {
|
||||
'#TITLE': 'title',
|
||||
'#SUBTITLE': 'subtitle',
|
||||
'#ARTIST': 'artist',
|
||||
'#TITLETRANSLIT': 'title_transliteration',
|
||||
'#SUBTITLETRANSLIT': 'subtitle_transliteration',
|
||||
'#ARTISTTRANSLIT': 'artist_transliteration',
|
||||
'#GENRE': 'genre',
|
||||
'#CREDIT': 'chart_author',
|
||||
'#BANNER': 'image_banner',
|
||||
'#BACKGROUND': 'image_background',
|
||||
# '#LYRICSPATH': '',
|
||||
'#CDTITLE': 'image_cd_title',
|
||||
'#MUSIC': 'audio_filelist',
|
||||
'#OFFSET': 'audio_offsets',
|
||||
'#SAMPLESTART': 'audio_preview_times',
|
||||
'#SAMPLELENGTH': 'audio_preview_times',
|
||||
# '#SELECTABLE': '',
|
||||
'#BPMS': 'bpm_values',
|
||||
# '#STOPS': '',
|
||||
# '#BGCHANGES': '',
|
||||
# '#KEYSOUNDS': '',
|
||||
}
|
||||
|
||||
|
||||
static func load_chart(lines):
|
||||
var metadata = {}
|
||||
var notes = []
|
||||
|
||||
assert(lines[0].begins_with('#NOTES:'))
|
||||
metadata['chart_type'] = lines[1].strip_edges().rstrip(':')
|
||||
metadata['description'] = lines[2].strip_edges().rstrip(':')
|
||||
metadata['difficulty_str'] = lines[3].strip_edges().rstrip(':')
|
||||
metadata['numerical_meter'] = lines[4].strip_edges().rstrip(':')
|
||||
metadata['groove_radar'] = lines[5].strip_edges().rstrip(':')
|
||||
|
||||
# Measures are separated by lines that start with a comma
|
||||
# Each line has a state for each of the pads, e.g. '0000' for none pressed
|
||||
# The lines become even subdivisions of the measure, so if there's 4 lines everything represents a 1/4 beat, if there's 8 lines everything represents a 1/8 beat etc.
|
||||
# For this reason it's probably best to just have a float for beat-within-measure rather than integer beats.
|
||||
var measures = [[]]
|
||||
for i in range(6, len(lines)):
|
||||
var line = lines[i].strip_edges()
|
||||
if line.begins_with(','):
|
||||
measures.append([])
|
||||
elif line.begins_with(';'):
|
||||
break
|
||||
elif len(line) > 0:
|
||||
measures[-1].append(line)
|
||||
|
||||
var ongoing_holds = {}
|
||||
var num_notes := 0
|
||||
var num_jumps := 0
|
||||
var num_hands := 0
|
||||
var num_holds := 0
|
||||
var num_rolls := 0
|
||||
var num_mines := 0
|
||||
|
||||
for measure in range(len(measures)):
|
||||
var m_lines = measures[measure]
|
||||
var m_length = len(m_lines) # Divide out all lines by this
|
||||
for beat in m_length:
|
||||
var line : String = m_lines[beat]
|
||||
# Jump check at a line-level (check for multiple 1/2/4s)
|
||||
var hits : int = line.count('1') + line.count('2') + line.count('4')
|
||||
# Hand/quad check more complex as need to check hold/roll state as well
|
||||
# TODO: are they exclusive? Does quad override hand override jump? SM5 doesn't have quads and has hands+jumps inclusive
|
||||
var total_pressed : int = hits + len(ongoing_holds)
|
||||
var jump : bool = hits >= 2
|
||||
var hand : bool = total_pressed >= 3
|
||||
# var quad : bool = total_pressed >= 4
|
||||
num_notes += hits
|
||||
num_jumps += int(jump)
|
||||
num_hands += int(hand)
|
||||
var time = measure + beat/float(m_length)
|
||||
for col in len(line):
|
||||
match line[col]:
|
||||
'1':
|
||||
notes.append(Note.NoteTap.new(time, col))
|
||||
'2': # Hold
|
||||
ongoing_holds[col] = len(notes)
|
||||
notes.append(Note.NoteHold.new(time, col, 0.0))
|
||||
num_holds += 1
|
||||
'4': # Roll
|
||||
ongoing_holds[col] = len(notes)
|
||||
notes.append(Note.NoteRoll.new(time, col, 0.0))
|
||||
num_rolls += 1
|
||||
'3': # End Hold/Roll
|
||||
assert(ongoing_holds.has(col))
|
||||
notes[ongoing_holds[col]].set_time_release(time)
|
||||
ongoing_holds.erase(col)
|
||||
'M': # Mine
|
||||
num_mines += 1
|
||||
pass
|
||||
metadata['num_notes'] = num_notes
|
||||
metadata['num_taps'] = num_notes - num_jumps
|
||||
metadata['num_jumps'] = num_jumps
|
||||
metadata['num_hands'] = num_hands
|
||||
metadata['num_holds'] = num_holds
|
||||
metadata['num_rolls'] = num_rolls
|
||||
metadata['num_mines'] = num_mines
|
||||
metadata['notes'] = notes
|
||||
return metadata
|
||||
|
||||
|
||||
static func load_file(filename: String) -> Array:
|
||||
# Output is [metadata, [[meta0, chart0], ..., [metaN, chartN]]]
|
||||
# Technically, declarations end with a semicolon instead of a linebreak.
|
||||
# This is a PITA to do correctly in GDScript and the files in our collection are well-behaved with linebreaks anyway, so we won't bother.
|
||||
var file := File.new()
|
||||
match file.open(filename, File.READ):
|
||||
OK:
|
||||
pass
|
||||
var err:
|
||||
print_debug('Error loading file: ', err)
|
||||
return []
|
||||
var length = file.get_len()
|
||||
var lines = [[]] # First list will be header, then every subsequent one is a chart
|
||||
while (file.get_position() < (length-1)): # Could probably replace this with file.eof_reached()
|
||||
var line : String = file.get_line()
|
||||
if line.begins_with('#NOTES'): # Split to a new list for each chart definition
|
||||
lines.append([])
|
||||
lines[-1].append(line)
|
||||
file.close()
|
||||
|
||||
var metadata = {}
|
||||
for line in lines[0]:
|
||||
var tokens = line.rstrip(';').split(':')
|
||||
if TAG_TRANSLATIONS.has(tokens[0]):
|
||||
metadata[TAG_TRANSLATIONS[tokens[0]]] = tokens[1]
|
||||
elif len(tokens) >= 2:
|
||||
metadata[tokens[0]] = tokens[1]
|
||||
var charts = []
|
||||
|
||||
for i in range(1, len(lines)):
|
||||
charts.append(load_chart(lines[i]))
|
||||
|
||||
return [metadata, charts]
|
|
@ -0,0 +1,73 @@
|
|||
extends Node
|
||||
# A legacy format that is relatively easily parsed. Radial game mode.
|
||||
const TAP_DURATION := 0.062500
|
||||
const ID_BREAK := 4
|
||||
const ID_HOLD := 2
|
||||
const ID_SLIDE_END := 128
|
||||
const ID3_SLIDE_CHORD := 0 # Straight line
|
||||
const ID3_SLIDE_ARC_CW := 1
|
||||
const ID3_SLIDE_ARC_ACW := 2
|
||||
|
||||
static func load_file(filename):
|
||||
var file = File.new()
|
||||
var err = file.open(filename, File.READ)
|
||||
if err != OK:
|
||||
print(err)
|
||||
return err
|
||||
var metadata := {}
|
||||
var num_taps := 0
|
||||
var num_holds := 0
|
||||
var num_slides := 0
|
||||
var notes := []
|
||||
var beats_per_measure := 4
|
||||
var length = file.get_len()
|
||||
var slide_ids = {}
|
||||
while (file.get_position() < (length-2)):
|
||||
var noteline = file.get_csv_line()
|
||||
var time_hit := (float(noteline[0]) + (float(noteline[1]))) * beats_per_measure
|
||||
var duration := float(noteline[2]) * beats_per_measure
|
||||
var column := int(noteline[3])
|
||||
var id := int(noteline[4])
|
||||
var id2 := int(noteline[5])
|
||||
var id3 := int(noteline[6])
|
||||
|
||||
match id:
|
||||
ID_HOLD:
|
||||
notes.push_back(Note.NoteHold.new(time_hit, column, duration))
|
||||
num_holds += 1
|
||||
ID_BREAK:
|
||||
notes.push_back(Note.NoteTap.new(time_hit, column, true))
|
||||
num_taps += 1
|
||||
ID_SLIDE_END:
|
||||
# id2 is slide ID
|
||||
if id2 in slide_ids:
|
||||
slide_ids[id2].column_release = column
|
||||
slide_ids[id2].update_slide_variables()
|
||||
_:
|
||||
if id2 == 0:
|
||||
notes.push_back(Note.NoteTap.new(time_hit, column))
|
||||
num_taps += 1
|
||||
else:
|
||||
# id2 is slide ID, id3 is slide pattern
|
||||
# In order to properly declare the slide, we need the paired endcap which may not be the next note
|
||||
var slide_type = Note.SlideType.CHORD
|
||||
match id3:
|
||||
ID3_SLIDE_CHORD:
|
||||
slide_type = Note.SlideType.CHORD
|
||||
ID3_SLIDE_ARC_CW:
|
||||
slide_type = Note.SlideType.ARC_CW
|
||||
ID3_SLIDE_ARC_ACW:
|
||||
slide_type = Note.SlideType.ARC_ACW
|
||||
_:
|
||||
print('Unknown slide type: ', id3)
|
||||
var note = Note.NoteStar.new(time_hit, column)
|
||||
num_slides += 1
|
||||
note.duration = duration
|
||||
notes.push_back(note)
|
||||
var slide = Note.NoteSlide.new(time_hit, column, duration, -1, slide_type)
|
||||
notes.push_back(slide)
|
||||
slide_ids[id2] = slide
|
||||
metadata['num_taps'] = num_taps
|
||||
metadata['num_holds'] = num_holds
|
||||
metadata['num_slides'] = num_slides
|
||||
return [metadata, notes]
|
|
@ -0,0 +1,34 @@
|
|||
extends Node
|
||||
# In case things need to be tested without a library
|
||||
|
||||
static func stress_pattern():
|
||||
var notes = []
|
||||
for bar in range(8):
|
||||
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 1))
|
||||
for i in range(1, 8):
|
||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/2.0), (bar + i)%8))
|
||||
notes.push_back(Note.NoteTap.new(bar*4 + (7/2.0), (bar + 3)%8))
|
||||
for bar in range(8, 16):
|
||||
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 2))
|
||||
for i in range(1, 8):
|
||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/2.0), (bar + i)%8))
|
||||
notes.push_back(Note.NoteTap.new(bar*4 + ((i+0.5)/2.0), (bar + i)%8))
|
||||
notes.push_back(Note.make_slide(bar*4 + ((i+1)/2.0), 1, (bar + i)%8, 0))
|
||||
for bar in range(16, 24):
|
||||
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 2))
|
||||
notes.push_back(Note.NoteHold.new(bar*4, (bar+1)%8, 1))
|
||||
for i in range(2, 8):
|
||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/2.0), (bar + i)%8))
|
||||
notes.push_back(Note.NoteHold.new(bar*4 + ((i+1)/2.0), (bar + i)%8, 0.5))
|
||||
for bar in range(24, 32):
|
||||
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 1))
|
||||
for i in range(1, 32):
|
||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/8.0), (bar + i)%8))
|
||||
if (i%2) > 0:
|
||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/8.0), (bar + i + 4)%8))
|
||||
for bar in range(32, 48):
|
||||
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 1))
|
||||
for i in range(1, 32):
|
||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/8.0), (bar + i)%8))
|
||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/8.0), (bar + i + 3)%8))
|
||||
return notes
|
|
@ -0,0 +1,40 @@
|
|||
extends Control
|
||||
onready var mainMenu := $'%mainMenu'
|
||||
onready var optionPanel := $'%OptionPanel'
|
||||
|
||||
const touchGamePath := 'res://scenes/RadialGame.tscn'
|
||||
const stepGamePath := 'res://scenes/StepGame.tscn'
|
||||
const touchGameScene := preload(touchGamePath)
|
||||
const stepGameScene := preload(stepGamePath)
|
||||
const SettingsMenu := preload('res://scenes/SettingsMenu.tscn')
|
||||
|
||||
var activeGame: Node = null
|
||||
|
||||
func exit_mode() -> void:
|
||||
remove_child(activeGame)
|
||||
activeGame = null
|
||||
mainMenu.show()
|
||||
optionPanel.show()
|
||||
|
||||
|
||||
func _on_MainMenu_start_stepgame() -> void:
|
||||
mainMenu.hide()
|
||||
activeGame = stepGameScene.instance()
|
||||
activeGame.connect('exit_mode', self, 'exit_mode')
|
||||
add_child_below_node(mainMenu, activeGame)
|
||||
|
||||
|
||||
func _on_MainMenu_start_touchgame() -> void:
|
||||
mainMenu.hide()
|
||||
activeGame = touchGameScene.instance()
|
||||
activeGame.connect('exit_mode', self, 'exit_mode')
|
||||
add_child_below_node(mainMenu, activeGame)
|
||||
activeGame.alignment_horizontal = AspectRatioContainer.ALIGN_BEGIN
|
||||
|
||||
|
||||
func _on_mainMenu_open_settings():
|
||||
mainMenu.hide()
|
||||
optionPanel.hide()
|
||||
activeGame = SettingsMenu.instance()
|
||||
activeGame.connect('exit_mode', self, 'exit_mode')
|
||||
add_child_below_node(mainMenu, activeGame)
|
19
main.tscn
19
main.tscn
|
@ -1,24 +1,25 @@
|
|||
[gd_scene load_steps=5 format=2]
|
||||
[gd_scene load_steps=6 format=2]
|
||||
|
||||
[ext_resource path="res://RadialGame.tscn" type="PackedScene" id=1]
|
||||
[ext_resource path="res://scenes/MainMenu.tscn" type="PackedScene" id=1]
|
||||
[ext_resource path="res://scripts/TouchInput.gd" type="Script" id=2]
|
||||
[ext_resource path="res://default.theme" type="Theme" id=3]
|
||||
[ext_resource path="res://OptionPanel.tscn" type="PackedScene" id=13]
|
||||
[ext_resource path="res://main.gd" type="Script" id=4]
|
||||
[ext_resource path="res://scenes/OptionPanel.tscn" type="PackedScene" id=13]
|
||||
|
||||
[node name="main" type="Control"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
theme = ExtResource( 3 )
|
||||
script = ExtResource( 4 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="RadialGame" parent="." instance=ExtResource( 1 )]
|
||||
anchor_right = 0.0
|
||||
margin_right = 600.0
|
||||
rect_min_size = Vector2( 1080, 0 )
|
||||
[node name="mainMenu" parent="." instance=ExtResource( 1 )]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="OptionPanel" parent="." instance=ExtResource( 13 )]
|
||||
unique_name_in_owner = true
|
||||
anchor_left = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
@ -35,3 +36,7 @@ script = ExtResource( 2 )
|
|||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[connection signal="open_settings" from="mainMenu" to="." method="_on_mainMenu_open_settings"]
|
||||
[connection signal="start_stepgame" from="mainMenu" to="." method="_on_MainMenu_start_stepgame"]
|
||||
[connection signal="start_touchgame" from="mainMenu" to="." method="_on_MainMenu_start_touchgame"]
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
[gd_resource type="Theme" load_steps=7 format=2]
|
||||
|
||||
[ext_resource path="res://assets/fonts/Sniglet-Regular.ttf" type="DynamicFontData" id=1]
|
||||
|
||||
[sub_resource type="DynamicFont" id=1]
|
||||
size = 48
|
||||
outline_size = 4
|
||||
outline_color = Color( 0, 0, 0, 1 )
|
||||
font_data = ExtResource( 1 )
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id=3]
|
||||
content_margin_left = 24.0
|
||||
content_margin_right = 24.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color( 0.184314, 0.180392, 0.192157, 1 )
|
||||
border_width_left = 12
|
||||
border_width_top = 4
|
||||
border_width_right = 12
|
||||
border_width_bottom = 4
|
||||
border_color = Color( 0.172549, 0.164706, 0.196078, 1 )
|
||||
corner_radius_top_left = 16
|
||||
corner_radius_top_right = 16
|
||||
corner_radius_bottom_right = 16
|
||||
corner_radius_bottom_left = 16
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id=4]
|
||||
content_margin_left = 24.0
|
||||
content_margin_right = 24.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color( 0.517647, 0.482353, 0.647059, 1 )
|
||||
border_width_left = 12
|
||||
border_width_top = 4
|
||||
border_width_right = 12
|
||||
border_width_bottom = 4
|
||||
border_color = Color( 0.172549, 0.164706, 0.196078, 1 )
|
||||
corner_radius_top_left = 16
|
||||
corner_radius_top_right = 16
|
||||
corner_radius_bottom_right = 16
|
||||
corner_radius_bottom_left = 16
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id=2]
|
||||
content_margin_left = 24.0
|
||||
content_margin_right = 24.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color( 0.235294, 0.227451, 0.266667, 1 )
|
||||
border_width_left = 12
|
||||
border_width_top = 4
|
||||
border_width_right = 12
|
||||
border_width_bottom = 4
|
||||
border_color = Color( 0.172549, 0.164706, 0.196078, 1 )
|
||||
corner_radius_top_left = 16
|
||||
corner_radius_top_right = 16
|
||||
corner_radius_bottom_right = 16
|
||||
corner_radius_bottom_left = 16
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id=5]
|
||||
content_margin_left = 24.0
|
||||
content_margin_right = 24.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color( 0.376471, 0.356863, 0.443137, 1 )
|
||||
border_width_left = 12
|
||||
border_width_top = 4
|
||||
border_width_right = 12
|
||||
border_width_bottom = 4
|
||||
border_color = Color( 0.709804, 0.709804, 1, 1 )
|
||||
border_blend = true
|
||||
corner_radius_top_left = 16
|
||||
corner_radius_top_right = 16
|
||||
corner_radius_bottom_right = 16
|
||||
corner_radius_bottom_left = 16
|
||||
shadow_color = Color( 0.423529, 0.670588, 1, 0.372549 )
|
||||
shadow_size = 6
|
||||
|
||||
[resource]
|
||||
default_font = SubResource( 1 )
|
||||
Button/colors/font_color = Color( 0.88, 0.88, 0.88, 1 )
|
||||
Button/colors/font_color_disabled = Color( 0.9, 0.9, 0.9, 0.2 )
|
||||
Button/colors/font_color_hover = Color( 0.827451, 0.788235, 1, 1 )
|
||||
Button/colors/font_color_pressed = Color( 1, 1, 1, 1 )
|
||||
Button/constants/hseparation = 2
|
||||
Button/fonts/font = SubResource( 1 )
|
||||
Button/styles/disabled = SubResource( 3 )
|
||||
Button/styles/focus = SubResource( 4 )
|
||||
Button/styles/hover = SubResource( 2 )
|
||||
Button/styles/normal = SubResource( 2 )
|
||||
Button/styles/pressed = SubResource( 5 )
|
|
@ -35,12 +35,10 @@ Video="*res://singletons/Video.gd"
|
|||
|
||||
[debug]
|
||||
|
||||
settings/fps/force_fps=120
|
||||
gdscript/warnings/unused_variable=false
|
||||
gdscript/warnings/shadowed_variable=false
|
||||
gdscript/warnings/unused_argument=false
|
||||
gdscript/warnings/unused_signal=false
|
||||
gdscript/warnings/return_value_discarded=false
|
||||
gdscript/warnings/integer_division=false
|
||||
|
||||
[display]
|
||||
|
@ -48,6 +46,7 @@ gdscript/warnings/integer_division=false
|
|||
window/size/width=1080
|
||||
window/size/height=1080
|
||||
window/size/fullscreen=true
|
||||
window/dpi/allow_hidpi=true
|
||||
window/handheld/orientation="sensor"
|
||||
window/stretch/mode="2d"
|
||||
window/stretch/aspect="expand"
|
||||
|
@ -60,7 +59,7 @@ singletons_disabled=[ ]
|
|||
[rendering]
|
||||
|
||||
vram_compression/import_etc=true
|
||||
environment/default_clear_color=Color( 0.11, 0.11, 0.11, 1 )
|
||||
environment/default_clear_color=Color( 0.109804, 0.109804, 0.109804, 1 )
|
||||
quality/filters/msaa=1
|
||||
environment/default_environment="res://default_env.tres"
|
||||
quality/subsampling/x=1.0
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
extends HBoxContainer
|
||||
onready var le_directory := $le_directory
|
||||
onready var btn_browse := $btn_browse
|
||||
onready var btn_remove := $btn_remove
|
||||
|
||||
signal path_updated
|
||||
|
||||
export var directory: String = "" setget set_directory, get_directory
|
||||
func set_directory(value: String):
|
||||
directory = value
|
||||
if le_directory:
|
||||
le_directory.text = directory
|
||||
emit_signal('path_updated', value)
|
||||
|
||||
func get_directory():
|
||||
return le_directory.text
|
||||
|
||||
export var removable: bool = true setget set_removable
|
||||
func set_removable(value: bool):
|
||||
removable = value
|
||||
if btn_remove:
|
||||
btn_remove.disabled = not removable
|
||||
|
||||
export var readonly: bool = false setget set_readonly
|
||||
func set_readonly(value: bool):
|
||||
readonly = value
|
||||
if le_directory:
|
||||
le_directory.editable = not readonly
|
||||
if btn_browse:
|
||||
btn_browse.disabled = readonly
|
||||
|
||||
func _ready():
|
||||
le_directory.text = directory
|
||||
le_directory.editable = not readonly
|
||||
btn_browse.disabled = readonly
|
||||
btn_remove.disabled = not removable
|
||||
|
||||
#func _process(delta):
|
||||
# pass
|
||||
|
||||
|
||||
func _on_btn_browse_pressed():
|
||||
var dialog = FileDialog.new()
|
||||
dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
dialog.mode = FileDialog.MODE_OPEN_DIR
|
||||
dialog.connect("dir_selected", self, "set_directory")
|
||||
add_child(dialog)
|
||||
dialog.popup(Rect2(0, 0, 800, 600))
|
||||
|
||||
|
||||
func _on_btn_remove_pressed():
|
||||
set_directory("")
|
|
@ -0,0 +1,30 @@
|
|||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://scenes/DirectorySettingsEntry.gd" type="Script" id=1]
|
||||
|
||||
[node name="DirectorySettingsEntry" type="HBoxContainer"]
|
||||
margin_top = 18.0
|
||||
margin_right = 1080.0
|
||||
margin_bottom = 42.0
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="le_directory" type="LineEdit" parent="."]
|
||||
margin_right = 937.0
|
||||
margin_bottom = 24.0
|
||||
rect_min_size = Vector2( 480, 1.36422e-12 )
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="btn_browse" type="Button" parent="."]
|
||||
margin_left = 941.0
|
||||
margin_right = 1012.0
|
||||
margin_bottom = 24.0
|
||||
text = "Browse..."
|
||||
|
||||
[node name="btn_remove" type="Button" parent="."]
|
||||
margin_left = 1016.0
|
||||
margin_right = 1080.0
|
||||
margin_bottom = 24.0
|
||||
text = "Remove"
|
||||
|
||||
[connection signal="pressed" from="btn_browse" to="." method="_on_btn_browse_pressed"]
|
||||
[connection signal="pressed" from="btn_remove" to="." method="_on_btn_remove_pressed"]
|
|
@ -1,6 +1,6 @@
|
|||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://FontTesting.gd" type="Script" id=1]
|
||||
[ext_resource path="res://scripts/FontTesting.gd" type="Script" id=1]
|
||||
|
||||
[node name="Control" type="Control"]
|
||||
anchor_right = 1.0
|
|
@ -0,0 +1,43 @@
|
|||
extends VBoxContainer
|
||||
|
||||
export var joypad_index = 0
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
pass # Replace with function body.
|
||||
|
||||
const color_off = Color.blue
|
||||
const color_on = Color.white
|
||||
|
||||
var id_left = 0
|
||||
var id_down = 1
|
||||
var id_up = 2
|
||||
var id_right = 3
|
||||
var id_start = 4
|
||||
var id_back = 5
|
||||
|
||||
func set_name(name):
|
||||
$Label.text = name
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(delta: float) -> void:
|
||||
var left = Input.get_joy_axis(joypad_index, id_left)
|
||||
var down = Input.get_joy_axis(joypad_index, id_down)
|
||||
var up = Input.get_joy_axis(joypad_index, id_up)
|
||||
var right = Input.get_joy_axis(joypad_index, id_right)
|
||||
var start = Input.get_joy_axis(joypad_index, id_start)
|
||||
var back = Input.get_joy_axis(joypad_index, id_back)
|
||||
var precision = "%.05f"
|
||||
$Grid/left/Label.text = precision % left
|
||||
$Grid/down/Label.text = precision % down
|
||||
$Grid/up/Label.text = precision % up
|
||||
$Grid/right/Label.text = precision % right
|
||||
$TopRow/lbl_start.text = "start\n" + precision % start
|
||||
$TopRow/lbl_back.text = "back\n" + precision % back
|
||||
|
||||
$Grid/left/t.modulate = color_on if Input.is_joy_button_pressed(joypad_index, id_left) else color_off
|
||||
$Grid/down/t.modulate = color_on if Input.is_joy_button_pressed(joypad_index, id_down) else color_off
|
||||
$Grid/up/t.modulate = color_on if Input.is_joy_button_pressed(joypad_index, id_up) else color_off
|
||||
$Grid/right/t.modulate = color_on if Input.is_joy_button_pressed(joypad_index, id_right) else color_off
|
||||
$TopRow/lbl_start.uppercase = Input.is_joy_button_pressed(joypad_index, id_start)
|
||||
$TopRow/lbl_back.uppercase = Input.is_joy_button_pressed(joypad_index, id_back)
|
|
@ -0,0 +1,157 @@
|
|||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://assets/step_arrow_2048_atlas_texture.tres" type="Texture" id=1]
|
||||
[ext_resource path="res://scenes/JoypadMonitor6Btn.gd" type="Script" id=2]
|
||||
|
||||
[node name="JoypadMonitor" type="VBoxContainer"]
|
||||
margin_right = 160.0
|
||||
margin_bottom = 200.0
|
||||
script = ExtResource( 2 )
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
margin_right = 200.0
|
||||
margin_bottom = 14.0
|
||||
text = "Joypad Name"
|
||||
align = 1
|
||||
|
||||
[node name="TopRow" type="Control" parent="."]
|
||||
margin_top = 18.0
|
||||
margin_right = 200.0
|
||||
margin_bottom = 34.0
|
||||
rect_min_size = Vector2( 160, 16 )
|
||||
|
||||
[node name="lbl_back" type="Label" parent="TopRow"]
|
||||
margin_right = 40.0
|
||||
margin_bottom = 14.0
|
||||
text = "BACK"
|
||||
|
||||
[node name="lbl_start" type="Label" parent="TopRow"]
|
||||
anchor_right = 1.0
|
||||
margin_bottom = 14.0
|
||||
text = "START"
|
||||
align = 2
|
||||
|
||||
[node name="Grid" type="GridContainer" parent="."]
|
||||
margin_top = 38.0
|
||||
margin_right = 200.0
|
||||
margin_bottom = 238.0
|
||||
rect_min_size = Vector2( 192, 192 )
|
||||
columns = 3
|
||||
|
||||
[node name="up_left" type="Control" parent="Grid"]
|
||||
margin_right = 64.0
|
||||
margin_bottom = 64.0
|
||||
rect_min_size = Vector2( 64, 64 )
|
||||
|
||||
[node name="up" type="Control" parent="Grid"]
|
||||
margin_left = 68.0
|
||||
margin_right = 132.0
|
||||
margin_bottom = 64.0
|
||||
rect_min_size = Vector2( 64, 64 )
|
||||
|
||||
[node name="t" type="TextureRect" parent="Grid/up"]
|
||||
margin_right = 512.0
|
||||
margin_bottom = 512.0
|
||||
rect_scale = Vector2( 0.125, 0.125 )
|
||||
texture = ExtResource( 1 )
|
||||
stretch_mode = 1
|
||||
|
||||
[node name="Label" type="Label" parent="Grid/up"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
text = "0.00"
|
||||
align = 1
|
||||
valign = 1
|
||||
|
||||
[node name="up_right" type="Control" parent="Grid"]
|
||||
margin_left = 136.0
|
||||
margin_right = 200.0
|
||||
margin_bottom = 64.0
|
||||
rect_min_size = Vector2( 64, 64 )
|
||||
|
||||
[node name="left" type="Control" parent="Grid"]
|
||||
margin_top = 68.0
|
||||
margin_right = 64.0
|
||||
margin_bottom = 132.0
|
||||
rect_min_size = Vector2( 64, 64 )
|
||||
|
||||
[node name="t" type="TextureRect" parent="Grid/left"]
|
||||
margin_top = 64.0
|
||||
margin_right = 512.0
|
||||
margin_bottom = 576.0
|
||||
rect_rotation = -90.0
|
||||
rect_scale = Vector2( 0.125, 0.125 )
|
||||
texture = ExtResource( 1 )
|
||||
stretch_mode = 1
|
||||
|
||||
[node name="Label" type="Label" parent="Grid/left"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
text = "0.00"
|
||||
align = 1
|
||||
valign = 1
|
||||
|
||||
[node name="center" type="Control" parent="Grid"]
|
||||
margin_left = 68.0
|
||||
margin_top = 68.0
|
||||
margin_right = 132.0
|
||||
margin_bottom = 132.0
|
||||
rect_min_size = Vector2( 64, 64 )
|
||||
|
||||
[node name="right" type="Control" parent="Grid"]
|
||||
margin_left = 136.0
|
||||
margin_top = 68.0
|
||||
margin_right = 200.0
|
||||
margin_bottom = 132.0
|
||||
rect_min_size = Vector2( 64, 64 )
|
||||
|
||||
[node name="t" type="TextureRect" parent="Grid/right"]
|
||||
margin_left = 64.0
|
||||
margin_right = 576.0
|
||||
margin_bottom = 512.0
|
||||
rect_rotation = 90.0
|
||||
rect_scale = Vector2( 0.125, 0.125 )
|
||||
texture = ExtResource( 1 )
|
||||
stretch_mode = 1
|
||||
|
||||
[node name="Label" type="Label" parent="Grid/right"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
text = "0.00"
|
||||
align = 1
|
||||
valign = 1
|
||||
|
||||
[node name="down_left" type="Control" parent="Grid"]
|
||||
margin_top = 136.0
|
||||
margin_right = 64.0
|
||||
margin_bottom = 200.0
|
||||
rect_min_size = Vector2( 64, 64 )
|
||||
|
||||
[node name="down" type="Control" parent="Grid"]
|
||||
margin_left = 68.0
|
||||
margin_top = 136.0
|
||||
margin_right = 132.0
|
||||
margin_bottom = 200.0
|
||||
rect_min_size = Vector2( 64, 64 )
|
||||
|
||||
[node name="t" type="TextureRect" parent="Grid/down"]
|
||||
margin_right = 512.0
|
||||
margin_bottom = 512.0
|
||||
rect_scale = Vector2( 0.125, 0.125 )
|
||||
texture = ExtResource( 1 )
|
||||
stretch_mode = 1
|
||||
flip_v = true
|
||||
|
||||
[node name="Label" type="Label" parent="Grid/down"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
text = "0.00"
|
||||
align = 1
|
||||
valign = 1
|
||||
|
||||
[node name="down_right" type="Control" parent="Grid"]
|
||||
margin_left = 136.0
|
||||
margin_top = 136.0
|
||||
margin_right = 200.0
|
||||
margin_bottom = 200.0
|
||||
rect_min_size = Vector2( 64, 64 )
|
|
@ -0,0 +1,59 @@
|
|||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://mainmenu_theme.tres" type="Theme" id=1]
|
||||
[ext_resource path="res://scripts/MainMenu.gd" type="Script" id=2]
|
||||
|
||||
[node name="MainMenu" type="Control"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
theme = ExtResource( 1 )
|
||||
script = ExtResource( 2 )
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
custom_constants/separation = 16
|
||||
alignment = 1
|
||||
|
||||
[node name="btn_touch" type="Button" parent="VBoxContainer"]
|
||||
margin_left = 451.0
|
||||
margin_top = 380.0
|
||||
margin_right = 629.0
|
||||
margin_bottom = 448.0
|
||||
size_flags_horizontal = 4
|
||||
text = "Touch"
|
||||
|
||||
[node name="btn_step" type="Button" parent="VBoxContainer"]
|
||||
margin_left = 465.0
|
||||
margin_top = 464.0
|
||||
margin_right = 614.0
|
||||
margin_bottom = 532.0
|
||||
size_flags_horizontal = 4
|
||||
text = "Step"
|
||||
|
||||
[node name="btn_settings" type="Button" parent="VBoxContainer"]
|
||||
margin_left = 426.0
|
||||
margin_top = 548.0
|
||||
margin_right = 653.0
|
||||
margin_bottom = 616.0
|
||||
size_flags_horizontal = 4
|
||||
text = "Settings"
|
||||
|
||||
[node name="btn_quit" type="Button" parent="VBoxContainer"]
|
||||
margin_left = 471.0
|
||||
margin_top = 632.0
|
||||
margin_right = 608.0
|
||||
margin_bottom = 700.0
|
||||
size_flags_horizontal = 4
|
||||
text = "Quit"
|
||||
|
||||
[node name="lbl_settingspath" type="Label" parent="."]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
text = "Library directory is"
|
||||
align = 1
|
||||
|
||||
[connection signal="pressed" from="VBoxContainer/btn_touch" to="." method="_on_btn_touch_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer/btn_step" to="." method="_on_btn_step_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer/btn_settings" to="." method="_on_btn_settings_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer/btn_quit" to="." method="quit"]
|
|
@ -1,7 +1,7 @@
|
|||
[gd_scene load_steps=6 format=2]
|
||||
|
||||
[ext_resource path="res://scripts/ScoreText.gd" type="Script" id=1]
|
||||
[ext_resource path="res://scripts/Menu.gd" type="Script" id=2]
|
||||
[ext_resource path="res://scripts/TouchMenu.gd" type="Script" id=2]
|
||||
[ext_resource path="res://shaders/menu.tres" type="Material" id=3]
|
||||
[ext_resource path="res://shaders/scoretext.tres" type="Material" id=4]
|
||||
|
||||
|
@ -11,6 +11,8 @@ _data = [ Vector2( -1, -1 ), 0.0, 0.0, 0, 0, Vector2( 0, 0 ), 2.0, 2.0, 1, 1, Ve
|
|||
|
||||
[node name="Menu" type="Control"]
|
||||
material = ExtResource( 3 )
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
rect_clip_content = true
|
||||
script = ExtResource( 2 )
|
||||
__meta__ = {
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=7 format=2]
|
||||
[gd_scene load_steps=8 format=2]
|
||||
|
||||
[ext_resource path="res://assets/fonts/NotoSans-Regular.ttf" type="DynamicFontData" id=1]
|
||||
[ext_resource path="res://scripts/OptionPanel.gd" type="Script" id=2]
|
||||
|
@ -39,6 +39,8 @@ func _process(delta):
|
|||
update()
|
||||
"
|
||||
|
||||
[sub_resource type="GradientTexture2D" id=4]
|
||||
|
||||
[node name="OptionPanel" type="VBoxContainer"]
|
||||
margin_left = 2.0
|
||||
margin_right = 269.0
|
||||
|
@ -64,8 +66,8 @@ __meta__ = {
|
|||
[node name="PanelContainer" type="PanelContainer" parent="."]
|
||||
margin_top = 38.0
|
||||
margin_right = 300.0
|
||||
margin_bottom = 638.0
|
||||
rect_min_size = Vector2( 300, 600 )
|
||||
margin_bottom = 169.0
|
||||
rect_min_size = Vector2( 300, 80 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
@ -74,15 +76,15 @@ __meta__ = {
|
|||
margin_left = 7.0
|
||||
margin_top = 7.0
|
||||
margin_right = 293.0
|
||||
margin_bottom = 593.0
|
||||
margin_bottom = 124.0
|
||||
|
||||
[node name="cb_qsettings" type="CheckBox" parent="PanelContainer/VBoxContainer"]
|
||||
margin_right = 286.0
|
||||
margin_bottom = 31.0
|
||||
pressed = true
|
||||
text = "Quick Settings"
|
||||
|
||||
[node name="vbox_qsettings" type="VBoxContainer" parent="PanelContainer/VBoxContainer"]
|
||||
visible = false
|
||||
margin_top = 35.0
|
||||
margin_right = 286.0
|
||||
margin_bottom = 279.0
|
||||
|
@ -211,14 +213,14 @@ __meta__ = {
|
|||
}
|
||||
|
||||
[node name="HSeparator" type="HSeparator" parent="PanelContainer/VBoxContainer"]
|
||||
margin_top = 283.0
|
||||
margin_top = 35.0
|
||||
margin_right = 286.0
|
||||
margin_bottom = 287.0
|
||||
margin_bottom = 39.0
|
||||
|
||||
[node name="cb_graphics" type="CheckBox" parent="PanelContainer/VBoxContainer"]
|
||||
margin_top = 291.0
|
||||
margin_top = 43.0
|
||||
margin_right = 286.0
|
||||
margin_bottom = 322.0
|
||||
margin_bottom = 74.0
|
||||
text = "Graphics"
|
||||
|
||||
[node name="vbox_graphics" type="VBoxContainer" parent="PanelContainer/VBoxContainer"]
|
||||
|
@ -296,19 +298,25 @@ __meta__ = {
|
|||
}
|
||||
|
||||
[node name="HSeparator2" type="HSeparator" parent="PanelContainer/VBoxContainer"]
|
||||
margin_top = 326.0
|
||||
margin_top = 78.0
|
||||
margin_right = 286.0
|
||||
margin_bottom = 330.0
|
||||
margin_bottom = 82.0
|
||||
|
||||
[node name="VidTextureRect" type="TextureRect" parent="PanelContainer/VBoxContainer" groups=[
|
||||
"VideoTexRects",
|
||||
]]
|
||||
margin_top = 334.0
|
||||
[node name="cb_vid" type="CheckBox" parent="PanelContainer/VBoxContainer"]
|
||||
margin_top = 86.0
|
||||
margin_right = 286.0
|
||||
margin_bottom = 586.0
|
||||
margin_bottom = 117.0
|
||||
text = "Show Video Here"
|
||||
|
||||
[node name="VidTextureRect" type="TextureRect" parent="PanelContainer/VBoxContainer" groups=["VideoTexRects"]]
|
||||
visible = false
|
||||
margin_top = 369.0
|
||||
margin_right = 286.0
|
||||
margin_bottom = 609.0
|
||||
rect_min_size = Vector2( 240, 240 )
|
||||
size_flags_horizontal = 7
|
||||
size_flags_vertical = 7
|
||||
texture = SubResource( 4 )
|
||||
expand = true
|
||||
stretch_mode = 6
|
||||
|
||||
|
@ -323,3 +331,4 @@ stretch_mode = 6
|
|||
[connection signal="toggled" from="PanelContainer/VBoxContainer/vbox_graphics/hbox/btn_wakelock" to="." method="_on_btn_wakelock_toggled"]
|
||||
[connection signal="value_changed" from="PanelContainer/VBoxContainer/vbox_graphics/sl_SSX" to="." method="_on_sl_SSX_value_changed"]
|
||||
[connection signal="value_changed" from="PanelContainer/VBoxContainer/vbox_graphics/sl_SSY" to="." method="_on_sl_SSY_value_changed"]
|
||||
[connection signal="toggled" from="PanelContainer/VBoxContainer/cb_vid" to="PanelContainer/VBoxContainer/VidTextureRect" method="set_visible"]
|
|
@ -0,0 +1,11 @@
|
|||
extends AspectRatioContainer
|
||||
signal exit_mode
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
pass # Replace with function body.
|
||||
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
#func _process(delta):
|
||||
# pass
|
|
@ -1,21 +1,22 @@
|
|||
[gd_scene load_steps=21 format=2]
|
||||
[gd_scene load_steps=22 format=2]
|
||||
|
||||
[ext_resource path="res://scripts/InputHandler.gd" type="Script" id=1]
|
||||
[ext_resource path="res://scripts/NoteViewport.gd" type="Script" id=1]
|
||||
[ext_resource path="res://assets/text-4k.png" type="Texture" id=2]
|
||||
[ext_resource path="res://assets/fonts/Sniglet-Regular.ttf" type="DynamicFontData" id=3]
|
||||
[ext_resource path="res://scripts/ScreenFilter.gd" type="Script" id=4]
|
||||
[ext_resource path="res://scripts/NoteViewport.gd" type="Script" id=5]
|
||||
[ext_resource path="res://scripts/NotePainter.gd" type="Script" id=6]
|
||||
[ext_resource path="res://scripts/NoteHandler.gd" type="Script" id=8]
|
||||
[ext_resource path="res://scripts/Receptors.gd" type="Script" id=9]
|
||||
[ext_resource path="res://shaders/notelines.shader" type="Shader" id=10]
|
||||
[ext_resource path="res://shaders/notemesh.shader" type="Shader" id=11]
|
||||
[ext_resource path="res://shaders/receptors.shader" type="Shader" id=12]
|
||||
[ext_resource path="res://scripts/Bezel.gd" type="Script" id=13]
|
||||
[ext_resource path="res://Menu.tscn" type="PackedScene" id=15]
|
||||
[ext_resource path="res://scripts/NotePainter.gd" type="Script" id=3]
|
||||
[ext_resource path="res://scripts/InputHandler.gd" type="Script" id=4]
|
||||
[ext_resource path="res://scripts/Bezel.gd" type="Script" id=5]
|
||||
[ext_resource path="res://scripts/NoteHandler.gd" type="Script" id=6]
|
||||
[ext_resource path="res://assets/fonts/Sniglet-Regular.ttf" type="DynamicFontData" id=7]
|
||||
[ext_resource path="res://scripts/Receptors.gd" type="Script" id=8]
|
||||
[ext_resource path="res://scripts/ScreenFilter.gd" type="Script" id=9]
|
||||
[ext_resource path="res://scenes/Menu.tscn" type="PackedScene" id=10]
|
||||
[ext_resource path="res://shaders/receptors.shader" type="Shader" id=11]
|
||||
[ext_resource path="res://shaders/notemesh.shader" type="Shader" id=12]
|
||||
[ext_resource path="res://shaders/notelines.shader" type="Shader" id=13]
|
||||
[ext_resource path="res://scenes/RadialGame.gd" type="Script" id=14]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id=1]
|
||||
shader = ExtResource( 12 )
|
||||
shader = ExtResource( 11 )
|
||||
shader_param/num_receptors = 8
|
||||
shader_param/receptor_offset = 0.392699
|
||||
shader_param/line_color = Color( 0, 0, 1, 1 )
|
||||
|
@ -29,14 +30,14 @@ shader_param/px2 = 0.00217391
|
|||
shader_param/alpha = 1.0
|
||||
|
||||
[sub_resource type="ShaderMaterial" id=2]
|
||||
shader = ExtResource( 11 )
|
||||
shader = ExtResource( 12 )
|
||||
shader_param/bps = null
|
||||
shader_param/star_color = null
|
||||
shader_param/held_color = null
|
||||
shader_param/screen_size = null
|
||||
|
||||
[sub_resource type="ShaderMaterial" id=3]
|
||||
shader = ExtResource( 10 )
|
||||
shader = ExtResource( 13 )
|
||||
shader_param/line_color = Color( 0.8, 0.8, 1, 0.8 )
|
||||
shader_param/line_color_double = Color( 1, 1, 0.6, 0.9 )
|
||||
shader_param/dot_color = Color( 1, 1, 1, 0.8 )
|
||||
|
@ -55,9 +56,9 @@ shader_param/array_size = 256
|
|||
size = 48
|
||||
outline_size = 2
|
||||
outline_color = Color( 0, 0, 0, 1 )
|
||||
font_data = ExtResource( 3 )
|
||||
font_data = ExtResource( 7 )
|
||||
|
||||
[sub_resource type="GDScript" id=7]
|
||||
[sub_resource type="GDScript" id=5]
|
||||
script/source = "extends Label
|
||||
|
||||
const colors = [Color.gray, Color.lightgray, Color.aqua, Color.gold]
|
||||
|
@ -73,27 +74,24 @@ func _on_NoteHandler_finished_song(song_key, score_data) -> void:
|
|||
visible = false
|
||||
"
|
||||
|
||||
[sub_resource type="CanvasItemMaterial" id=5]
|
||||
[sub_resource type="CanvasItemMaterial" id=6]
|
||||
blend_mode = 4
|
||||
|
||||
[sub_resource type="Curve" id=6]
|
||||
[sub_resource type="Curve" id=7]
|
||||
min_value = -1.0
|
||||
_data = [ Vector2( -1, -1 ), 0.0, 0.0, 0, 0, Vector2( 0, 0 ), 2.0, 2.0, 1, 1, Vector2( 1, 1 ), 0.0, 0.0, 0, 0 ]
|
||||
|
||||
[node name="RadialGame" type="AspectRatioContainer"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
script = ExtResource( 14 )
|
||||
|
||||
[node name="Square" type="Control" parent="."]
|
||||
[node name="square" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
margin_right = 1080.0
|
||||
margin_bottom = 1080.0
|
||||
|
||||
[node name="video" type="TextureRect" parent="Square" groups=[
|
||||
"VideoTexRects",
|
||||
]]
|
||||
[node name="video" type="TextureRect" parent="square" groups=["VideoTexRects"]]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
|
@ -106,25 +104,18 @@ __meta__ = {
|
|||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="ScreenFilter" type="ColorRect" parent="Square"]
|
||||
[node name="ScreenFilter" type="ColorRect" parent="square"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color( 0, 0, 0, 1 )
|
||||
script = ExtResource( 4 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Receptors" type="Control" parent="Square"]
|
||||
material = SubResource( 1 )
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 9 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="NoteHandler" type="Control" parent="Square"]
|
||||
[node name="receptors" type="Control" parent="square"]
|
||||
unique_name_in_owner = true
|
||||
material = SubResource( 1 )
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 8 )
|
||||
|
@ -132,28 +123,38 @@ __meta__ = {
|
|||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Viewport" type="Viewport" parent="Square/NoteHandler"]
|
||||
[node name="noteHandler" type="Control" parent="square"]
|
||||
unique_name_in_owner = true
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 6 )
|
||||
|
||||
[node name="viewport" type="Viewport" parent="square/noteHandler"]
|
||||
unique_name_in_owner = true
|
||||
size = Vector2( 1080, 1080 )
|
||||
transparent_bg = true
|
||||
usage = 1
|
||||
render_target_v_flip = true
|
||||
script = ExtResource( 5 )
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="Center" type="Node2D" parent="Square/NoteHandler/Viewport"]
|
||||
[node name="Center" type="Node2D" parent="square/noteHandler/viewport"]
|
||||
position = Vector2( 540, 540 )
|
||||
|
||||
[node name="SlideTrailHandler" type="Node2D" parent="Square/NoteHandler/Viewport/Center"]
|
||||
[node name="slideTrailHandler" type="Node2D" parent="square/noteHandler/viewport/Center"]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="JudgeText" type="MeshInstance2D" parent="Square/NoteHandler/Viewport/Center"]
|
||||
[node name="judgeText" type="MeshInstance2D" parent="square/noteHandler/viewport/Center"]
|
||||
unique_name_in_owner = true
|
||||
texture = ExtResource( 2 )
|
||||
|
||||
[node name="meshinstance" type="MeshInstance2D" parent="Square/NoteHandler/Viewport/Center"]
|
||||
[node name="meshinstance" type="MeshInstance2D" parent="square/noteHandler/viewport/Center"]
|
||||
unique_name_in_owner = true
|
||||
material = SubResource( 2 )
|
||||
|
||||
[node name="notelines" type="MeshInstance2D" parent="Square/NoteHandler/Viewport/Center"]
|
||||
[node name="notelines" type="MeshInstance2D" parent="square/noteHandler/viewport/Center"]
|
||||
unique_name_in_owner = true
|
||||
material = SubResource( 3 )
|
||||
|
||||
[node name="lbl_combo" type="Label" parent="Square/NoteHandler"]
|
||||
[node name="lbl_combo" type="Label" parent="square/noteHandler"]
|
||||
visible = false
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
|
@ -169,45 +170,38 @@ custom_fonts/font = SubResource( 4 )
|
|||
text = "0"
|
||||
align = 1
|
||||
valign = 1
|
||||
script = SubResource( 7 )
|
||||
script = SubResource( 5 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Painter" type="Control" parent="Square"]
|
||||
material = SubResource( 5 )
|
||||
[node name="painter" type="Control" parent="square"]
|
||||
unique_name_in_owner = true
|
||||
material = SubResource( 6 )
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 6 )
|
||||
script = ExtResource( 3 )
|
||||
|
||||
[node name="Menu" parent="square" instance=ExtResource( 10 )]
|
||||
ease_curve = SubResource( 7 )
|
||||
|
||||
[node name="Bezel" type="Control" parent="square"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 5 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Menu" parent="Square" instance=ExtResource( 15 )]
|
||||
[node name="InputHandler" type="Control" parent="square"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
NoteHandlerPath = NodePath("../NoteHandler")
|
||||
ReceptorsPath = NodePath("../Receptors")
|
||||
ease_curve = SubResource( 6 )
|
||||
|
||||
[node name="Bezel" type="Control" parent="Square"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 13 )
|
||||
script = ExtResource( 4 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="InputHandler" type="Control" parent="Square"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[connection signal="item_rect_changed" from="Square" to="Square/NoteHandler/Viewport" method="_on_Square_item_rect_changed"]
|
||||
[connection signal="combo_changed" from="Square/NoteHandler" to="Square/NoteHandler/lbl_combo" method="_on_NoteHandler_combo_changed"]
|
||||
[connection signal="finished_song" from="Square/NoteHandler" to="Square/NoteHandler/lbl_combo" method="_on_NoteHandler_finished_song"]
|
||||
[connection signal="column_pressed" from="Square/InputHandler" to="Square/NoteHandler" method="_on_InputHandler_column_pressed"]
|
||||
[connection signal="column_released" from="Square/InputHandler" to="Square/NoteHandler" method="_on_InputHandler_column_released"]
|
||||
[connection signal="combo_changed" from="square/noteHandler" to="square/noteHandler/lbl_combo" method="_on_NoteHandler_combo_changed"]
|
||||
[connection signal="finished_song" from="square/noteHandler" to="square/noteHandler/lbl_combo" method="_on_NoteHandler_finished_song"]
|
||||
[connection signal="column_pressed" from="square/InputHandler" to="square/noteHandler" method="_on_InputHandler_column_pressed"]
|
||||
[connection signal="column_released" from="square/InputHandler" to="square/noteHandler" method="_on_InputHandler_column_released"]
|
|
@ -0,0 +1,58 @@
|
|||
extends Control
|
||||
const DirectorySettingsEntry = preload('res://scenes/DirectorySettingsEntry.tscn')
|
||||
onready var container_folders = $'%container_folders'
|
||||
|
||||
signal exit_mode
|
||||
|
||||
var paths = []
|
||||
var path_entries := []
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
paths = Array(Settings.get_library_paths())
|
||||
generate_entries()
|
||||
|
||||
func generate_entries():
|
||||
paths.append("")
|
||||
for entry in path_entries:
|
||||
container_folders.remove_child(entry)
|
||||
path_entries = []
|
||||
|
||||
for path in paths:
|
||||
var entry = DirectorySettingsEntry.instance()
|
||||
entry.connect('path_updated', self, '_entry_updated', [len(path_entries)])
|
||||
path_entries.append(entry)
|
||||
entry.directory = path
|
||||
container_folders.add_child(entry)
|
||||
path_entries[0].removable = false
|
||||
path_entries[0].readonly = true
|
||||
|
||||
func get_paths_from_entries() -> Array:
|
||||
var paths := []
|
||||
for entry in path_entries:
|
||||
var dir = entry.directory
|
||||
if dir:
|
||||
paths.append(entry.directory)
|
||||
return paths
|
||||
|
||||
func _save_settings():
|
||||
var paths = get_paths_from_entries()
|
||||
var user_path: String = paths[0]
|
||||
var extra_paths: Array = paths.slice(1, -1)
|
||||
Settings.set_additional_library_paths(extra_paths)
|
||||
|
||||
func _entry_updated(new_path: String, index: int):
|
||||
paths = get_paths_from_entries()
|
||||
if (index == len(path_entries)-1):
|
||||
if new_path:
|
||||
generate_entries()
|
||||
elif not new_path:
|
||||
generate_entries()
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
#func _process(delta):
|
||||
# pass
|
||||
|
||||
|
||||
func _on_btn_back_pressed():
|
||||
_save_settings()
|
||||
emit_signal('exit_mode')
|
|
@ -0,0 +1,32 @@
|
|||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://scenes/SettingsMenu.gd" type="Script" id=2]
|
||||
|
||||
[node name="SettingsMenu" type="Control"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 2 )
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer"]
|
||||
margin_right = 1080.0
|
||||
margin_bottom = 14.0
|
||||
text = "Resource Folders"
|
||||
align = 1
|
||||
|
||||
[node name="container_folders" type="VBoxContainer" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
margin_top = 18.0
|
||||
margin_right = 1080.0
|
||||
margin_bottom = 18.0
|
||||
|
||||
[node name="btn_back" type="Button" parent="VBoxContainer"]
|
||||
margin_top = 22.0
|
||||
margin_right = 1080.0
|
||||
margin_bottom = 42.0
|
||||
text = "Back"
|
||||
|
||||
[connection signal="pressed" from="VBoxContainer/btn_back" to="." method="_on_btn_back_pressed"]
|
|
@ -0,0 +1,12 @@
|
|||
extends Control
|
||||
signal exit_mode
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
pass # Replace with function body.
|
||||
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
#func _process(delta):
|
||||
# pass
|
|
@ -0,0 +1,182 @@
|
|||
[gd_scene load_steps=17 format=2]
|
||||
|
||||
[ext_resource path="res://scripts/NoteViewport.gd" type="Script" id=1]
|
||||
[ext_resource path="res://assets/text-4k.png" type="Texture" id=2]
|
||||
[ext_resource path="res://scripts/NotePainter.gd" type="Script" id=3]
|
||||
[ext_resource path="res://scripts/InputHandler.gd" type="Script" id=4]
|
||||
[ext_resource path="res://scenes/StepMenu.tscn" type="PackedScene" id=5]
|
||||
[ext_resource path="res://scripts/NoteHandler.gd" type="Script" id=6]
|
||||
[ext_resource path="res://assets/fonts/Sniglet-Regular.ttf" type="DynamicFontData" id=7]
|
||||
[ext_resource path="res://scenes/StepGame.gd" type="Script" id=8]
|
||||
[ext_resource path="res://scripts/ScreenFilter.gd" type="Script" id=9]
|
||||
[ext_resource path="res://shaders/notemesh.shader" type="Shader" id=12]
|
||||
[ext_resource path="res://shaders/notelines.shader" type="Shader" id=13]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id=2]
|
||||
shader = ExtResource( 12 )
|
||||
shader_param/bps = null
|
||||
shader_param/star_color = null
|
||||
shader_param/held_color = null
|
||||
shader_param/screen_size = null
|
||||
|
||||
[sub_resource type="ShaderMaterial" id=3]
|
||||
shader = ExtResource( 13 )
|
||||
shader_param/line_color = Color( 0.8, 0.8, 1, 0.8 )
|
||||
shader_param/line_color_double = Color( 1, 1, 0.6, 0.9 )
|
||||
shader_param/dot_color = Color( 1, 1, 1, 0.8 )
|
||||
shader_param/bps = 1.0
|
||||
shader_param/line_thickness = 0.012
|
||||
shader_param/line_thickness_min = 0.0
|
||||
shader_param/dot_thickness = 0.033
|
||||
shader_param/dot_fullbright_thickness = 0.013
|
||||
shader_param/max_angle = 1.0708
|
||||
shader_param/max_dist = 1.25
|
||||
shader_param/array_postmul = Vector3( 1, 1, 1 )
|
||||
shader_param/array_sidelen = 16
|
||||
shader_param/array_size = 256
|
||||
|
||||
[sub_resource type="DynamicFont" id=4]
|
||||
size = 48
|
||||
outline_size = 2
|
||||
outline_color = Color( 0, 0, 0, 1 )
|
||||
font_data = ExtResource( 7 )
|
||||
|
||||
[sub_resource type="GDScript" id=5]
|
||||
script/source = "extends Label
|
||||
|
||||
const colors = [Color.gray, Color.lightgray, Color.aqua, Color.gold]
|
||||
|
||||
|
||||
func _on_NoteHandler_combo_changed(value) -> void:
|
||||
text = str(value)
|
||||
visible = (value > 0)
|
||||
add_color_override('font_color', colors[int(min(3, value/50))])
|
||||
|
||||
|
||||
func _on_NoteHandler_finished_song(song_key, score_data) -> void:
|
||||
visible = false
|
||||
"
|
||||
|
||||
[sub_resource type="CanvasItemMaterial" id=6]
|
||||
blend_mode = 4
|
||||
|
||||
[node name="StepGame" type="Control"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 8 )
|
||||
|
||||
[node name="video" type="TextureRect" parent="." groups=["VideoTexRects"]]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
rect_pivot_offset = Vector2( 540, 540 )
|
||||
mouse_filter = 2
|
||||
expand = true
|
||||
stretch_mode = 6
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="ScreenFilter" type="ColorRect" parent="."]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color( 0, 0, 0, 1 )
|
||||
script = ExtResource( 9 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="receptors" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="receptors"]
|
||||
margin_right = 1080.0
|
||||
margin_bottom = 1080.0
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="noteHandler" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 6 )
|
||||
|
||||
[node name="viewport" type="Viewport" parent="noteHandler"]
|
||||
unique_name_in_owner = true
|
||||
size = Vector2( 1080, 1080 )
|
||||
transparent_bg = true
|
||||
render_target_v_flip = true
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="Center" type="Node2D" parent="noteHandler/viewport"]
|
||||
position = Vector2( 540, 540 )
|
||||
|
||||
[node name="slideTrailHandler" type="Node2D" parent="noteHandler/viewport/Center"]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="judgeText" type="MeshInstance2D" parent="noteHandler/viewport/Center"]
|
||||
unique_name_in_owner = true
|
||||
texture = ExtResource( 2 )
|
||||
|
||||
[node name="meshinstance" type="MeshInstance2D" parent="noteHandler/viewport/Center"]
|
||||
unique_name_in_owner = true
|
||||
material = SubResource( 2 )
|
||||
|
||||
[node name="notelines" type="MeshInstance2D" parent="noteHandler/viewport/Center"]
|
||||
unique_name_in_owner = true
|
||||
material = SubResource( 3 )
|
||||
|
||||
[node name="lbl_combo" type="Label" parent="noteHandler"]
|
||||
visible = false
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
margin_left = -20.0
|
||||
margin_top = -7.0
|
||||
margin_right = 20.0
|
||||
margin_bottom = 7.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
custom_fonts/font = SubResource( 4 )
|
||||
text = "0"
|
||||
align = 1
|
||||
valign = 1
|
||||
script = SubResource( 5 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="painter" type="Control" parent="noteHandler"]
|
||||
unique_name_in_owner = true
|
||||
material = SubResource( 6 )
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 3 )
|
||||
|
||||
[node name="StepMenu" parent="." instance=ExtResource( 5 )]
|
||||
|
||||
[node name="InputHandler" type="Control" parent="."]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 4 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="square" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[connection signal="combo_changed" from="noteHandler" to="noteHandler/lbl_combo" method="_on_NoteHandler_combo_changed"]
|
||||
[connection signal="finished_song" from="noteHandler" to="noteHandler/lbl_combo" method="_on_NoteHandler_finished_song"]
|
||||
[connection signal="column_pressed" from="InputHandler" to="noteHandler" method="_on_InputHandler_column_pressed"]
|
||||
[connection signal="column_released" from="InputHandler" to="noteHandler" method="_on_InputHandler_column_released"]
|
|
@ -0,0 +1,38 @@
|
|||
[gd_scene load_steps=7 format=2]
|
||||
|
||||
[ext_resource path="res://scripts/ScoreText.gd" type="Script" id=1]
|
||||
[ext_resource path="res://scripts/StepMenu.gd" type="Script" id=2]
|
||||
[ext_resource path="res://shaders/menu.tres" type="Material" id=3]
|
||||
[ext_resource path="res://shaders/scoretext.tres" type="Material" id=4]
|
||||
[ext_resource path="res://scenes/JoypadMonitor6Btn.tscn" type="PackedScene" id=5]
|
||||
|
||||
[sub_resource type="Curve" id=1]
|
||||
min_value = -1.0
|
||||
_data = [ Vector2( -1, -1 ), 0.0, 0.0, 0, 0, Vector2( 0, 0 ), 2.0, 2.0, 1, 1, Vector2( 1, 1 ), 0.0, 0.0, 0, 0 ]
|
||||
|
||||
[node name="StepMenu" type="Control"]
|
||||
material = ExtResource( 3 )
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
rect_clip_content = true
|
||||
script = ExtResource( 2 )
|
||||
ease_curve = SubResource( 1 )
|
||||
|
||||
[node name="ScoreText" type="Node2D" parent="."]
|
||||
material = ExtResource( 4 )
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="PVMusic" type="AudioStreamPlayer" parent="."]
|
||||
bus = "Preview"
|
||||
|
||||
[node name="JoypadMonitor" parent="." instance=ExtResource( 5 )]
|
||||
|
||||
[node name="JoypadMonitor2" parent="." instance=ExtResource( 5 )]
|
||||
margin_left = 200.0
|
||||
joypad_index = 1
|
||||
|
||||
[node name="JoypadMonitor3" parent="." instance=ExtResource( 5 )]
|
||||
margin_left = 400.0
|
||||
margin_right = 200.0
|
||||
margin_bottom = 238.0
|
||||
joypad_index = 1
|
|
@ -0,0 +1,85 @@
|
|||
tool
|
||||
extends Control
|
||||
|
||||
# This is for playing around with weird control point curves in-editor.
|
||||
# Some slide types are a bit difficult to reason about.
|
||||
|
||||
const ORBIT_INNER_RADIUS = sin(deg2rad(22.5)) # ~0.38
|
||||
const ORBIT_KAPPA = (sqrt(2)-1) * 4.0 / 3.0 # This is the length of control points along a tangent to approximate a circle (multiply by desired radius)
|
||||
|
||||
export(float, -360, 360, 7.5) var angle_entry = -22.5 # degrees
|
||||
export(float, -360, 360, 7.5) var angle_exit = 112.5 # degrees
|
||||
var inner_radius = ORBIT_INNER_RADIUS
|
||||
export(float, 5.0, 120.0) var max_arc_angle := 45.0
|
||||
export(bool) var show_points := true
|
||||
export(bool) var show_handles := true
|
||||
export(bool) var flip_direction := false
|
||||
var sideorbit_center := Vector2.ZERO
|
||||
|
||||
var curve2d := Curve2D.new()
|
||||
|
||||
func _draw() -> void:
|
||||
# draw_circle(rect_size * 0.5, inner_radius * GameTheme.receptor_ring_radius, Color.darkgreen)
|
||||
var points = curve2d.get_baked_points()
|
||||
if len(points) < 2:
|
||||
return
|
||||
draw_set_transform(rect_size * 0.5, 0, Vector2.ONE * GameTheme.receptor_ring_radius)
|
||||
draw_circle(sideorbit_center, inner_radius, Color.darkgreen)
|
||||
draw_multiline(points, Color.white)
|
||||
|
||||
var l = curve2d.get_point_count()
|
||||
var c_points = []
|
||||
var handles_in = []
|
||||
var handles_out = []
|
||||
for i in l:
|
||||
var p = curve2d.get_point_position(i)
|
||||
c_points.append(p)
|
||||
handles_in.append(curve2d.get_point_in(i) + p)
|
||||
handles_out.append(curve2d.get_point_out(i) + p)
|
||||
if show_handles:
|
||||
for i in l:
|
||||
draw_circle(handles_in[i], 0.01, Color.burlywood)
|
||||
draw_circle(handles_out[i], 0.01, Color.cadetblue)
|
||||
if show_points:
|
||||
for i in l:
|
||||
draw_circle(c_points[i], 0.012, Color.blanchedalmond)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
curve2d.clear_points()
|
||||
curve2d.bake_interval = 0.01
|
||||
var rad_in = deg2rad(angle_entry)
|
||||
var rad_out = deg2rad(angle_exit)
|
||||
curve2d.add_point(polar2cartesian(1.0, rad_in))
|
||||
Note.curve2d_make_sideorbit(curve2d, rad_in, rad_out, flip_direction)
|
||||
curve2d.add_point(polar2cartesian(1.0, rad_out))
|
||||
sideorbit_center = polar2cartesian(inner_radius, rad_in-PI*0.5*(-1 if flip_direction else 1))
|
||||
update()
|
||||
|
||||
#func curve2d_make_sideorbit(curve2d: Curve2D, rad_in: float, rad_out: float, ccw: bool, rad_max_arc:=PI*0.25, kappa:=ORBIT_KAPPA, inner_radius:=ORBIT_INNER_RADIUS):
|
||||
# var d_sign := -1 if ccw else 1
|
||||
#
|
||||
# sideorbit_center = polar2cartesian(inner_radius, rad_in-PI*0.5*d_sign)
|
||||
#
|
||||
# var rad_orbit_in := rad_in + PI*0.5*d_sign
|
||||
# var orbcenter_to_out := polar2cartesian(1.0, rad_out) - sideorbit_center
|
||||
# var rad_orbit_out := orbcenter_to_out.angle() - acos(inner_radius/orbcenter_to_out.length())*d_sign
|
||||
# var pos_orbit_out := sideorbit_center + polar2cartesian(inner_radius, rad_orbit_out)
|
||||
#
|
||||
# var rad_2 = rad_in + PI
|
||||
# var rad_2t = rad_2+PI*0.5*d_sign
|
||||
# var rad_3 = rad_out-PI*3/8*d_sign
|
||||
# var rad_3t = rad_3-PI*0.5*d_sign
|
||||
#
|
||||
# var a_diff = wrapf((rad_orbit_out-rad_orbit_in)*d_sign, 0.0001, TAU+0.0001)
|
||||
# var n = ceil(a_diff/rad_max_arc)
|
||||
# var ad = a_diff/n
|
||||
# var k = kappa*inner_radius*(2*ad/PI) # Not geometrically correct scaling but reasonable for now
|
||||
#
|
||||
## curve2d.add_point(polar2cartesian(1.0, rad_in))
|
||||
# curve2d.add_point(Vector2.ZERO, Vector2.ZERO, polar2cartesian(k, rad_2))
|
||||
# for i in range(1, n):
|
||||
# var ang = rad_orbit_in + i*ad*d_sign
|
||||
# curve2d.add_point(sideorbit_center + polar2cartesian(inner_radius, ang), polar2cartesian(k, ang-PI/2*d_sign), polar2cartesian(k, ang+PI/2*d_sign))
|
||||
#
|
||||
# curve2d.add_point(pos_orbit_out, polar2cartesian(k, rad_orbit_out-PI*0.5*d_sign))
|
||||
## curve2d.add_point(polar2cartesian(1.0, rad_out))
|
|
@ -0,0 +1,99 @@
|
|||
# Static functions mostly for FileLoader to make use of because of deficiancies in load()
|
||||
extends Node
|
||||
|
||||
const ERROR_CODES := [
|
||||
'OK', 'FAILED', 'ERR_UNAVAILABLE', 'ERR_UNCONFIGURED', 'ERR_UNAUTHORIZED', 'ERR_PARAMETER_RANGE_ERROR',
|
||||
'ERR_OUT_OF_MEMORY', 'ERR_FILE_NOT_FOUND', 'ERR_FILE_BAD_DRIVE', 'ERR_FILE_BAD_PATH','ERR_FILE_NO_PERMISSION',
|
||||
'ERR_FILE_ALREADY_IN_USE', 'ERR_FILE_CANT_OPEN', 'ERR_FILE_CANT_WRITE', 'ERR_FILE_CANT_READ', 'ERR_FILE_UNRECOGNIZED',
|
||||
'ERR_FILE_CORRUPT', 'ERR_FILE_MISSING_DEPENDENCIES', 'ERR_FILE_EOF', 'ERR_CANT_OPEN', 'ERR_CANT_CREATE', 'ERR_QUERY_FAILED',
|
||||
'ERR_ALREADY_IN_USE', 'ERR_LOCKED', 'ERR_TIMEOUT', 'ERR_CANT_CONNECT', 'ERR_CANT_RESOLVE', 'ERR_CONNECTION_ERROR',
|
||||
'ERR_CANT_ACQUIRE_RESOURCE', 'ERR_CANT_FORK', 'ERR_INVALID_DATA', 'ERR_INVALID_PARAMETER', 'ERR_ALREADY_EXISTS',
|
||||
'ERR_DOES_NOT_EXIST', 'ERR_DATABASE_CANT_READ', 'ERR_DATABASE_CANT_WRITE', 'ERR_COMPILATION_FAILED', 'ERR_METHOD_NOT_FOUND',
|
||||
'ERR_LINK_FAILED', 'ERR_SCRIPT_FAILED', 'ERR_CYCLIC_LINK', 'ERR_INVALID_DECLARATION', 'ERR_DUPLICATE_SYMBOL',
|
||||
'ERR_PARSE_ERROR', 'ERR_BUSY', 'ERR_SKIP', 'ERR_HELP', 'ERR_BUG'
|
||||
]
|
||||
|
||||
static func load_image(filename: String) -> ImageTexture:
|
||||
var tex := ImageTexture.new()
|
||||
var img := Image.new()
|
||||
img.load(filename)
|
||||
tex.create_from_image(img)
|
||||
return tex
|
||||
|
||||
|
||||
static func load_ogg(filename: String) -> AudioStreamOGGVorbis:
|
||||
# Loads the ogg file with that exact filename
|
||||
var audiostream = AudioStreamOGGVorbis.new()
|
||||
var oggfile = File.new()
|
||||
oggfile.open(filename, File.READ)
|
||||
audiostream.set_data(oggfile.get_buffer(oggfile.get_len()))
|
||||
oggfile.close()
|
||||
return audiostream
|
||||
|
||||
|
||||
static func load_video(filename: String):
|
||||
return load(filename)
|
||||
# This may need reenabling for some platforms:
|
||||
#var videostream = VideoStreamGDNative.new()
|
||||
#videostream.set_file(filename)
|
||||
#return videostream
|
||||
|
||||
|
||||
static func directory_list(directory: String, hidden: bool, sort:=true) -> Dictionary:
|
||||
# Sadly there's no filelist sugar so we make our own
|
||||
var output = {folders=[], files=[], err=OK}
|
||||
var dir = Directory.new()
|
||||
output.err = dir.open(directory)
|
||||
if output.err != OK:
|
||||
print_debug('Failed to open directory: ' + directory + '(Error code '+output.err+')')
|
||||
return output
|
||||
output.err = dir.list_dir_begin(true, !hidden)
|
||||
if output.err != OK:
|
||||
print_debug('Failed to begin listing directory: ' + directory + '(Error code '+output.err+')')
|
||||
return output
|
||||
|
||||
var item = dir.get_next()
|
||||
while (item != ''):
|
||||
if dir.current_is_dir():
|
||||
output['folders'].append(item)
|
||||
else:
|
||||
output['files'].append(item)
|
||||
item = dir.get_next()
|
||||
dir.list_dir_end()
|
||||
|
||||
if sort:
|
||||
output.folders.sort()
|
||||
output.files.sort()
|
||||
# Maybe convert the Arrays to PoolStringArrays?
|
||||
return output
|
||||
|
||||
|
||||
static func find_by_extensions(array, extensions=null) -> Dictionary:
|
||||
# Both args can be Array or PoolStringArray
|
||||
# If extensions omitted, do all extensions
|
||||
var output = {}
|
||||
if extensions:
|
||||
for ext in extensions:
|
||||
output[ext] = []
|
||||
for filename in array:
|
||||
for ext in extensions:
|
||||
if filename.ends_with(ext):
|
||||
output[ext].append(filename)
|
||||
else:
|
||||
for filename in array:
|
||||
var ext = filename.rsplit('.', false, 1)[1]
|
||||
if ext in output:
|
||||
output[ext].append(filename)
|
||||
else:
|
||||
output[ext] = [filename]
|
||||
return output
|
||||
|
||||
|
||||
static func init_directory(directory: String):
|
||||
var dir = Directory.new()
|
||||
var err = dir.make_dir_recursive(directory)
|
||||
if err == ERR_ALREADY_EXISTS: # API changed?
|
||||
err = OK
|
||||
if err != OK:
|
||||
print('An error occurred while trying to create the directory: ', directory, err, ERROR_CODES[err])
|
||||
return err
|
|
@ -0,0 +1,23 @@
|
|||
extends Control
|
||||
signal start_touchgame
|
||||
signal start_stepgame
|
||||
signal open_settings
|
||||
|
||||
func update_libraries_text() -> void:
|
||||
$lbl_settingspath.text = 'Data directories:\n' + '\n'.join(Settings.get_library_paths())
|
||||
|
||||
func _ready() -> void:
|
||||
update_libraries_text()
|
||||
Settings.connect('config_loaded', self, 'update_libraries_text')
|
||||
|
||||
func quit() -> void:
|
||||
get_tree().quit()
|
||||
|
||||
func _on_btn_touch_pressed() -> void:
|
||||
emit_signal('start_touchgame')
|
||||
|
||||
func _on_btn_step_pressed() -> void:
|
||||
emit_signal('start_stepgame')
|
||||
|
||||
func _on_btn_settings_pressed():
|
||||
emit_signal('open_settings')
|
|
@ -1,4 +1,5 @@
|
|||
extends Control
|
||||
const RadialMeshTools := preload('res://scripts/RadialMeshTools.gd')
|
||||
|
||||
var screen_height := 1080
|
||||
|
||||
|
@ -8,21 +9,16 @@ signal combo_changed(value)
|
|||
var running := false
|
||||
var song_key = ''
|
||||
|
||||
onready var MusicPlayer := SoundPlayer.music_player
|
||||
onready var VideoPlayer := Video.video
|
||||
onready var musicPlayer := SoundPlayer.music_player
|
||||
onready var videoPlayer := Video.video
|
||||
|
||||
onready var Painter = $'../Painter'
|
||||
onready var SlideTrailHandler = $'Viewport/Center/SlideTrailHandler'
|
||||
onready var JudgeText = $'Viewport/Center/JudgeText'
|
||||
onready var notelines = $'Viewport/Center/notelines'
|
||||
onready var meshinstance = $'Viewport/Center/meshinstance'
|
||||
onready var painter = $'%painter'
|
||||
onready var slideTrailHandler = $'%slideTrailHandler'
|
||||
onready var judgeText = $'%judgeText'
|
||||
onready var notelines = $'%notelines'
|
||||
onready var meshinstance = $'%meshinstance'
|
||||
onready var lbl_combo = $lbl_combo
|
||||
|
||||
const SQRT2 := sqrt(2)
|
||||
const DEG45 := deg2rad(45.0)
|
||||
const DEG90 := deg2rad(90.0)
|
||||
const DEG135 := deg2rad(135.0)
|
||||
|
||||
var time_zero_msec: int = 0
|
||||
var time: float = 0.0
|
||||
var t: float = 0.0 # Game time
|
||||
|
@ -42,37 +38,7 @@ var slide_trail_mesh_instances := {}
|
|||
|
||||
var noteline_array_image := Image.new()
|
||||
|
||||
# Text UVs
|
||||
var text_UV_arrays := []
|
||||
func make_text_UV(row: int, column: int) -> PoolVector2Array:
|
||||
return PoolVector2Array([Vector2(column/4.0, row/8.0), Vector2((column+1)/4.0, row/8.0), Vector2(column/4.0, (row+1)/8.0), Vector2((column+1)/4.0, (row+1)/8.0)])
|
||||
func make_text_UVs():
|
||||
for row in 8:
|
||||
for column in 4:
|
||||
text_UV_arrays.append(make_text_UV(row, column))
|
||||
enum TextStyle {STRAIGHT=0, ARC=1, ARC_EARLY=2, ARC_LATE=3}
|
||||
enum TextWord {NICE=0, OK=4, NG=8, PERFECT=12, GREAT=16, GOOD=20, ALMOST=24, MISS=28}
|
||||
const TextJudgement := {
|
||||
0: TextWord.PERFECT + TextStyle.ARC,
|
||||
1: TextWord.GREAT + TextStyle.ARC_LATE,
|
||||
-1: TextWord.GREAT + TextStyle.ARC_EARLY,
|
||||
2: TextWord.GOOD + TextStyle.ARC_LATE,
|
||||
-2: TextWord.GOOD + TextStyle.ARC_EARLY,
|
||||
3: TextWord.ALMOST + TextStyle.ARC_LATE,
|
||||
-3: TextWord.ALMOST + TextStyle.ARC_EARLY,
|
||||
'MISS': TextWord.MISS + TextStyle.ARC
|
||||
}
|
||||
const TextJudgementStraight := {
|
||||
0: TextWord.PERFECT + TextStyle.STRAIGHT,
|
||||
1: TextWord.GREAT + TextStyle.STRAIGHT,
|
||||
-1: TextWord.GREAT + TextStyle.STRAIGHT,
|
||||
2: TextWord.GOOD + TextStyle.STRAIGHT,
|
||||
-2: TextWord.GOOD + TextStyle.STRAIGHT,
|
||||
3: TextWord.ALMOST + TextStyle.STRAIGHT,
|
||||
-3: TextWord.ALMOST + TextStyle.STRAIGHT,
|
||||
'MISS': TextWord.MISS + TextStyle.STRAIGHT
|
||||
}
|
||||
|
||||
#----------------------------------------------------------------------------------------------------------------------------------------------
|
||||
var current_combo := 0
|
||||
func increment_combo():
|
||||
current_combo += 1
|
||||
|
@ -87,131 +53,16 @@ func initialise_scores():
|
|||
scores = {}
|
||||
for type in [Note.NOTE_TAP, Note.NOTE_HOLD, Note.NOTE_STAR]:
|
||||
scores[type] = {}
|
||||
for key in TextJudgement:
|
||||
for key in RadialMeshTools.TextJudgement:
|
||||
scores[type][key] = 0
|
||||
# Release types
|
||||
for type in [Note.NOTE_HOLD, Note.NOTE_SLIDE]:
|
||||
scores[Note.RELEASE_SCORE_TYPES[type]] = {}
|
||||
for key in TextJudgement:
|
||||
for key in RadialMeshTools.TextJudgement:
|
||||
scores[Note.RELEASE_SCORE_TYPES[type]][key] = 0
|
||||
scores['max_combo'] = 0
|
||||
current_combo = 0
|
||||
|
||||
func make_text_mesh(mesh: ArrayMesh, text_id: int, pos: Vector2, angle: float, alpha:=1.0, scale:=1.0):
|
||||
var r := GameTheme.judge_text_size2 * scale
|
||||
var vertex_array := PoolVector2Array([
|
||||
pos+polar2cartesian(r, angle+GameTheme.JUDGE_TEXT_ANG2), # TODO: fix this UV/vertex order mess
|
||||
pos+polar2cartesian(r, angle+GameTheme.JUDGE_TEXT_ANG1),
|
||||
pos+polar2cartesian(r, angle+GameTheme.JUDGE_TEXT_ANG4),
|
||||
pos+polar2cartesian(r, angle+GameTheme.JUDGE_TEXT_ANG3)
|
||||
])
|
||||
var arrays = []
|
||||
arrays.resize(Mesh.ARRAY_MAX)
|
||||
arrays[Mesh.ARRAY_VERTEX] = vertex_array
|
||||
arrays[Mesh.ARRAY_TEX_UV] = text_UV_arrays[text_id]
|
||||
arrays[Mesh.ARRAY_COLOR] = GameTheme.color_array_text(alpha)
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays)
|
||||
|
||||
func make_judgement_text(mesh: ArrayMesh, text_id: int, col: int, progress:=0.0):
|
||||
make_text_mesh(mesh, text_id,
|
||||
GameTheme.RADIAL_UNIT_VECTORS[col] * GameTheme.receptor_ring_radius * lerp(0.85, 0.85*0.75, progress),
|
||||
GameTheme.RADIAL_COL_ANGLES[col]-PI/2.0, lerp(1.0, 0.0, progress), lerp(1.0, 0.75, progress)
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
# Helper functions to generate meshes from vertex arrays
|
||||
func make_tap_mesh(mesh: ArrayMesh, note_center: Vector2, scale:=1.0, color_array:=GameTheme.COLOR_ARRAY_TAP):
|
||||
var dim = GameTheme.sprite_size2 * scale
|
||||
var vertex_array = PoolVector2Array([note_center + Vector2(-dim, -dim), note_center + Vector2(dim, -dim), note_center + Vector2(-dim, dim), note_center + Vector2(dim, dim)])
|
||||
var arrays = []
|
||||
arrays.resize(Mesh.ARRAY_MAX)
|
||||
arrays[Mesh.ARRAY_VERTEX] = vertex_array
|
||||
arrays[Mesh.ARRAY_TEX_UV] = GameTheme.UV_ARRAY_TAP
|
||||
arrays[Mesh.ARRAY_COLOR] = color_array
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays)
|
||||
|
||||
func make_hold_mesh(mesh: ArrayMesh, note_center: Vector2, note_center_rel: Vector2, scale:=1.0, angle:=0.0, color_array = GameTheme.COLOR_ARRAY_HOLD):
|
||||
var dim = GameTheme.sprite_size2 * scale
|
||||
var dim2 = dim * SQRT2
|
||||
var a1 = angle - DEG45
|
||||
var a2 = angle + DEG45
|
||||
var a3 = angle - DEG90
|
||||
var a4 = angle + DEG90
|
||||
var a5 = angle - DEG135
|
||||
var a6 = angle + DEG135
|
||||
var vertex_array = PoolVector2Array([
|
||||
note_center + polar2cartesian(dim2, a1), note_center + polar2cartesian(dim2, a2),
|
||||
note_center + polar2cartesian(dim, a3), note_center + polar2cartesian(dim, a4),
|
||||
note_center_rel + polar2cartesian(dim, a3), note_center_rel + polar2cartesian(dim, a4),
|
||||
note_center_rel + polar2cartesian(dim2, a5), note_center_rel + polar2cartesian(dim2, a6)
|
||||
])
|
||||
var arrays = []
|
||||
arrays.resize(Mesh.ARRAY_MAX)
|
||||
arrays[Mesh.ARRAY_VERTEX] = vertex_array
|
||||
arrays[Mesh.ARRAY_TEX_UV] = GameTheme.UV_ARRAY_HOLD
|
||||
arrays[Mesh.ARRAY_COLOR] = color_array
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays)
|
||||
|
||||
func make_star_mesh(mesh: ArrayMesh, note_center: Vector2, scale:=1.0, angle:=0.0, color_array:=GameTheme.COLOR_ARRAY_STAR):
|
||||
var dim = GameTheme.sprite_size2 * scale * SQRT2
|
||||
var a1 = angle - DEG45
|
||||
var a2 = angle + DEG45
|
||||
var a3 = angle - DEG135
|
||||
var a4 = angle + DEG135
|
||||
var vertex_array = PoolVector2Array([
|
||||
note_center + polar2cartesian(dim, a1), note_center + polar2cartesian(dim, a2),
|
||||
note_center + polar2cartesian(dim, a3), note_center + polar2cartesian(dim, a4)
|
||||
])
|
||||
var arrays = []
|
||||
arrays.resize(Mesh.ARRAY_MAX)
|
||||
arrays[Mesh.ARRAY_VERTEX] = vertex_array
|
||||
arrays[Mesh.ARRAY_TEX_UV] = GameTheme.UV_ARRAY_STAR
|
||||
arrays[Mesh.ARRAY_COLOR] = color_array
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays)
|
||||
|
||||
#func make_arrow_mesh(mesh: ArrayMesh, vertex_array, color_array = GameTheme.COLOR_ARRAY_TAP):
|
||||
# var arrays = []
|
||||
# arrays.resize(Mesh.ARRAY_MAX)
|
||||
# arrays[Mesh.ARRAY_VERTEX] = vertex_array
|
||||
# arrays[Mesh.ARRAY_TEX_UV] = UV_ARRAY_ARROW
|
||||
# arrays[Mesh.ARRAY_COLOR] = color_array
|
||||
# mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays)
|
||||
|
||||
|
||||
const slide_arrows_per_unit_length := 10
|
||||
func make_slide_trail_mesh(note) -> ArrayMesh:
|
||||
# Generates a mesh centered around origin. Make sure the MeshInstance2D that draws this is centered on the screen.
|
||||
var mesh = ArrayMesh.new()
|
||||
var arrays = []
|
||||
arrays.resize(Mesh.ARRAY_MAX)
|
||||
var vertices := PoolVector2Array()
|
||||
var uvs := PoolVector2Array()
|
||||
var colors := PoolColorArray()
|
||||
var size := GameTheme.sprite_size2 * sqrt(2)
|
||||
var color := GameTheme.COLOR_DOUBLE_SLIDE if note.double_hit else GameTheme.COLOR_SLIDE
|
||||
|
||||
match note.get_points():
|
||||
[var positions, var angles]:
|
||||
var trail_length : int = len(positions)
|
||||
vertices.resize(3*trail_length)
|
||||
uvs.resize(3*trail_length)
|
||||
colors.resize(3*trail_length)
|
||||
for i in trail_length:
|
||||
var u = GameTheme.UV_ARRAY_SLIDE_ARROW if i%3 else GameTheme.UV_ARRAY_SLIDE_ARROW2
|
||||
for j in 3:
|
||||
uvs[i*3+j] = u[j]
|
||||
colors[i*3+j] = Color(color.r, color.g, color.b, (1.0+float(i))/float(trail_length))
|
||||
var angle : float = angles[i]
|
||||
var offset : Vector2 = positions[i] * GameTheme.receptor_ring_radius
|
||||
vertices[i*3] = offset
|
||||
vertices[i*3+1] = offset + polar2cartesian(size, angle+PI*0.75)
|
||||
vertices[i*3+2] = offset + polar2cartesian(size, angle-PI*0.75)
|
||||
|
||||
arrays[Mesh.ARRAY_VERTEX] = vertices
|
||||
arrays[Mesh.ARRAY_TEX_UV] = uvs
|
||||
arrays[Mesh.ARRAY_COLOR] = colors
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
||||
return mesh
|
||||
|
||||
#----------------------------------------------------------------------------------------------------------------------------------------------
|
||||
func make_judgement_column(judgement, column: int):
|
||||
|
@ -313,97 +164,109 @@ func check_hold_release(col):
|
|||
do_hold_release(note) # Separate function since there's no need to 'consume' releases
|
||||
|
||||
#----------------------------------------------------------------------------------------------------------------------------------------------
|
||||
const arr_div := Vector3(2.0, float(Rules.COLS), TAU)
|
||||
func draw_note(mesh, note, position, t):
|
||||
var output = null
|
||||
var scale := 1.0
|
||||
|
||||
if position < GameTheme.INNER_NOTE_CIRCLE_RATIO:
|
||||
scale *= position/GameTheme.INNER_NOTE_CIRCLE_RATIO
|
||||
position = GameTheme.INNER_NOTE_CIRCLE_RATIO
|
||||
|
||||
var note_center = (GameTheme.RADIAL_UNIT_VECTORS[note.column] * position * GameTheme.receptor_ring_radius)
|
||||
var color: PoolColorArray
|
||||
|
||||
match note.type:
|
||||
Note.NOTE_TAP:
|
||||
color = GameTheme.color_array_tap(clamp((note.time_death-t)/Note.DEATH_DELAY, 0.0, 1.0), note.double_hit)
|
||||
RadialMeshTools.make_tap_mesh(mesh, note_center, scale, color)
|
||||
|
||||
Note.NOTE_STAR:
|
||||
color = GameTheme.color_array_star(clamp((note.time_death-t)/Note.DEATH_DELAY, 0.0, 1.0), note.double_hit)
|
||||
var angle = fmod(t/note.duration, 1.0)*TAU
|
||||
RadialMeshTools.make_star_mesh(mesh, note_center, scale, angle, color)
|
||||
|
||||
Note.NOTE_HOLD:
|
||||
if note.is_held:
|
||||
position = (t+GameTheme.note_forecast_beats-note.time_release)/GameTheme.note_forecast_beats
|
||||
color = GameTheme.COLOR_ARRAY_HOLD_HELD
|
||||
note_center = GameTheme.RADIAL_UNIT_VECTORS[note.column] * GameTheme.receptor_ring_radius * max(position, 1.0)
|
||||
elif position > 1.0:
|
||||
color = GameTheme.COLOR_ARRAY_DOUBLE_MISS_8 if note.double_hit else GameTheme.COLOR_ARRAY_HOLD_MISS
|
||||
if note.time_released != INF:
|
||||
position = (t+GameTheme.note_forecast_beats-note.time_released)/GameTheme.note_forecast_beats
|
||||
note_center = GameTheme.RADIAL_UNIT_VECTORS[note.column] * GameTheme.receptor_ring_radius * position
|
||||
else:
|
||||
color = GameTheme.COLOR_ARRAY_DOUBLE_8 if note.double_hit else GameTheme.COLOR_ARRAY_HOLD
|
||||
var position_rel : float = (t+GameTheme.note_forecast_beats-note.time_release)/GameTheme.note_forecast_beats
|
||||
if position_rel > 0:
|
||||
var note_rel_center := (GameTheme.RADIAL_UNIT_VECTORS[note.column] * position_rel * GameTheme.receptor_ring_radius)
|
||||
output = [position_rel, note.column]
|
||||
if position_rel < GameTheme.INNER_NOTE_CIRCLE_RATIO:
|
||||
position_rel = GameTheme.INNER_NOTE_CIRCLE_RATIO
|
||||
var note_center_rel = (GameTheme.RADIAL_UNIT_VECTORS[note.column] * position_rel * GameTheme.receptor_ring_radius)
|
||||
RadialMeshTools.make_hold_mesh(mesh, note_center, note_center_rel, scale, GameTheme.RADIAL_COL_ANGLES[note.column], color)
|
||||
|
||||
Note.NOTE_SLIDE:
|
||||
var trail_alpha := 1.0
|
||||
if position < GameTheme.INNER_NOTE_CIRCLE_RATIO:
|
||||
trail_alpha = 0.0
|
||||
elif position < 1.0:
|
||||
trail_alpha = min(1.0, (position-GameTheme.INNER_NOTE_CIRCLE_RATIO)/(1-GameTheme.INNER_NOTE_CIRCLE_RATIO*2))
|
||||
else:
|
||||
var trail_progress : float = clamp((t - note.time_hit - GameTheme.SLIDE_DELAY)/(note.duration - GameTheme.SLIDE_DELAY), 0.0, 1.0)
|
||||
var star_pos : Vector2 = note.get_position(trail_progress) * GameTheme.receptor_ring_radius
|
||||
var star_angle : float = note.get_angle(trail_progress)
|
||||
RadialMeshTools.make_star_mesh(mesh, star_pos, 1.33, star_angle)
|
||||
if note.progress != INF:
|
||||
slide_trail_mesh_instances[note.slide_id].material.set_shader_param('trail_progress', note.progress)
|
||||
if t > note.time_release:
|
||||
trail_alpha = max(1 - (t - note.time_release)/Note.DEATH_DELAY, 0.0)
|
||||
slide_trail_mesh_instances[note.slide_id].material.set_shader_param('base_alpha', trail_alpha*GameTheme.slide_trail_alpha) # TODO: somehow factor this out?
|
||||
|
||||
return output
|
||||
|
||||
|
||||
#----------------------------------------------------------------------------------------------------------------------------------------------
|
||||
const arr_div := Vector3(2.0, float(Rules.COLS), TAU) # Scaling factors to avoid LDR processing clipping our values
|
||||
func _draw():
|
||||
var mesh := ArrayMesh.new()
|
||||
var noteline_data : Image = noteline_array_image.get_rect(Rect2(0, 0, 16, 16))
|
||||
noteline_data.lock()
|
||||
var i := 0
|
||||
var j := 0
|
||||
var hit_spots := []
|
||||
var release_spots := []
|
||||
|
||||
for note in active_notes:
|
||||
var position : float = (t+GameTheme.note_forecast_beats-note.time_hit)/GameTheme.note_forecast_beats
|
||||
var scale := 1.0
|
||||
|
||||
if note.hittable:
|
||||
noteline_data.set_pixel(
|
||||
i%16, i/16, Color(
|
||||
position/arr_div.x,
|
||||
float(note.column)/arr_div.y,
|
||||
GameTheme.RADIAL_COL_ANGLES[note.column]/arr_div.z
|
||||
)
|
||||
)
|
||||
i += 1
|
||||
hit_spots.append([position, note.column])
|
||||
var release = draw_note(mesh, note, position, t)
|
||||
if release:
|
||||
release_spots.append(release)
|
||||
|
||||
if position < GameTheme.INNER_NOTE_CIRCLE_RATIO:
|
||||
scale *= position/GameTheme.INNER_NOTE_CIRCLE_RATIO
|
||||
position = GameTheme.INNER_NOTE_CIRCLE_RATIO
|
||||
|
||||
var note_center = (GameTheme.RADIAL_UNIT_VECTORS[note.column] * position * GameTheme.receptor_ring_radius)
|
||||
var color: PoolColorArray
|
||||
match note.type:
|
||||
Note.NOTE_TAP:
|
||||
color = GameTheme.color_array_tap(clamp((note.time_death-t)/Note.DEATH_DELAY, 0.0, 1.0), note.double_hit)
|
||||
make_tap_mesh(mesh, note_center, scale, color)
|
||||
Note.NOTE_STAR:
|
||||
color = GameTheme.color_array_star(clamp((note.time_death-t)/Note.DEATH_DELAY, 0.0, 1.0), note.double_hit)
|
||||
var angle = fmod(t/note.duration, 1.0)*TAU
|
||||
make_star_mesh(mesh, note_center, scale, angle, color)
|
||||
Note.NOTE_HOLD:
|
||||
if note.is_held:
|
||||
position = (t+GameTheme.note_forecast_beats-note.time_release)/GameTheme.note_forecast_beats
|
||||
color = GameTheme.COLOR_ARRAY_HOLD_HELD
|
||||
note_center = GameTheme.RADIAL_UNIT_VECTORS[note.column] * GameTheme.receptor_ring_radius * max(position, 1.0)
|
||||
elif position > 1.0:
|
||||
color = GameTheme.COLOR_ARRAY_DOUBLE_MISS_8 if note.double_hit else GameTheme.COLOR_ARRAY_HOLD_MISS
|
||||
if note.time_released != INF:
|
||||
position = (t+GameTheme.note_forecast_beats-note.time_released)/GameTheme.note_forecast_beats
|
||||
note_center = GameTheme.RADIAL_UNIT_VECTORS[note.column] * GameTheme.receptor_ring_radius * position
|
||||
else:
|
||||
color = GameTheme.COLOR_ARRAY_DOUBLE_8 if note.double_hit else GameTheme.COLOR_ARRAY_HOLD
|
||||
var position_rel : float = (t+GameTheme.note_forecast_beats-note.time_release)/GameTheme.note_forecast_beats
|
||||
if position_rel > 0:
|
||||
var note_rel_center := (GameTheme.RADIAL_UNIT_VECTORS[note.column] * position_rel * GameTheme.receptor_ring_radius)
|
||||
noteline_data.set_pixel(
|
||||
j%16, 15, Color(
|
||||
position_rel/arr_div.x,
|
||||
float(note.column)/arr_div.y,
|
||||
GameTheme.RADIAL_COL_ANGLES[note.column]/arr_div.z
|
||||
)
|
||||
)
|
||||
j += 1
|
||||
if position_rel < GameTheme.INNER_NOTE_CIRCLE_RATIO:
|
||||
position_rel = GameTheme.INNER_NOTE_CIRCLE_RATIO
|
||||
var note_center_rel = (GameTheme.RADIAL_UNIT_VECTORS[note.column] * position_rel * GameTheme.receptor_ring_radius)
|
||||
make_hold_mesh(mesh, note_center, note_center_rel, scale, GameTheme.RADIAL_COL_ANGLES[note.column], color)
|
||||
Note.NOTE_SLIDE:
|
||||
var trail_alpha := 1.0
|
||||
if position < GameTheme.INNER_NOTE_CIRCLE_RATIO:
|
||||
trail_alpha = 0.0
|
||||
elif position < 1.0:
|
||||
trail_alpha = min(1.0, (position-GameTheme.INNER_NOTE_CIRCLE_RATIO)/(1-GameTheme.INNER_NOTE_CIRCLE_RATIO*2))
|
||||
else:
|
||||
var trail_progress : float = clamp((t - note.time_hit - GameTheme.SLIDE_DELAY)/(note.duration - GameTheme.SLIDE_DELAY), 0.0, 1.0)
|
||||
var star_pos : Vector2 = note.get_position(trail_progress) * GameTheme.receptor_ring_radius
|
||||
var star_angle : float = note.get_angle(trail_progress)
|
||||
make_star_mesh(mesh, star_pos, 1.33, star_angle)
|
||||
if note.progress != INF:
|
||||
slide_trail_mesh_instances[note.slide_id].material.set_shader_param('trail_progress', note.progress)
|
||||
if t > note.time_release:
|
||||
trail_alpha = max(1 - (t - note.time_release)/Note.DEATH_DELAY, 0.0)
|
||||
slide_trail_mesh_instances[note.slide_id].material.set_shader_param('base_alpha', trail_alpha*GameTheme.slide_trail_alpha)
|
||||
# In the absense of shader uniform arrays, we have to send our data via a texture
|
||||
var noteline_data : Image = noteline_array_image.get_rect(Rect2(0, 0, 16, 16))
|
||||
|
||||
noteline_data.lock()
|
||||
var i := 0
|
||||
for spot in hit_spots:
|
||||
noteline_data.set_pixel(i%16, i/16, Color(spot[0]/arr_div.x, float(spot[1])/arr_div.y, GameTheme.RADIAL_COL_ANGLES[spot[1]]/arr_div.z))
|
||||
i += 1
|
||||
i = 0
|
||||
for spot in release_spots:
|
||||
noteline_data.set_pixel(i%16, 15, Color(spot[0]/arr_div.x, float(spot[1])/arr_div.y, GameTheme.RADIAL_COL_ANGLES[spot[1]]/arr_div.z))
|
||||
i += 1
|
||||
noteline_data.unlock()
|
||||
|
||||
var noteline_data_tex := ImageTexture.new()
|
||||
noteline_data_tex.create_from_image(noteline_data, 0)
|
||||
notelines.set_texture(noteline_data_tex)
|
||||
|
||||
# The mesh that we've added all our note vertices and UVs to
|
||||
meshinstance.set_mesh(mesh)
|
||||
|
||||
# Another mesh for judgement texts
|
||||
var textmesh := ArrayMesh.new()
|
||||
for text in active_judgement_texts:
|
||||
make_judgement_text(textmesh, TextJudgement[text.judgement], text.col, (t-text.time)/GameTheme.judge_text_duration)
|
||||
JudgeText.set_mesh(textmesh)
|
||||
RadialMeshTools.make_judgement_text(textmesh, RadialMeshTools.TextJudgement[text.judgement], text.col, (t-text.time)/GameTheme.judge_text_duration)
|
||||
judgeText.set_mesh(textmesh)
|
||||
|
||||
|
||||
func _input(event):
|
||||
|
@ -435,15 +298,16 @@ func _input(event):
|
|||
func _init():
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
|
||||
GameTheme.init_radial_values()
|
||||
make_text_UVs()
|
||||
initialise_scores()
|
||||
|
||||
|
||||
func set_time(seconds: float):
|
||||
var msecs = OS.get_ticks_msec()
|
||||
time_zero_msec = msecs - (seconds * 1000)
|
||||
time = seconds
|
||||
t = game_time(time)
|
||||
|
||||
|
||||
func make_noteline_mesh(vertices := 32) -> ArrayMesh:
|
||||
assert(vertices > 3)
|
||||
var rec_scale1 = (float(screen_height)/float(GameTheme.receptor_ring_radius))*0.5
|
||||
|
@ -468,6 +332,7 @@ func make_noteline_mesh(vertices := 32) -> ArrayMesh:
|
|||
mesh_playfield.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_FAN, arrays)
|
||||
return mesh_playfield
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
notelines.set_mesh(make_noteline_mesh())
|
||||
|
@ -484,6 +349,7 @@ func _ready():
|
|||
meshinstance.material.set_shader_param('screen_size', get_viewport().get_size())
|
||||
meshinstance.set_texture(GameTheme.tex_notes)
|
||||
|
||||
|
||||
func load_track(song_key: String, difficulty_key: String):
|
||||
self.song_key = song_key
|
||||
set_time(-3.0)
|
||||
|
@ -499,42 +365,50 @@ func load_track(song_key: String, difficulty_key: String):
|
|||
sync_offset_audio = data.audio_offsets[0]
|
||||
sync_offset_video = data.video_offsets[0]
|
||||
var videostream = FileLoader.load_video('songs/' + data.filepath.rstrip('/') + '/' + data.video_filelist[0])
|
||||
MusicPlayer.set_stream(FileLoader.load_ogg('songs/' + data.filepath.rstrip('/') + '/' + data.audio_filelist[0]))
|
||||
VideoPlayer.set_stream(videostream)
|
||||
musicPlayer.set_stream(FileLoader.load_ogg('songs/' + data.filepath.rstrip('/') + '/' + data.audio_filelist[0]))
|
||||
videoPlayer.set_stream(videostream)
|
||||
# all_notes = FileLoader.Test.stress_pattern()
|
||||
|
||||
Note.process_note_list(all_notes, false)
|
||||
for note in all_notes:
|
||||
if note.type == Note.NOTE_SLIDE:
|
||||
slide_trail_meshes[note.slide_id] = make_slide_trail_mesh(note)
|
||||
slide_trail_meshes[note.slide_id] = RadialMeshTools.make_slide_trail_mesh(note)
|
||||
|
||||
initialise_scores() # Remove old score
|
||||
|
||||
|
||||
func stop():
|
||||
MusicPlayer.stop()
|
||||
VideoPlayer.stop()
|
||||
musicPlayer.stop()
|
||||
videoPlayer.stop()
|
||||
# running = false
|
||||
next_note_to_load = 10000000 # Hacky but whatever
|
||||
|
||||
|
||||
func intro_click():
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, GameTheme.snd_count_in)
|
||||
|
||||
|
||||
func get_realtime_precise() -> float:
|
||||
# Usually we only update the gametime once per process loop, but for input callbacks it's good to have msec precision
|
||||
return (OS.get_ticks_msec() - time_zero_msec)/1000.0
|
||||
|
||||
|
||||
func game_time(realtime: float) -> float:
|
||||
return realtime * bpm / 60.0
|
||||
|
||||
|
||||
func real_time(gametime: float) -> float:
|
||||
return gametime * 60.0 / bpm
|
||||
|
||||
|
||||
func video_start_time() -> float:
|
||||
return -sync_offset_video
|
||||
|
||||
|
||||
func audio_start_time() -> float:
|
||||
return -sync_offset_audio
|
||||
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
var timers_set := false
|
||||
func _process(delta):
|
||||
|
@ -563,14 +437,14 @@ func _process(delta):
|
|||
timer.connect('timeout', timer, 'queue_free')
|
||||
|
||||
var vt_delta := time - video_start_time()
|
||||
if (0.0 <= vt_delta) and (vt_delta < 3.0) and not VideoPlayer.is_playing():
|
||||
VideoPlayer.play()
|
||||
VideoPlayer.set_stream_position(vt_delta)
|
||||
if (0.0 <= vt_delta) and (vt_delta < 3.0) and not videoPlayer.is_playing():
|
||||
videoPlayer.play()
|
||||
videoPlayer.set_stream_position(vt_delta)
|
||||
var at_delta := time - audio_start_time()
|
||||
if (0.0 <= at_delta) and (at_delta < 3.0) and not MusicPlayer.is_playing():
|
||||
# MusicPlayer.play()
|
||||
# MusicPlayer.seek(at_delta)
|
||||
MusicPlayer.play(at_delta)
|
||||
if (0.0 <= at_delta) and (at_delta < 3.0) and not musicPlayer.is_playing():
|
||||
# musicPlayer.play()
|
||||
# musicPlayer.seek(at_delta)
|
||||
musicPlayer.play(at_delta)
|
||||
|
||||
# Clean out expired notes
|
||||
var miss_time: float = Rules.JUDGEMENT_TIMES_POST[-1] * bpm/60.0
|
||||
|
@ -583,7 +457,7 @@ func _process(delta):
|
|||
scores[Note.RELEASE_SCORE_TYPES[Note.NOTE_HOLD]][3] += 1
|
||||
make_judgement_column(3, note.column)
|
||||
Note.NOTE_SLIDE:
|
||||
SlideTrailHandler.remove_child(slide_trail_mesh_instances[note.slide_id])
|
||||
slideTrailHandler.remove_child(slide_trail_mesh_instances[note.slide_id])
|
||||
slide_trail_mesh_instances.erase(note.slide_id)
|
||||
var idx = active_slide_trails.find(note)
|
||||
if idx > -1:
|
||||
|
@ -630,14 +504,14 @@ func _process(delta):
|
|||
meshi.material.set_shader_param('trail_progress', 0.0)
|
||||
meshi.set_texture(GameTheme.tex_slide_arrow)
|
||||
slide_trail_mesh_instances[note.slide_id] = meshi
|
||||
SlideTrailHandler.add_child(meshi)
|
||||
slideTrailHandler.add_child(meshi)
|
||||
|
||||
next_note_to_load += 1
|
||||
|
||||
if (
|
||||
next_note_to_load >= len(all_notes)
|
||||
and not VideoPlayer.is_playing()
|
||||
and not MusicPlayer.is_playing()
|
||||
and not videoPlayer.is_playing()
|
||||
and not musicPlayer.is_playing()
|
||||
and active_notes.empty()
|
||||
and active_judgement_texts.empty()
|
||||
and slide_trail_mesh_instances.empty()
|
||||
|
@ -650,7 +524,7 @@ func _process(delta):
|
|||
# Redraw
|
||||
meshinstance.material.set_shader_param('screen_size', get_viewport().get_size())
|
||||
update()
|
||||
Painter.update()
|
||||
painter.update()
|
||||
|
||||
|
||||
func _on_InputHandler_column_pressed(column) -> void:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
extends Control
|
||||
|
||||
onready var Viewport := get_node(@'../NoteHandler/Viewport')
|
||||
onready var viewport := $'%viewport'
|
||||
|
||||
func _draw():
|
||||
draw_texture_rect(Viewport.get_texture(), Rect2(Vector2.ZERO, rect_size), false)
|
||||
# texture = Viewport.get_texture()
|
||||
draw_texture_rect(viewport.get_texture(), Rect2(Vector2.ZERO, rect_size), false)
|
||||
# texture = viewport.get_texture()
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
extends Viewport
|
||||
|
||||
onready var square := $'%square'
|
||||
onready var root := $'/root'
|
||||
onready var base_height = 1080.0
|
||||
|
||||
var container_size := Vector2(1080, 1080)
|
||||
|
@ -10,17 +12,15 @@ func set_render_scale(scale: Vector2):
|
|||
$Center.position = size * 0.5
|
||||
$Center.scale = size/base_height
|
||||
|
||||
func update_size() -> void:
|
||||
var winscale = min(root.size.x, root.size.y)/base_height
|
||||
container_size = square.rect_size * winscale
|
||||
set_render_scale(scale)
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
Settings.connect('subsampling_changed', self, 'set_render_scale')
|
||||
set_render_scale(Settings.subsampling)
|
||||
_on_Square_item_rect_changed()
|
||||
|
||||
|
||||
onready var Square := $'../../'
|
||||
onready var Root := $'/root'
|
||||
onready var Main := $'/root/main'
|
||||
func _on_Square_item_rect_changed() -> void:
|
||||
var winscale = min(Root.size.x, Root.size.y)/base_height
|
||||
container_size = Square.rect_size * winscale
|
||||
set_render_scale(scale)
|
||||
square.connect('item_rect_changed', self, 'update_size')
|
||||
root.connect('size_changed', self, 'update_size')
|
||||
scale = Settings.subsampling
|
||||
update_size()
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
extends Node
|
||||
|
||||
const SQRT2 := sqrt(2)
|
||||
const DEG45 := deg2rad(45.0)
|
||||
const DEG90 := deg2rad(90.0)
|
||||
const DEG135 := deg2rad(135.0)
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
# Helper functions to generate meshes from vertex arrays
|
||||
static func make_tap_mesh(mesh: ArrayMesh, note_center: Vector2, scale:=1.0, color_array:=GameTheme.COLOR_ARRAY_TAP):
|
||||
var dim = GameTheme.sprite_size2 * scale
|
||||
var vertex_array = PoolVector2Array([note_center + Vector2(-dim, -dim), note_center + Vector2(dim, -dim), note_center + Vector2(-dim, dim), note_center + Vector2(dim, dim)])
|
||||
var arrays = []
|
||||
arrays.resize(Mesh.ARRAY_MAX)
|
||||
arrays[Mesh.ARRAY_VERTEX] = vertex_array
|
||||
arrays[Mesh.ARRAY_TEX_UV] = GameTheme.UV_ARRAY_TAP
|
||||
arrays[Mesh.ARRAY_COLOR] = color_array
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays)
|
||||
|
||||
static func make_hold_mesh(mesh: ArrayMesh, note_center: Vector2, note_center_rel: Vector2, scale:=1.0, angle:=0.0, color_array = GameTheme.COLOR_ARRAY_HOLD):
|
||||
var dim = GameTheme.sprite_size2 * scale
|
||||
var dim2 = dim * SQRT2
|
||||
var a1 = angle - DEG45
|
||||
var a2 = angle + DEG45
|
||||
var a3 = angle - DEG90
|
||||
var a4 = angle + DEG90
|
||||
var a5 = angle - DEG135
|
||||
var a6 = angle + DEG135
|
||||
var vertex_array = PoolVector2Array([
|
||||
note_center + polar2cartesian(dim2, a1), note_center + polar2cartesian(dim2, a2),
|
||||
note_center + polar2cartesian(dim, a3), note_center + polar2cartesian(dim, a4),
|
||||
note_center_rel + polar2cartesian(dim, a3), note_center_rel + polar2cartesian(dim, a4),
|
||||
note_center_rel + polar2cartesian(dim2, a5), note_center_rel + polar2cartesian(dim2, a6)
|
||||
])
|
||||
var arrays = []
|
||||
arrays.resize(Mesh.ARRAY_MAX)
|
||||
arrays[Mesh.ARRAY_VERTEX] = vertex_array
|
||||
arrays[Mesh.ARRAY_TEX_UV] = GameTheme.UV_ARRAY_HOLD
|
||||
arrays[Mesh.ARRAY_COLOR] = color_array
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays)
|
||||
|
||||
static func make_star_mesh(mesh: ArrayMesh, note_center: Vector2, scale:=1.0, angle:=0.0, color_array:=GameTheme.COLOR_ARRAY_STAR):
|
||||
var dim = GameTheme.sprite_size2 * scale * SQRT2
|
||||
var a1 = angle - DEG45
|
||||
var a2 = angle + DEG45
|
||||
var a3 = angle - DEG135
|
||||
var a4 = angle + DEG135
|
||||
var vertex_array = PoolVector2Array([
|
||||
note_center + polar2cartesian(dim, a1), note_center + polar2cartesian(dim, a2),
|
||||
note_center + polar2cartesian(dim, a3), note_center + polar2cartesian(dim, a4)
|
||||
])
|
||||
var arrays = []
|
||||
arrays.resize(Mesh.ARRAY_MAX)
|
||||
arrays[Mesh.ARRAY_VERTEX] = vertex_array
|
||||
arrays[Mesh.ARRAY_TEX_UV] = GameTheme.UV_ARRAY_STAR
|
||||
arrays[Mesh.ARRAY_COLOR] = color_array
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays)
|
||||
|
||||
#func make_arrow_mesh(mesh: ArrayMesh, vertex_array, color_array = GameTheme.COLOR_ARRAY_TAP):
|
||||
# var arrays = []
|
||||
# arrays.resize(Mesh.ARRAY_MAX)
|
||||
# arrays[Mesh.ARRAY_VERTEX] = vertex_array
|
||||
# arrays[Mesh.ARRAY_TEX_UV] = UV_ARRAY_ARROW
|
||||
# arrays[Mesh.ARRAY_COLOR] = color_array
|
||||
# mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays)
|
||||
|
||||
|
||||
const slide_arrows_per_unit_length := 10
|
||||
static func make_slide_trail_mesh(note) -> ArrayMesh:
|
||||
# Generates a mesh centered around origin. Make sure the MeshInstance2D that draws this is centered on the screen.
|
||||
var mesh = ArrayMesh.new()
|
||||
var arrays = []
|
||||
arrays.resize(Mesh.ARRAY_MAX)
|
||||
var vertices := PoolVector2Array()
|
||||
var uvs := PoolVector2Array()
|
||||
var colors := PoolColorArray()
|
||||
var size := GameTheme.sprite_size2 * sqrt(2)
|
||||
var color := GameTheme.COLOR_DOUBLE_SLIDE if note.double_hit else GameTheme.COLOR_SLIDE
|
||||
|
||||
match note.get_points():
|
||||
[var positions, var angles]:
|
||||
var trail_length : int = len(positions)
|
||||
vertices.resize(3*trail_length)
|
||||
uvs.resize(3*trail_length)
|
||||
colors.resize(3*trail_length)
|
||||
for i in trail_length:
|
||||
var idx = (trail_length-i-1)*3 # We want the earliest ones to be drawn last so that loops/sharp bends will have the first pass on top
|
||||
var u = GameTheme.UV_ARRAY_SLIDE_ARROW if i%3 else GameTheme.UV_ARRAY_SLIDE_ARROW2
|
||||
for j in 3:
|
||||
uvs[idx+j] = u[j]
|
||||
colors[idx+j] = Color(color.r, color.g, color.b, (1.0+float(i))/float(trail_length))
|
||||
var angle : float = angles[i]
|
||||
var offset : Vector2 = positions[i] * GameTheme.receptor_ring_radius
|
||||
vertices[idx] = offset
|
||||
vertices[idx+1] = offset + polar2cartesian(size, angle+PI*0.75)
|
||||
vertices[idx+2] = offset + polar2cartesian(size, angle-PI*0.75)
|
||||
|
||||
arrays[Mesh.ARRAY_VERTEX] = vertices
|
||||
arrays[Mesh.ARRAY_TEX_UV] = uvs
|
||||
arrays[Mesh.ARRAY_COLOR] = colors
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
||||
return mesh
|
||||
|
||||
|
||||
|
||||
#----------------------------------------------------------------------------------------------------------------------------------------------
|
||||
# Text UVs
|
||||
|
||||
# Old dynamic code:
|
||||
#var text_UV_arrays := []
|
||||
#func make_text_UV(row: int, column: int) -> PoolVector2Array:
|
||||
# return PoolVector2Array([Vector2(column/4.0, row/8.0), Vector2((column+1)/4.0, row/8.0), Vector2(column/4.0, (row+1)/8.0), Vector2((column+1)/4.0, (row+1)/8.0)])
|
||||
#func make_text_UVs():
|
||||
# for row in 8:
|
||||
# for column in 4:
|
||||
# text_UV_arrays.append(make_text_UV(row, column))
|
||||
|
||||
# This is replaced by a quick hacky python codegen:
|
||||
#>>> def make_text_UV(row, column):
|
||||
#... return f'PoolVector2Array([Vector2({column}/4.0, {row}/8.0), Vector2({column+1}/4.0, {row}/8.0), Vector2({column}/4.0, {row+1}/8.0), Vector2({column+1}/4.0, {row+1}/8.0)])'
|
||||
#>>> for row in range(8):
|
||||
#... for col in range(4):
|
||||
#... print(make_text_UV(row, col) + ',')
|
||||
|
||||
const text_UV_arrays := [
|
||||
PoolVector2Array([Vector2(0/4.0, 0/8.0), Vector2(1/4.0, 0/8.0), Vector2(0/4.0, 1/8.0), Vector2(1/4.0, 1/8.0)]),
|
||||
PoolVector2Array([Vector2(1/4.0, 0/8.0), Vector2(2/4.0, 0/8.0), Vector2(1/4.0, 1/8.0), Vector2(2/4.0, 1/8.0)]),
|
||||
PoolVector2Array([Vector2(2/4.0, 0/8.0), Vector2(3/4.0, 0/8.0), Vector2(2/4.0, 1/8.0), Vector2(3/4.0, 1/8.0)]),
|
||||
PoolVector2Array([Vector2(3/4.0, 0/8.0), Vector2(4/4.0, 0/8.0), Vector2(3/4.0, 1/8.0), Vector2(4/4.0, 1/8.0)]),
|
||||
PoolVector2Array([Vector2(0/4.0, 1/8.0), Vector2(1/4.0, 1/8.0), Vector2(0/4.0, 2/8.0), Vector2(1/4.0, 2/8.0)]),
|
||||
PoolVector2Array([Vector2(1/4.0, 1/8.0), Vector2(2/4.0, 1/8.0), Vector2(1/4.0, 2/8.0), Vector2(2/4.0, 2/8.0)]),
|
||||
PoolVector2Array([Vector2(2/4.0, 1/8.0), Vector2(3/4.0, 1/8.0), Vector2(2/4.0, 2/8.0), Vector2(3/4.0, 2/8.0)]),
|
||||
PoolVector2Array([Vector2(3/4.0, 1/8.0), Vector2(4/4.0, 1/8.0), Vector2(3/4.0, 2/8.0), Vector2(4/4.0, 2/8.0)]),
|
||||
PoolVector2Array([Vector2(0/4.0, 2/8.0), Vector2(1/4.0, 2/8.0), Vector2(0/4.0, 3/8.0), Vector2(1/4.0, 3/8.0)]),
|
||||
PoolVector2Array([Vector2(1/4.0, 2/8.0), Vector2(2/4.0, 2/8.0), Vector2(1/4.0, 3/8.0), Vector2(2/4.0, 3/8.0)]),
|
||||
PoolVector2Array([Vector2(2/4.0, 2/8.0), Vector2(3/4.0, 2/8.0), Vector2(2/4.0, 3/8.0), Vector2(3/4.0, 3/8.0)]),
|
||||
PoolVector2Array([Vector2(3/4.0, 2/8.0), Vector2(4/4.0, 2/8.0), Vector2(3/4.0, 3/8.0), Vector2(4/4.0, 3/8.0)]),
|
||||
PoolVector2Array([Vector2(0/4.0, 3/8.0), Vector2(1/4.0, 3/8.0), Vector2(0/4.0, 4/8.0), Vector2(1/4.0, 4/8.0)]),
|
||||
PoolVector2Array([Vector2(1/4.0, 3/8.0), Vector2(2/4.0, 3/8.0), Vector2(1/4.0, 4/8.0), Vector2(2/4.0, 4/8.0)]),
|
||||
PoolVector2Array([Vector2(2/4.0, 3/8.0), Vector2(3/4.0, 3/8.0), Vector2(2/4.0, 4/8.0), Vector2(3/4.0, 4/8.0)]),
|
||||
PoolVector2Array([Vector2(3/4.0, 3/8.0), Vector2(4/4.0, 3/8.0), Vector2(3/4.0, 4/8.0), Vector2(4/4.0, 4/8.0)]),
|
||||
PoolVector2Array([Vector2(0/4.0, 4/8.0), Vector2(1/4.0, 4/8.0), Vector2(0/4.0, 5/8.0), Vector2(1/4.0, 5/8.0)]),
|
||||
PoolVector2Array([Vector2(1/4.0, 4/8.0), Vector2(2/4.0, 4/8.0), Vector2(1/4.0, 5/8.0), Vector2(2/4.0, 5/8.0)]),
|
||||
PoolVector2Array([Vector2(2/4.0, 4/8.0), Vector2(3/4.0, 4/8.0), Vector2(2/4.0, 5/8.0), Vector2(3/4.0, 5/8.0)]),
|
||||
PoolVector2Array([Vector2(3/4.0, 4/8.0), Vector2(4/4.0, 4/8.0), Vector2(3/4.0, 5/8.0), Vector2(4/4.0, 5/8.0)]),
|
||||
PoolVector2Array([Vector2(0/4.0, 5/8.0), Vector2(1/4.0, 5/8.0), Vector2(0/4.0, 6/8.0), Vector2(1/4.0, 6/8.0)]),
|
||||
PoolVector2Array([Vector2(1/4.0, 5/8.0), Vector2(2/4.0, 5/8.0), Vector2(1/4.0, 6/8.0), Vector2(2/4.0, 6/8.0)]),
|
||||
PoolVector2Array([Vector2(2/4.0, 5/8.0), Vector2(3/4.0, 5/8.0), Vector2(2/4.0, 6/8.0), Vector2(3/4.0, 6/8.0)]),
|
||||
PoolVector2Array([Vector2(3/4.0, 5/8.0), Vector2(4/4.0, 5/8.0), Vector2(3/4.0, 6/8.0), Vector2(4/4.0, 6/8.0)]),
|
||||
PoolVector2Array([Vector2(0/4.0, 6/8.0), Vector2(1/4.0, 6/8.0), Vector2(0/4.0, 7/8.0), Vector2(1/4.0, 7/8.0)]),
|
||||
PoolVector2Array([Vector2(1/4.0, 6/8.0), Vector2(2/4.0, 6/8.0), Vector2(1/4.0, 7/8.0), Vector2(2/4.0, 7/8.0)]),
|
||||
PoolVector2Array([Vector2(2/4.0, 6/8.0), Vector2(3/4.0, 6/8.0), Vector2(2/4.0, 7/8.0), Vector2(3/4.0, 7/8.0)]),
|
||||
PoolVector2Array([Vector2(3/4.0, 6/8.0), Vector2(4/4.0, 6/8.0), Vector2(3/4.0, 7/8.0), Vector2(4/4.0, 7/8.0)]),
|
||||
PoolVector2Array([Vector2(0/4.0, 7/8.0), Vector2(1/4.0, 7/8.0), Vector2(0/4.0, 8/8.0), Vector2(1/4.0, 8/8.0)]),
|
||||
PoolVector2Array([Vector2(1/4.0, 7/8.0), Vector2(2/4.0, 7/8.0), Vector2(1/4.0, 8/8.0), Vector2(2/4.0, 8/8.0)]),
|
||||
PoolVector2Array([Vector2(2/4.0, 7/8.0), Vector2(3/4.0, 7/8.0), Vector2(2/4.0, 8/8.0), Vector2(3/4.0, 8/8.0)]),
|
||||
PoolVector2Array([Vector2(3/4.0, 7/8.0), Vector2(4/4.0, 7/8.0), Vector2(3/4.0, 8/8.0), Vector2(4/4.0, 8/8.0)])
|
||||
]
|
||||
|
||||
enum TextStyle {STRAIGHT=0, ARC=1, ARC_EARLY=2, ARC_LATE=3}
|
||||
enum TextWord {NICE=0, OK=4, NG=8, PERFECT=12, GREAT=16, GOOD=20, ALMOST=24, MISS=28}
|
||||
const TextJudgement := {
|
||||
0: TextWord.PERFECT + TextStyle.ARC,
|
||||
1: TextWord.GREAT + TextStyle.ARC_LATE,
|
||||
-1: TextWord.GREAT + TextStyle.ARC_EARLY,
|
||||
2: TextWord.GOOD + TextStyle.ARC_LATE,
|
||||
-2: TextWord.GOOD + TextStyle.ARC_EARLY,
|
||||
3: TextWord.ALMOST + TextStyle.ARC_LATE,
|
||||
-3: TextWord.ALMOST + TextStyle.ARC_EARLY,
|
||||
'MISS': TextWord.MISS + TextStyle.ARC
|
||||
}
|
||||
const TextJudgementStraight := {
|
||||
0: TextWord.PERFECT + TextStyle.STRAIGHT,
|
||||
1: TextWord.GREAT + TextStyle.STRAIGHT,
|
||||
-1: TextWord.GREAT + TextStyle.STRAIGHT,
|
||||
2: TextWord.GOOD + TextStyle.STRAIGHT,
|
||||
-2: TextWord.GOOD + TextStyle.STRAIGHT,
|
||||
3: TextWord.ALMOST + TextStyle.STRAIGHT,
|
||||
-3: TextWord.ALMOST + TextStyle.STRAIGHT,
|
||||
'MISS': TextWord.MISS + TextStyle.STRAIGHT
|
||||
}
|
||||
|
||||
static func make_text_mesh(mesh: ArrayMesh, text_id: int, pos: Vector2, angle: float, alpha:=1.0, scale:=1.0):
|
||||
var r := GameTheme.judge_text_size2 * scale
|
||||
var vertex_array := PoolVector2Array([
|
||||
pos+polar2cartesian(r, angle+GameTheme.JUDGE_TEXT_ANG2), # TODO: fix this UV/vertex order mess
|
||||
pos+polar2cartesian(r, angle+GameTheme.JUDGE_TEXT_ANG1),
|
||||
pos+polar2cartesian(r, angle+GameTheme.JUDGE_TEXT_ANG4),
|
||||
pos+polar2cartesian(r, angle+GameTheme.JUDGE_TEXT_ANG3)
|
||||
])
|
||||
var arrays = []
|
||||
arrays.resize(Mesh.ARRAY_MAX)
|
||||
arrays[Mesh.ARRAY_VERTEX] = vertex_array
|
||||
arrays[Mesh.ARRAY_TEX_UV] = text_UV_arrays[text_id]
|
||||
arrays[Mesh.ARRAY_COLOR] = GameTheme.color_array_text(alpha)
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLE_STRIP, arrays)
|
||||
|
||||
static func make_judgement_text(mesh: ArrayMesh, text_id: int, col: int, progress:=0.0):
|
||||
make_text_mesh(mesh, text_id,
|
||||
GameTheme.RADIAL_UNIT_VECTORS[col] * GameTheme.receptor_ring_radius * lerp(0.85, 0.85*0.75, progress),
|
||||
GameTheme.RADIAL_COL_ANGLES[col]-PI/2.0, lerp(1.0, 0.0, progress), lerp(1.0, 0.75, progress)
|
||||
)
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
extends Node2D
|
||||
|
||||
var TitleFont := preload("res://assets/MenuTitleFont.tres").duplicate()
|
||||
var ScoreFont := preload("res://assets/MenuScoreFont.tres").duplicate()
|
||||
var titleFont := preload("res://assets/MenuTitleFont.tres").duplicate()
|
||||
var scoreFont := preload("res://assets/MenuScoreFont.tres").duplicate()
|
||||
|
||||
var score = ""
|
||||
var score_sub = ""
|
||||
|
@ -9,16 +9,16 @@ var score_sub = ""
|
|||
var f_scale := 1.0 setget set_f_scale
|
||||
func set_f_scale(value: float) -> void:
|
||||
f_scale = value
|
||||
TitleFont.size = int(round(32*f_scale))
|
||||
TitleFont.outline_size = int(max(round(2*f_scale), 1))
|
||||
ScoreFont.size = int(round(96*f_scale))
|
||||
ScoreFont.outline_size = int(max(round(2*f_scale), 1))
|
||||
titleFont.size = int(round(32*f_scale))
|
||||
titleFont.outline_size = int(max(round(2*f_scale), 1))
|
||||
scoreFont.size = int(round(96*f_scale))
|
||||
scoreFont.outline_size = int(max(round(2*f_scale), 1))
|
||||
|
||||
func draw_string_centered(font, position, string, color := Color.white):
|
||||
draw_string(font, Vector2(position.x - font.get_string_size(string).x/2.0, position.y + font.get_ascent()), string, color)
|
||||
|
||||
func _draw():
|
||||
if score:
|
||||
draw_string_centered(ScoreFont, Vector2(0, 0)*f_scale, score)
|
||||
draw_string_centered(scoreFont, Vector2(0, 0)*f_scale, score)
|
||||
if score_sub:
|
||||
draw_string_centered(TitleFont, Vector2(0, 128)*f_scale, score_sub)
|
||||
draw_string_centered(titleFont, Vector2(0, 128)*f_scale, score_sub)
|
||||
|
|
|
@ -0,0 +1,623 @@
|
|||
#tool
|
||||
extends Control
|
||||
|
||||
onready var noteHandler := $'%noteHandler'
|
||||
onready var receptors := $'%receptors'
|
||||
onready var scoreText := $ScoreText
|
||||
onready var PVMusic := SoundPlayer.music_player_pv
|
||||
|
||||
var f_scale := 1.0 setget set_f_scale
|
||||
func set_f_scale(value: float) -> void:
|
||||
f_scale = value
|
||||
titleFont.size = int(round(32*f_scale))
|
||||
titleFont.outline_size = int(max(round(2*f_scale), 1))
|
||||
genreFont.size = int(round(48*f_scale))
|
||||
genreFont.outline_size = int(max(round(2*f_scale), 1))
|
||||
diffNumFont.size = int(round(36*f_scale))
|
||||
diffNumFont.outline_size = int(max(round(1*f_scale), 1))
|
||||
scoreText.set_f_scale(f_scale)
|
||||
func update_scale() -> void:
|
||||
self.f_scale = min(rect_size.x/1080, rect_size.y/1080)
|
||||
|
||||
var genres = {}
|
||||
|
||||
enum ChartDifficulty {EASY, BASIC, ADV, EXPERT, MASTER}
|
||||
enum MenuMode {SONG_SELECT, CHART_SELECT, OPTIONS, GAMEPLAY, SCORE_SCREEN}
|
||||
|
||||
var menu_mode = MenuMode.SONG_SELECT
|
||||
var menu_mode_prev = MenuMode.SONG_SELECT
|
||||
var menu_mode_prev_fade_timer := 0.0
|
||||
var menu_mode_prev_fade_timer_duration := 0.25
|
||||
var currently_playing := false
|
||||
|
||||
var selected_genre: int = 0
|
||||
var selected_genre_vis: int = 0
|
||||
var selected_genre_delta: float = 0.0 # For floaty display scrolling
|
||||
var target_song_idx: float = 0.0 setget set_target_song_idx
|
||||
var target_song_delta: float = 0.0 # For floaty display scrolling
|
||||
var selected_song_idx: int setget , get_song_idx
|
||||
var selected_song_key: String setget , get_song_key
|
||||
var selected_difficulty = ChartDifficulty.ADV
|
||||
|
||||
func set_target_song_idx(index):
|
||||
target_song_delta -= index - target_song_idx
|
||||
target_song_idx = index
|
||||
|
||||
func get_song_idx() -> int:
|
||||
return int(round(self.target_song_idx + target_song_delta))
|
||||
|
||||
func get_song_key() -> String:
|
||||
var songslist = genres[genres.keys()[selected_genre]]
|
||||
return songslist[int(round(self.target_song_idx)) % len(songslist)]
|
||||
|
||||
var scorescreen_song_key := ''
|
||||
var scorescreen_score_data := {}
|
||||
var scorescreen_datetime := {}
|
||||
var scorescreen_saved := false
|
||||
|
||||
var touch_rects = []
|
||||
|
||||
var titleFont: DynamicFont = preload('res://assets/MenuTitleFont.tres').duplicate()
|
||||
var genreFont: DynamicFont = preload('res://assets/MenuGenreFont.tres').duplicate()
|
||||
var diffNumFont: DynamicFont = preload('res://assets/MenuDiffNumberFont.tres').duplicate()
|
||||
var scoreFont: DynamicFont = preload('res://assets/MenuScoreFont.tres').duplicate()
|
||||
var snd_interact := preload('res://assets/softclap.wav')
|
||||
var snd_error := preload('res://assets/miss.wav')
|
||||
|
||||
export var ease_curve: Curve
|
||||
|
||||
|
||||
class lerp_array extends Resource:
|
||||
var array
|
||||
func _init(array: Array):
|
||||
self.array = array
|
||||
|
||||
func value(index: float):
|
||||
# Only >= 0 for now, but should be fine since it's an arraylike anyway
|
||||
var i := min(int(floor(index)), len(array)-2) # Somewhat hacky - if we pass len(array)-1 as index, it will return lerp(a[-2], a[-1], 1) == a[-1]
|
||||
var f := min(index - i, 1.0)
|
||||
return lerp(array[i], array[i+1], f)
|
||||
|
||||
func len():
|
||||
return len(array)
|
||||
|
||||
|
||||
onready var joypad_monitors = [$JoypadMonitor, $JoypadMonitor2, $JoypadMonitor3]
|
||||
func update_joypad_labels():
|
||||
var num_joypads = len(Input.get_connected_joypads())
|
||||
var num_monitors = len(joypad_monitors)
|
||||
for i in num_monitors:
|
||||
joypad_monitors[i].visible = true
|
||||
if num_monitors > num_joypads:
|
||||
for i in range(num_joypads, num_monitors):
|
||||
joypad_monitors[i].visible = false
|
||||
for i in num_joypads:
|
||||
joypad_monitors[i].set_name(Input.get_joy_name(i))
|
||||
|
||||
func get_rect_center(rect: Rect2) -> Vector2:
|
||||
return rect.position + rect.size*0.5
|
||||
|
||||
func scan_library():
|
||||
var results = FileLoader.scan_library()
|
||||
genres = results.genres
|
||||
|
||||
func save_score() -> int:
|
||||
var data = {'score_data': scorescreen_score_data, 'song_key': scorescreen_song_key}
|
||||
var dt = scorescreen_datetime
|
||||
var filename = 'scores/%04d%02d%02dT%02d%02d%02d.json'%[dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second]
|
||||
match FileLoader.save_json(filename, data):
|
||||
OK:
|
||||
scorescreen_saved = true
|
||||
return OK
|
||||
var err:
|
||||
print_debug('Error saving score file %s'%filename)
|
||||
return err
|
||||
|
||||
func load_score(filename: String):
|
||||
var result = FileLoader.load_json('scores/%s'%filename)
|
||||
if not (result is Dictionary):
|
||||
print('An error occurred while trying to access the chosen score file: ', result)
|
||||
return result
|
||||
var data = {}
|
||||
for key in result.score_data:
|
||||
var value = {}
|
||||
for k2 in result.score_data[key]:
|
||||
if k2 != 'MISS':
|
||||
k2 = int(k2) # Could use something more robust later
|
||||
value[k2] = result.score_data[key][k2]
|
||||
data[int(key)] = value
|
||||
scorescreen_score_data = data
|
||||
scorescreen_song_key = result.song_key
|
||||
scorescreen_saved = true
|
||||
set_menu_mode(MenuMode.SCORE_SCREEN)
|
||||
|
||||
func load_preview():
|
||||
var tmp = self.selected_song_key
|
||||
var data = Library.all_songs[tmp]
|
||||
PVMusic.stop()
|
||||
PVMusic.set_stream(FileLoader.load_ogg('songs/' + data.filepath.rstrip('/') + '/' + data.audio_filelist[0]))
|
||||
PVMusic.play(16*60.0/data.BPM)
|
||||
|
||||
func _ready():
|
||||
scan_library()
|
||||
connect('item_rect_changed', self, 'update_scale')
|
||||
noteHandler.connect('finished_song', self, 'finished_song')
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(delta):
|
||||
target_song_delta -= ease_curve.interpolate(clamp(target_song_delta, -2, 2)*0.5) * 10 * delta
|
||||
if abs(target_song_delta) < 0.02: # Snap
|
||||
target_song_delta = 0.0
|
||||
|
||||
var g_diff = selected_genre - (selected_genre_vis + selected_genre_delta)
|
||||
selected_genre_delta += ease_curve.interpolate(clamp(g_diff, -1, 1)) * 10 * delta
|
||||
if selected_genre_delta > 0.5:
|
||||
selected_genre_delta -= 1.0
|
||||
selected_genre_vis += 1
|
||||
elif selected_genre_delta < -0.5:
|
||||
selected_genre_delta += 1.0
|
||||
selected_genre_vis -= 1
|
||||
if abs(g_diff) < 0.02: # Snap
|
||||
selected_genre_delta = 0.0
|
||||
selected_genre_vis = selected_genre
|
||||
|
||||
menu_mode_prev_fade_timer = max(0.0, menu_mode_prev_fade_timer - delta)
|
||||
update()
|
||||
if (menu_mode == MenuMode.GAMEPLAY) and (menu_mode_prev_fade_timer <= 0.0) and not noteHandler.running:
|
||||
noteHandler.load_track(self.selected_song_key, Library.Song.default_difficulty_keys[selected_difficulty])
|
||||
noteHandler.running = true
|
||||
|
||||
|
||||
func draw_string_centered(font: Font, position: Vector2, string: String, color := GameTheme.COLOR_MENU_TEXT, vcenter := false) -> Vector2:
|
||||
# Draws horizontally centered from the baseline. Can vcenter via ascent but not perfectly reliable.
|
||||
# Returns size of the string.
|
||||
var ss := font.get_string_size(string)
|
||||
var v := -(font.get_descent() - font.get_height()*0.475) if vcenter else 0.0 # This VCentering is a little fudgey but works for our current fonts
|
||||
draw_string(font, Vector2(position.x - ss.x*0.5, position.y + v).round(), string, color)
|
||||
return ss
|
||||
|
||||
func draw_string_ralign(font: Font, position: Vector2, string: String, color := GameTheme.COLOR_MENU_TEXT, vcenter := false) -> Vector2:
|
||||
# Draws from the bottom-right. Can vcenter via ascent but not perfectly reliable.
|
||||
# Returns size of the string.
|
||||
var ss := font.get_string_size(string)
|
||||
var ascent := font.get_ascent() if vcenter else 0.0
|
||||
draw_string(font, Vector2(position.x - ss.x, position.y + ascent*0.5).round(), string, color)
|
||||
return ss
|
||||
|
||||
func draw_songtile(song_key, position, size, title_text:=false, difficulty=selected_difficulty, outline_px:=3.0, disabled:=false):
|
||||
# Draws from top left-corner. Returns Rect2 of the image (not the outline).
|
||||
# Draw difficulty-colored outline
|
||||
if typeof(difficulty) == TYPE_STRING:
|
||||
difficulty = Library.Song.difficulty_key_ids.get(difficulty, 0)
|
||||
|
||||
outline_px *= f_scale
|
||||
|
||||
var song_diffs = Library.all_songs[song_key]['chart_difficulties']
|
||||
if not (Library.Song.default_difficulty_keys[difficulty] in song_diffs):
|
||||
difficulty = Library.Song.difficulty_key_ids.get(song_diffs.keys()[-1], 0)
|
||||
var diff_color := GameTheme.COLOR_DIFFICULTY[difficulty*2]
|
||||
var rect := Rect2(position.x, position.y, size, size)
|
||||
draw_rect(Rect2(position.x - outline_px, position.y - outline_px, size + outline_px*2, size + outline_px*2), diff_color)
|
||||
draw_texture_rect(Library.get_song_tile_texture(song_key), rect, false, Color.white if not disabled else Color(0.5, 0.2, 0.1))
|
||||
# Draw track difficulty rating
|
||||
draw_string_ralign(diffNumFont, position+Vector2(size-2*f_scale, size-5*f_scale), song_diffs.get(Library.Song.default_difficulty_keys[difficulty], '0'), diff_color)
|
||||
if disabled:
|
||||
draw_string_centered(diffNumFont, position+Vector2(size/2, size/2), 'No Chart!', diff_color, true)
|
||||
if title_text:
|
||||
draw_string_centered(titleFont, position+Vector2(size/2.0, size+40*f_scale), str(Library.all_songs[song_key].title), diff_color.lightened(0.33))
|
||||
return rect
|
||||
|
||||
func diff_f2str(difficulty: float): # Convert .5 to +
|
||||
return str(int(floor(difficulty))) + ('+' if fmod(difficulty, 1.0)>0.4 else '')
|
||||
|
||||
var sel_scales := lerp_array.new([1.0, 0.8, 0.64, 0.5, 0.4, 0.4, 0.4, 0.4, 0.4])
|
||||
var bg_scales := lerp_array.new([0.64, 0.64, 0.64, 0.5, 0.4, 0.4, 0.4, 0.4, 0.4])
|
||||
func _draw_song_select(center: Vector2) -> Array:
|
||||
var size = 200 * f_scale
|
||||
var spacer_x = 12 * f_scale
|
||||
var spacer_y = 64 * f_scale
|
||||
var title_spacer_y = 48 * f_scale
|
||||
var gy: float = center.y - 500 * f_scale - size*selected_genre_delta
|
||||
var touchrects := []
|
||||
|
||||
if len(genres) <= 0:
|
||||
draw_string_centered(genreFont, Vector2(center.x, center.y-440*f_scale), 'No Songs in Library!', Color.aqua)
|
||||
draw_string_centered(diffNumFont, Vector2(center.x, center.y-390*f_scale), Settings.user_data_dir, Color.lightgreen)
|
||||
return touchrects
|
||||
|
||||
var ssid = self.selected_song_idx
|
||||
var s_delta = target_song_delta-round(target_song_delta)
|
||||
for gi in [-2, -1, 0, 1, 2]:
|
||||
var g = (selected_genre_vis + gi) % len(genres)
|
||||
var selected: bool = (gi == 0)
|
||||
var scales = sel_scales if selected else bg_scales
|
||||
|
||||
var subsize = size * scales.value(abs(s_delta))
|
||||
var gx = center.x - (subsize + spacer_x) * s_delta
|
||||
var songslist = Library.genre_songs[g].keys()
|
||||
var genre_str = '%s (%d songs)'%[genres.keys()[g], len(songslist)]
|
||||
draw_string_centered(genreFont, Vector2(center.x, gy), genre_str, Color.lightblue)
|
||||
|
||||
var s = len(songslist)
|
||||
var key = songslist[self.selected_song_idx % s]
|
||||
var y = gy + 16*f_scale
|
||||
var x = -subsize/2.0
|
||||
var r = draw_songtile(key, Vector2(gx+x, y), subsize, selected)
|
||||
touchrects.append({rect=r, song_idx=self.selected_song_idx, genre_idx=g})
|
||||
|
||||
var subsize_p = subsize
|
||||
var subsize_n = subsize
|
||||
var x_p = x
|
||||
var x_n = x
|
||||
for i in range(1, scales.len()):
|
||||
x_p += subsize_p + spacer_x
|
||||
x_n += subsize_n + spacer_x
|
||||
subsize_p = size * scales.value(abs(i-s_delta))
|
||||
subsize_n = size * scales.value(abs(-i-s_delta))
|
||||
r = draw_songtile(songslist[(ssid+i) % s], Vector2(gx+x_p, y), subsize_p)
|
||||
touchrects.append({rect=r, song_idx=ssid+i, genre_idx=g})
|
||||
r = draw_songtile(songslist[(ssid-i) % s], Vector2(gx-x_n - subsize_n, y), subsize_n)
|
||||
touchrects.append({rect=r, song_idx=ssid-i, genre_idx=g})
|
||||
gy += size*scales.value(0) + spacer_y + (title_spacer_y if selected else 0)
|
||||
var b = 1080 * f_scale
|
||||
var v1 = -590 * f_scale
|
||||
var v2 = -230 * f_scale
|
||||
var v4 = -v2
|
||||
var v3 = -v1
|
||||
var ps = PoolVector2Array([center+Vector2(-b, v1), center+Vector2(b, v1), center+Vector2(b, v2), center+Vector2(-b, v2)])
|
||||
var ps2 = PoolVector2Array([center+Vector2(-b, v3), center+Vector2(b, v3), center+Vector2(b, v4), center+Vector2(-b, v4)])
|
||||
var cs = PoolColorArray([Color(0,0,0.1,1.25), Color(0,0,0.1,1.25), Color(0,0,0,0), Color(0,0,0,0)])
|
||||
draw_polygon(ps, cs)
|
||||
draw_polygon(ps2, cs)
|
||||
draw_string_centered(genreFont, Vector2(center.x, center.y-440*f_scale), 'Select Song', Color.aqua)
|
||||
draw_string_centered(diffNumFont, Vector2(center.x, center.y-390*f_scale), 'Tap to scroll, tap focused to select', Color.lightgreen)
|
||||
return touchrects
|
||||
|
||||
func _draw_chart_select(center: Vector2) -> Array:
|
||||
# Select difficulty for chosen song
|
||||
var charts: Dictionary = Library.get_song_charts(self.selected_song_key)
|
||||
var song_data = Library.all_songs[self.selected_song_key]
|
||||
var diffs = song_data.chart_difficulties
|
||||
var n = len(diffs)
|
||||
var spacer_x = max(14, 70/n) * f_scale
|
||||
var size = min(192, (1000-spacer_x*(n-1))/n) * f_scale
|
||||
var rect_back = Rect2(center + Vector2(-300.0, 390.0)*f_scale, Vector2(600.0, 140.0)*f_scale)
|
||||
draw_rect(rect_back, Color.red)
|
||||
draw_string_centered(titleFont, get_rect_center(rect_back), 'Back to song selection', Color.white, true)
|
||||
draw_string_centered(genreFont, center+Vector2(0, -360*f_scale), 'Select Difficulty', Color.aqua)
|
||||
draw_string_centered(diffNumFont, center+Vector2(0, -300*f_scale), 'Tap to show stats, tap focused to play', Color.lightgreen)
|
||||
var touchrects = [{rect=rect_back, chart_idx=-1, enabled=true}] # invisible back button
|
||||
var x = center.x - (size*n + spacer_x*(n-1))/2
|
||||
|
||||
for diff in diffs:
|
||||
var i_diff = Library.Song.difficulty_key_ids.get(diff, 0)
|
||||
var width = 8 if i_diff == selected_difficulty else 3
|
||||
var chart_exists: bool = (diff in charts)
|
||||
var r = draw_songtile(self.selected_song_key, Vector2(x, center.y-160*f_scale), size, false, i_diff, width, not chart_exists)
|
||||
touchrects.append({rect=r, chart_idx=i_diff, enabled=chart_exists})
|
||||
x += size + spacer_x
|
||||
draw_string_centered(titleFont, center+Vector2(0, size-116*f_scale), str(Library.all_songs[self.selected_song_key].title))
|
||||
|
||||
draw_string_centered(titleFont, center+Vector2(-50*f_scale, size-64*f_scale), 'BPM:')
|
||||
draw_string_centered(titleFont, center+Vector2(+50*f_scale, size-64*f_scale), str(song_data.BPM))
|
||||
|
||||
if len(charts) > 0:
|
||||
var sel_chart: Array = charts.values()[min(selected_difficulty, len(charts)-1)]
|
||||
var all_notes: Array = sel_chart[1]
|
||||
var meta: Dictionary = sel_chart[0]
|
||||
|
||||
var notestrs = ['Taps:', 'Holds:', 'Slides:']
|
||||
var notetypes = [0, 1, 2]
|
||||
var note_counts = [meta.num_taps, meta.num_holds, meta.num_slides]
|
||||
for i in len(notestrs):
|
||||
draw_string_centered(titleFont, center+Vector2(-50*f_scale, size+(12+i*50)*f_scale), notestrs[i])
|
||||
draw_string_centered(titleFont, center+Vector2(+50*f_scale, size+(12+i*50)*f_scale), str(note_counts[notetypes[i]]))
|
||||
else:
|
||||
draw_string_centered(titleFont, center+Vector2(0, size-12*f_scale), 'No available charts!', Color.red)
|
||||
|
||||
return touchrects
|
||||
|
||||
func _draw_score_screen(center: Vector2) -> Array:
|
||||
var size = 192 * f_scale
|
||||
var spacer_x = 12 * f_scale
|
||||
var touchrects = []
|
||||
var songslist = genres[genres.keys()[selected_genre]]
|
||||
var song_key = scorescreen_song_key
|
||||
# var song_data = Library.all_songs[song_key]
|
||||
var chart: Array = Library.get_song_charts(song_key)[Library.Song.default_difficulty_keys[selected_difficulty]]
|
||||
var all_notes: Array = chart[1]
|
||||
var meta: Dictionary = chart[0]
|
||||
|
||||
var x = center.x
|
||||
var y = -160*f_scale
|
||||
var x_score = 110
|
||||
var y_score = -380
|
||||
var x2 = -360*f_scale
|
||||
var x_spacing = 124*f_scale
|
||||
var y_spacing = 42*f_scale
|
||||
var y2 = y + y_spacing*1.5
|
||||
var y3 = y2 + y_spacing
|
||||
|
||||
var tex_judgement_text = GameTheme.tex_judgement_text
|
||||
var judgement_text_scale = 0.667
|
||||
var judgement_text_size = Vector2(256, 64) * judgement_text_scale
|
||||
|
||||
draw_songtile(song_key, center + Vector2.LEFT*size*0.5 + Vector2(-x_score, y_score)*f_scale, size, false, selected_difficulty, 3)
|
||||
draw_string_centered(titleFont, center + Vector2.DOWN*size + Vector2(-x_score, y_score+48)*f_scale, str(Library.all_songs[song_key].title))
|
||||
var notestrs = ['Taps (%d):'%meta.num_taps, 'Holds (%d) Hit:'%meta.num_holds, 'Released:', 'Stars (%d):'%meta.num_slides, 'Slides:']
|
||||
var notetypes = [0, 1, -1, 2, -2]
|
||||
var note_spacing = [0.0, 1.25, 2.25, 3.5, 4.5]
|
||||
var judgestrs = Array(Rules.JUDGEMENT_STRINGS + ['Miss'])
|
||||
var judge_scores = [1.0, 0.9, 0.75, 0.5, 0.0]
|
||||
var notetype_weights = [1.0, 1.0, 1.0, 1.0, 1.0]
|
||||
var notecount_total = 0
|
||||
var notecount_early = 0
|
||||
var notecount_late = 0
|
||||
var total_score = 0.0
|
||||
var total_scoremax = 0.0
|
||||
|
||||
for i in len(judgestrs):
|
||||
# For each judgement type, print a column header
|
||||
# draw_string_centered(titleFont, Vector2(x2+x_spacing*(i+1), y2), judgestrs[i])
|
||||
var dst_rect = Rect2(center+Vector2(x2+x_spacing*(i+1)-judgement_text_size.x*f_scale/2.0, y2), judgement_text_size*f_scale)
|
||||
draw_texture_rect_region(tex_judgement_text, dst_rect, Rect2(0, 128*(i+3), 512, 128))
|
||||
draw_string_centered(titleFont, center+Vector2(x2+x_spacing*(len(judgestrs)+1), y2+34*f_scale), 'Score')
|
||||
|
||||
for i in len(notestrs):
|
||||
# For each note type, make a row and print scores
|
||||
var idx = notetypes[i]
|
||||
var note_score = 0
|
||||
var note_count = 0
|
||||
var y_row = y3 + y_spacing * (note_spacing[i]+1)
|
||||
draw_string_centered(titleFont, center+Vector2(x2-20*f_scale, y_row), notestrs[i])
|
||||
for j in len(judgestrs):
|
||||
var score
|
||||
if j == 0:
|
||||
score = scorescreen_score_data[idx][0]
|
||||
elif j >= len(judgestrs)-1:
|
||||
score = scorescreen_score_data[idx]['MISS']
|
||||
else:
|
||||
score = scorescreen_score_data[idx][j] + scorescreen_score_data[idx][-j]
|
||||
notecount_early += scorescreen_score_data[idx][-j]
|
||||
notecount_late += scorescreen_score_data[idx][j]
|
||||
if (j >= len(judgestrs)-1) and (idx == -1):
|
||||
draw_string_centered(titleFont, center+Vector2(x2+x_spacing*(j+1), y_row), '^')
|
||||
else:
|
||||
draw_string_centered(titleFont, center+Vector2(x2+x_spacing*(j+1), y_row), str(score))
|
||||
notecount_total += score # Kinda redundant, will probably refactor eventually
|
||||
note_count += score
|
||||
note_score += score * judge_scores[j]
|
||||
draw_string_centered(titleFont, center+Vector2(x2+x_spacing*(len(judgestrs)+1), y_row), '%2.2f%%'%(note_score/max(note_count, 1)*100.0))
|
||||
total_score += note_score * notetype_weights[i]
|
||||
total_scoremax += note_count * notetype_weights[i]
|
||||
|
||||
var overall_score = total_score/max(total_scoremax, 1.0)
|
||||
var score_idx = 0
|
||||
for cutoff in Rules.SCORE_CUTOFFS:
|
||||
if overall_score >= cutoff:
|
||||
break
|
||||
else:
|
||||
score_idx += 1
|
||||
scoreText.position = center+Vector2(x_score, y_score)*f_scale
|
||||
scoreText.score = Rules.SCORE_STRINGS[score_idx]
|
||||
scoreText.score_sub = '%2.3f%%'%(overall_score*100.0)
|
||||
scoreText.update()
|
||||
|
||||
draw_string_centered(titleFont, center+Vector2(-150, y3+y_spacing*7), 'Early : Late')
|
||||
draw_string_centered(titleFont, center+Vector2(-150, y3+y_spacing*8), '%3d%% : %3d%%'%[notecount_early*100/max(notecount_total, 1), notecount_late*100/max(notecount_total, 1)])
|
||||
draw_string_centered(titleFont, center+Vector2(150, y3+y_spacing*7.5), 'Max Combo: %d'%scorescreen_score_data.get('max_combo', 0)) # Safety for older saves
|
||||
|
||||
var txt_offset = Vector2.DOWN*10*f_scale
|
||||
var rect_songs := Rect2(center+Vector2(-100.0, 300.0)*f_scale, Vector2(400.0, 100.0)*f_scale)
|
||||
draw_rect(rect_songs, Color.red)
|
||||
draw_string_centered(titleFont, get_rect_center(rect_songs), 'Song Select', Color.white, true)
|
||||
touchrects.append({rect=rect_songs, next_menu=MenuMode.SONG_SELECT})
|
||||
|
||||
var rect_save := Rect2(center+Vector2(-300.0, 300.0)*f_scale, Vector2(180.0, 100.0)*f_scale)
|
||||
if not scorescreen_saved:
|
||||
draw_rect(rect_save, Color(0.0, 0.01, 1.0))
|
||||
draw_string_centered(titleFont, get_rect_center(rect_save), 'Save', Color.white, true)
|
||||
touchrects.append({rect=rect_save, action='save'})
|
||||
else:
|
||||
draw_rect(rect_save, Color.darkgray)
|
||||
draw_string_centered(titleFont, get_rect_center(rect_save), 'Saved', Color.white, true)
|
||||
|
||||
draw_string_centered(genreFont, center+Vector2.UP*410*f_scale, 'Results', Color.aqua)
|
||||
return touchrects
|
||||
|
||||
func _draw_gameplay(center: Vector2) -> Array:
|
||||
var touchrects = []
|
||||
|
||||
var rect_songselect := Rect2(center+Vector2(+860.0, 480.0)*f_scale, Vector2(100.0, 50.0)*f_scale)
|
||||
draw_rect(rect_songselect, Color.red)
|
||||
draw_string_centered(titleFont, get_rect_center(rect_songselect), 'Stop', Color.white, true)
|
||||
touchrects.append({rect=rect_songselect, action='stop'})
|
||||
return touchrects
|
||||
|
||||
|
||||
func _draw():
|
||||
var songs = len(Library.all_songs)
|
||||
var score_screen_filter_alpha := 0.65
|
||||
var size = 216
|
||||
var outline_px = 3
|
||||
var center = rect_size * 0.5
|
||||
touch_rects = []
|
||||
scoreText.hide()
|
||||
for i in MenuMode:
|
||||
touch_rects.append([])
|
||||
|
||||
if menu_mode_prev_fade_timer > 0.0:
|
||||
var progress = 1.0 - menu_mode_prev_fade_timer/menu_mode_prev_fade_timer_duration
|
||||
var center_prev = lerp(center, center+Vector2(0.0, 1200.0), progress)
|
||||
var center_next = center_prev + Vector2(0.0, -1200.0)
|
||||
match menu_mode_prev:
|
||||
MenuMode.SONG_SELECT:
|
||||
_draw_song_select(center_prev)
|
||||
MenuMode.CHART_SELECT:
|
||||
_draw_chart_select(center_prev)
|
||||
MenuMode.OPTIONS:
|
||||
pass
|
||||
MenuMode.GAMEPLAY:
|
||||
GameTheme.set_screen_filter_alpha(lerp(0.0, score_screen_filter_alpha, progress))
|
||||
MenuMode.SCORE_SCREEN:
|
||||
_draw_score_screen(center_prev)
|
||||
match menu_mode:
|
||||
MenuMode.SONG_SELECT:
|
||||
_draw_song_select(center_next)
|
||||
MenuMode.CHART_SELECT:
|
||||
_draw_chart_select(center_next)
|
||||
MenuMode.OPTIONS:
|
||||
pass
|
||||
MenuMode.GAMEPLAY:
|
||||
GameTheme.set_screen_filter_alpha(1.0 - progress)
|
||||
MenuMode.SCORE_SCREEN:
|
||||
_draw_score_screen(center_next)
|
||||
scoreText.show()
|
||||
else:
|
||||
match menu_mode:
|
||||
MenuMode.SONG_SELECT:
|
||||
GameTheme.set_screen_filter_alpha(1.0)
|
||||
touch_rects[menu_mode] = _draw_song_select(center)
|
||||
MenuMode.CHART_SELECT:
|
||||
GameTheme.set_screen_filter_alpha(1.0)
|
||||
touch_rects[menu_mode] = _draw_chart_select(center)
|
||||
MenuMode.OPTIONS:
|
||||
pass
|
||||
MenuMode.GAMEPLAY:
|
||||
GameTheme.set_screen_filter_alpha(0.0)
|
||||
touch_rects[menu_mode] = _draw_gameplay(center)
|
||||
MenuMode.SCORE_SCREEN:
|
||||
GameTheme.set_screen_filter_alpha(score_screen_filter_alpha)
|
||||
touch_rects[menu_mode] = _draw_score_screen(center)
|
||||
scoreText.show()
|
||||
|
||||
# var joypad_labels_pos = Vector2(32, 32)
|
||||
# for i in len(joypad_labels):
|
||||
# draw_string(titleFont, joypad_labels_pos, "Joypad #%02d: %s"%[i, joypad_labels[i]])
|
||||
# joypad_labels_pos.y += 32
|
||||
|
||||
|
||||
func set_menu_mode(mode):
|
||||
receptors.fade(mode == MenuMode.GAMEPLAY)
|
||||
if mode == MenuMode.GAMEPLAY:
|
||||
PVMusic.stop()
|
||||
rect_clip_content = false
|
||||
else:
|
||||
rect_clip_content = true
|
||||
menu_mode_prev = menu_mode
|
||||
menu_mode = mode
|
||||
menu_mode_prev_fade_timer = menu_mode_prev_fade_timer_duration
|
||||
|
||||
func touch_select_song(touchdict):
|
||||
if (self.selected_genre == touchdict.genre_idx) and (self.selected_song_idx == touchdict.song_idx):
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, 0.0)
|
||||
# var songslist = genres[genres.keys()[selected_genre]]
|
||||
# selected_song_key = songslist[self.target_song_idx % len(songslist)]
|
||||
set_menu_mode(MenuMode.CHART_SELECT)
|
||||
else:
|
||||
self.selected_genre = touchdict.genre_idx
|
||||
self.target_song_idx = touchdict.song_idx
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, -4.5)
|
||||
load_preview()
|
||||
|
||||
func touch_select_chart(touchdict):
|
||||
if touchdict.chart_idx == selected_difficulty:
|
||||
if touchdict.enabled:
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, 0.0)
|
||||
set_menu_mode(MenuMode.GAMEPLAY)
|
||||
else:
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_error, 0.0)
|
||||
elif touchdict.chart_idx < 0:
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, -3.0, 0.7)
|
||||
set_menu_mode(MenuMode.SONG_SELECT)
|
||||
else:
|
||||
self.selected_difficulty = touchdict.chart_idx
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, -4.5)
|
||||
|
||||
func touch_gameplay(touchdict):
|
||||
if touchdict.has('action'):
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, 0.0)
|
||||
if touchdict.action == 'stop':
|
||||
noteHandler.stop()
|
||||
|
||||
func touch_score_screen(touchdict):
|
||||
if touchdict.has('next_menu'):
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, 0.0)
|
||||
set_menu_mode(touchdict.next_menu)
|
||||
scoreText.score = ''
|
||||
scoreText.score_sub = ''
|
||||
# TODO: time this to coincide with the menu going fully offscreen
|
||||
scoreText.update()
|
||||
elif touchdict.has('action'):
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, 0.0)
|
||||
if touchdict.action == 'save':
|
||||
save_score()
|
||||
|
||||
func finished_song(song_key, score_data):
|
||||
scorescreen_song_key = song_key
|
||||
scorescreen_score_data = score_data
|
||||
scorescreen_datetime = OS.get_datetime()
|
||||
scorescreen_saved = false
|
||||
set_menu_mode(MenuMode.SCORE_SCREEN)
|
||||
|
||||
func is_6button(device: int) -> bool:
|
||||
match Input.get_joy_name(device):
|
||||
"LHW FatMatV3":
|
||||
return true
|
||||
"LHW FatMat2":
|
||||
return true
|
||||
_: # For now, 12 button
|
||||
return false
|
||||
|
||||
func _input(event):
|
||||
if !visible:
|
||||
return
|
||||
|
||||
if event is InputEventJoypadButton:
|
||||
update_joypad_labels()
|
||||
# print_debug("Joypad #%02d is a '%s'" % [event.device, Input.get_joy_name(event.device)])
|
||||
# event.device
|
||||
# event.button_index
|
||||
# event.pressed
|
||||
# elif event is InputEventJoypadMotion:
|
||||
# update_joypad_labels()
|
||||
# print_debug("Joypad #%02d is a '%s'" % [event.device, Input.get_joy_name(event.device)])
|
||||
# event.device
|
||||
# event.axis
|
||||
# event.axis_value
|
||||
|
||||
elif event is InputEventMouseButton: # Add this if we ever manage to be rid of the curse of Touch->Mouse emulation: (event is InputEventScreenTouch)
|
||||
# print(event)
|
||||
if event.pressed:
|
||||
var pos = event.position - get_global_transform_with_canvas().get_origin()
|
||||
match menu_mode:
|
||||
MenuMode.SONG_SELECT:
|
||||
for d in touch_rects[MenuMode.SONG_SELECT]:
|
||||
if d.rect.has_point(pos):
|
||||
touch_select_song(d)
|
||||
MenuMode.CHART_SELECT:
|
||||
for d in touch_rects[MenuMode.CHART_SELECT]:
|
||||
if d.rect.has_point(pos):
|
||||
touch_select_chart(d)
|
||||
MenuMode.GAMEPLAY:
|
||||
for d in touch_rects[MenuMode.GAMEPLAY]:
|
||||
if d.rect.has_point(pos):
|
||||
touch_gameplay(d)
|
||||
MenuMode.SCORE_SCREEN:
|
||||
for d in touch_rects[MenuMode.SCORE_SCREEN]:
|
||||
if d.rect.has_point(pos):
|
||||
touch_score_screen(d)
|
||||
match menu_mode:
|
||||
MenuMode.SONG_SELECT:
|
||||
if event.is_action_pressed('ui_right'): # Sadly can't use match with this input system
|
||||
self.target_song_idx += 1
|
||||
elif event.is_action_pressed('ui_left'):
|
||||
self.target_song_idx -= 1
|
||||
elif event.is_action_pressed('ui_up'):
|
||||
selected_genre = posmod(selected_genre - 1, len(genres))
|
||||
elif event.is_action_pressed('ui_down'):
|
||||
selected_genre = posmod(selected_genre + 1, len(genres))
|
||||
elif event.is_action_pressed('ui_page_up'):
|
||||
selected_difficulty = int(max(0, selected_difficulty - 1))
|
||||
elif event.is_action_pressed('ui_page_down'):
|
||||
selected_difficulty = int(min(6, selected_difficulty + 1))
|
|
@ -37,9 +37,9 @@ func _draw(): # draw fingers points on screen
|
|||
# draw_line(touch_positions[i], touch_positions[i+1], Color(1,1,1,1))
|
||||
|
||||
func _process(delta):
|
||||
swipe_momentum *= max(1.0 - 5.0*delta, 0)
|
||||
if swipe_momentum.length_squared() < 1.0:
|
||||
swipe_momentum = Vector2.ZERO
|
||||
# swipe_momentum *= max(1.0 - 5.0*delta, 0)
|
||||
# if swipe_momentum.length_squared() < 1.0:
|
||||
# swipe_momentum = Vector2.ZERO
|
||||
update()
|
||||
|
||||
func update_data():
|
||||
|
@ -57,7 +57,7 @@ func _input(event):
|
|||
if (event is InputEventScreenDrag):
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
|
||||
touch_points[event.index] = {pressed = true, position = event.position}
|
||||
swipe_momentum = event.speed
|
||||
# swipe_momentum = event.speed
|
||||
elif (event is InputEventScreenTouch):
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
|
||||
if event.pressed:
|
||||
|
|
|
@ -1,24 +1,21 @@
|
|||
#tool
|
||||
#extends Node2D
|
||||
extends Control
|
||||
|
||||
export var NoteHandlerPath := @'../Center/NoteHandler'
|
||||
export var ReceptorsPath := @'../Center/Receptors'
|
||||
onready var NoteHandler := get_node(NoteHandlerPath)
|
||||
onready var Receptors := get_node(ReceptorsPath)
|
||||
onready var ScoreText := $ScoreText
|
||||
onready var noteHandler := $'%noteHandler'
|
||||
onready var receptors := $'%receptors'
|
||||
onready var scoreText := $ScoreText
|
||||
onready var PVMusic := SoundPlayer.music_player_pv
|
||||
|
||||
var f_scale := 1.0 setget set_f_scale
|
||||
func set_f_scale(value: float) -> void:
|
||||
f_scale = value
|
||||
TitleFont.size = int(round(32*f_scale))
|
||||
TitleFont.outline_size = int(max(round(2*f_scale), 1))
|
||||
GenreFont.size = int(round(48*f_scale))
|
||||
GenreFont.outline_size = int(max(round(2*f_scale), 1))
|
||||
DiffNumFont.size = int(round(36*f_scale))
|
||||
DiffNumFont.outline_size = int(max(round(1*f_scale), 1))
|
||||
ScoreText.set_f_scale(f_scale)
|
||||
titleFont.size = int(round(32*f_scale))
|
||||
titleFont.outline_size = int(max(round(2*f_scale), 1))
|
||||
genreFont.size = int(round(48*f_scale))
|
||||
genreFont.outline_size = int(max(round(2*f_scale), 1))
|
||||
diffNumFont.size = int(round(36*f_scale))
|
||||
diffNumFont.outline_size = int(max(round(1*f_scale), 1))
|
||||
scoreText.set_f_scale(f_scale)
|
||||
func update_scale() -> void:
|
||||
self.f_scale = rect_size.x/1080
|
||||
|
||||
|
@ -50,7 +47,10 @@ func get_song_idx() -> int:
|
|||
return int(round(self.target_song_idx + target_song_delta))
|
||||
|
||||
func get_song_key() -> String:
|
||||
var songslist = genres[genres.keys()[selected_genre]]
|
||||
var keys = genres.keys()
|
||||
if len(keys) <= selected_difficulty:
|
||||
return 'N/A'
|
||||
var songslist = genres[keys[selected_genre]]
|
||||
return songslist[int(round(self.target_song_idx)) % len(songslist)]
|
||||
|
||||
var scorescreen_song_key := ''
|
||||
|
@ -60,10 +60,10 @@ var scorescreen_saved := false
|
|||
|
||||
var touch_rects = []
|
||||
|
||||
var TitleFont: DynamicFont = preload('res://assets/MenuTitleFont.tres').duplicate()
|
||||
var GenreFont: DynamicFont = preload('res://assets/MenuGenreFont.tres').duplicate()
|
||||
var DiffNumFont: DynamicFont = preload('res://assets/MenuDiffNumberFont.tres').duplicate()
|
||||
var ScoreFont: DynamicFont = preload('res://assets/MenuScoreFont.tres').duplicate()
|
||||
var titleFont: DynamicFont = preload('res://assets/MenuTitleFont.tres').duplicate()
|
||||
var genreFont: DynamicFont = preload('res://assets/MenuGenreFont.tres').duplicate()
|
||||
var diffNumFont: DynamicFont = preload('res://assets/MenuDiffNumberFont.tres').duplicate()
|
||||
var scoreFont: DynamicFont = preload('res://assets/MenuScoreFont.tres').duplicate()
|
||||
var snd_interact := preload('res://assets/softclap.wav')
|
||||
var snd_error := preload('res://assets/miss.wav')
|
||||
|
||||
|
@ -132,7 +132,7 @@ func load_preview():
|
|||
func _ready():
|
||||
scan_library()
|
||||
connect('item_rect_changed', self, 'update_scale')
|
||||
NoteHandler.connect('finished_song', self, 'finished_song')
|
||||
noteHandler.connect('finished_song', self, 'finished_song')
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(delta):
|
||||
|
@ -158,9 +158,9 @@ func _process(delta):
|
|||
|
||||
menu_mode_prev_fade_timer = max(0.0, menu_mode_prev_fade_timer - delta)
|
||||
update()
|
||||
if (menu_mode == MenuMode.GAMEPLAY) and (menu_mode_prev_fade_timer <= 0.0) and not NoteHandler.running:
|
||||
NoteHandler.load_track(self.selected_song_key, Library.Song.default_difficulty_keys[selected_difficulty])
|
||||
NoteHandler.running = true
|
||||
if (menu_mode == MenuMode.GAMEPLAY) and (menu_mode_prev_fade_timer <= 0.0) and not noteHandler.running:
|
||||
noteHandler.load_track(self.selected_song_key, Library.Song.default_difficulty_keys[selected_difficulty])
|
||||
noteHandler.running = true
|
||||
|
||||
|
||||
func draw_string_centered(font: Font, position: Vector2, string: String, color := GameTheme.COLOR_MENU_TEXT, vcenter := false) -> Vector2:
|
||||
|
@ -195,11 +195,11 @@ func draw_songtile(song_key, position, size, title_text:=false, difficulty=selec
|
|||
draw_rect(Rect2(position.x - outline_px, position.y - outline_px, size + outline_px*2, size + outline_px*2), diff_color)
|
||||
draw_texture_rect(Library.get_song_tile_texture(song_key), rect, false, Color.white if not disabled else Color(0.5, 0.2, 0.1))
|
||||
# Draw track difficulty rating
|
||||
draw_string_ralign(DiffNumFont, position+Vector2(size-2*f_scale, size-5*f_scale), song_diffs.get(Library.Song.default_difficulty_keys[difficulty], '0'), diff_color)
|
||||
draw_string_ralign(diffNumFont, position+Vector2(size-2*f_scale, size-5*f_scale), song_diffs.get(Library.Song.default_difficulty_keys[difficulty], '0'), diff_color)
|
||||
if disabled:
|
||||
draw_string_centered(DiffNumFont, position+Vector2(size/2, size/2), 'No Chart!', diff_color, true)
|
||||
draw_string_centered(diffNumFont, position+Vector2(size/2, size/2), 'No Chart!', diff_color, true)
|
||||
if title_text:
|
||||
draw_string_centered(TitleFont, position+Vector2(size/2.0, size+40*f_scale), str(Library.all_songs[song_key].title), diff_color.lightened(0.33))
|
||||
draw_string_centered(titleFont, position+Vector2(size/2.0, size+40*f_scale), str(Library.all_songs[song_key].title), diff_color.lightened(0.33))
|
||||
return rect
|
||||
|
||||
func diff_f2str(difficulty: float): # Convert .5 to +
|
||||
|
@ -215,6 +215,11 @@ func _draw_song_select(center: Vector2) -> Array:
|
|||
var gy: float = center.y - 500 * f_scale - size*selected_genre_delta
|
||||
var touchrects := []
|
||||
|
||||
if len(genres) <= 0:
|
||||
draw_string_centered(genreFont, Vector2(center.x, center.y-440*f_scale), 'No Songs in Library!', Color.aqua)
|
||||
draw_string_centered(diffNumFont, Vector2(center.x, center.y-390*f_scale), Settings.user_data_dir, Color.lightgreen)
|
||||
return touchrects
|
||||
|
||||
var ssid = self.selected_song_idx
|
||||
var s_delta = target_song_delta-round(target_song_delta)
|
||||
for gi in [-2, -1, 0, 1, 2]:
|
||||
|
@ -226,7 +231,7 @@ func _draw_song_select(center: Vector2) -> Array:
|
|||
var gx = center.x - (subsize + spacer_x) * s_delta
|
||||
var songslist = Library.genre_songs[g].keys()
|
||||
var genre_str = '%s (%d songs)'%[genres.keys()[g], len(songslist)]
|
||||
draw_string_centered(GenreFont, Vector2(center.x, gy), genre_str, Color.lightblue)
|
||||
draw_string_centered(genreFont, Vector2(center.x, gy), genre_str, Color.lightblue)
|
||||
|
||||
var s = len(songslist)
|
||||
var key = songslist[self.selected_song_idx % s]
|
||||
|
@ -259,8 +264,8 @@ func _draw_song_select(center: Vector2) -> Array:
|
|||
var cs = PoolColorArray([Color(0,0,0.1,1.25), Color(0,0,0.1,1.25), Color(0,0,0,0), Color(0,0,0,0)])
|
||||
draw_polygon(ps, cs)
|
||||
draw_polygon(ps2, cs)
|
||||
draw_string_centered(GenreFont, Vector2(center.x, center.y-440*f_scale), 'Select Song', Color.aqua)
|
||||
draw_string_centered(DiffNumFont, Vector2(center.x, center.y-390*f_scale), 'Tap to scroll, tap focused to select', Color.lightgreen)
|
||||
draw_string_centered(genreFont, Vector2(center.x, center.y-440*f_scale), 'Select Song', Color.aqua)
|
||||
draw_string_centered(diffNumFont, Vector2(center.x, center.y-390*f_scale), 'Tap to scroll, tap focused to select', Color.lightgreen)
|
||||
return touchrects
|
||||
|
||||
func _draw_chart_select(center: Vector2) -> Array:
|
||||
|
@ -273,9 +278,9 @@ func _draw_chart_select(center: Vector2) -> Array:
|
|||
var size = min(192, (1000-spacer_x*(n-1))/n) * f_scale
|
||||
var rect_back = Rect2(center + Vector2(-300.0, 390.0)*f_scale, Vector2(600.0, 140.0)*f_scale)
|
||||
draw_rect(rect_back, Color.red)
|
||||
draw_string_centered(TitleFont, get_rect_center(rect_back), 'Back to song selection', Color.white, true)
|
||||
draw_string_centered(GenreFont, center+Vector2(0, -360*f_scale), 'Select Difficulty', Color.aqua)
|
||||
draw_string_centered(DiffNumFont, center+Vector2(0, -300*f_scale), 'Tap to show stats, tap focused to play', Color.lightgreen)
|
||||
draw_string_centered(titleFont, get_rect_center(rect_back), 'Back to song selection', Color.white, true)
|
||||
draw_string_centered(genreFont, center+Vector2(0, -360*f_scale), 'Select Difficulty', Color.aqua)
|
||||
draw_string_centered(diffNumFont, center+Vector2(0, -300*f_scale), 'Tap to show stats, tap focused to play', Color.lightgreen)
|
||||
var touchrects = [{rect=rect_back, chart_idx=-1, enabled=true}] # invisible back button
|
||||
var x = center.x - (size*n + spacer_x*(n-1))/2
|
||||
|
||||
|
@ -286,10 +291,10 @@ func _draw_chart_select(center: Vector2) -> Array:
|
|||
var r = draw_songtile(self.selected_song_key, Vector2(x, center.y-160*f_scale), size, false, i_diff, width, not chart_exists)
|
||||
touchrects.append({rect=r, chart_idx=i_diff, enabled=chart_exists})
|
||||
x += size + spacer_x
|
||||
draw_string_centered(TitleFont, center+Vector2(0, size-116*f_scale), str(Library.all_songs[self.selected_song_key].title))
|
||||
draw_string_centered(titleFont, center+Vector2(0, size-116*f_scale), str(Library.all_songs[self.selected_song_key].title))
|
||||
|
||||
draw_string_centered(TitleFont, center+Vector2(-50*f_scale, size-64*f_scale), 'BPM:')
|
||||
draw_string_centered(TitleFont, center+Vector2(+50*f_scale, size-64*f_scale), str(song_data.BPM))
|
||||
draw_string_centered(titleFont, center+Vector2(-50*f_scale, size-64*f_scale), 'BPM:')
|
||||
draw_string_centered(titleFont, center+Vector2(+50*f_scale, size-64*f_scale), str(song_data.BPM))
|
||||
|
||||
if len(charts) > 0:
|
||||
var sel_chart: Array = charts.values()[min(selected_difficulty, len(charts)-1)]
|
||||
|
@ -300,10 +305,10 @@ func _draw_chart_select(center: Vector2) -> Array:
|
|||
var notetypes = [0, 1, 2]
|
||||
var note_counts = [meta.num_taps, meta.num_holds, meta.num_slides]
|
||||
for i in len(notestrs):
|
||||
draw_string_centered(TitleFont, center+Vector2(-50*f_scale, size+(12+i*50)*f_scale), notestrs[i])
|
||||
draw_string_centered(TitleFont, center+Vector2(+50*f_scale, size+(12+i*50)*f_scale), str(note_counts[notetypes[i]]))
|
||||
draw_string_centered(titleFont, center+Vector2(-50*f_scale, size+(12+i*50)*f_scale), notestrs[i])
|
||||
draw_string_centered(titleFont, center+Vector2(+50*f_scale, size+(12+i*50)*f_scale), str(note_counts[notetypes[i]]))
|
||||
else:
|
||||
draw_string_centered(TitleFont, center+Vector2(0, size-12*f_scale), 'No available charts!', Color.red)
|
||||
draw_string_centered(titleFont, center+Vector2(0, size-12*f_scale), 'No available charts!', Color.red)
|
||||
|
||||
return touchrects
|
||||
|
||||
|
@ -333,7 +338,7 @@ func _draw_score_screen(center: Vector2) -> Array:
|
|||
var judgement_text_size = Vector2(256, 64) * judgement_text_scale
|
||||
|
||||
draw_songtile(song_key, center + Vector2.LEFT*size*0.5 + Vector2(-x_score, y_score)*f_scale, size, false, selected_difficulty, 3)
|
||||
draw_string_centered(TitleFont, center + Vector2.DOWN*size + Vector2(-x_score, y_score+48)*f_scale, str(Library.all_songs[song_key].title))
|
||||
draw_string_centered(titleFont, center + Vector2.DOWN*size + Vector2(-x_score, y_score+48)*f_scale, str(Library.all_songs[song_key].title))
|
||||
var notestrs = ['Taps (%d):'%meta.num_taps, 'Holds (%d) Hit:'%meta.num_holds, 'Released:', 'Stars (%d):'%meta.num_slides, 'Slides:']
|
||||
var notetypes = [0, 1, -1, 2, -2]
|
||||
var note_spacing = [0.0, 1.25, 2.25, 3.5, 4.5]
|
||||
|
@ -348,10 +353,10 @@ func _draw_score_screen(center: Vector2) -> Array:
|
|||
|
||||
for i in len(judgestrs):
|
||||
# For each judgement type, print a column header
|
||||
# draw_string_centered(TitleFont, Vector2(x2+x_spacing*(i+1), y2), judgestrs[i])
|
||||
# draw_string_centered(titleFont, Vector2(x2+x_spacing*(i+1), y2), judgestrs[i])
|
||||
var dst_rect = Rect2(center+Vector2(x2+x_spacing*(i+1)-judgement_text_size.x*f_scale/2.0, y2), judgement_text_size*f_scale)
|
||||
draw_texture_rect_region(tex_judgement_text, dst_rect, Rect2(0, 128*(i+3), 512, 128))
|
||||
draw_string_centered(TitleFont, center+Vector2(x2+x_spacing*(len(judgestrs)+1), y2+34*f_scale), 'Score')
|
||||
draw_string_centered(titleFont, center+Vector2(x2+x_spacing*(len(judgestrs)+1), y2+34*f_scale), 'Score')
|
||||
|
||||
for i in len(notestrs):
|
||||
# For each note type, make a row and print scores
|
||||
|
@ -359,7 +364,7 @@ func _draw_score_screen(center: Vector2) -> Array:
|
|||
var note_score = 0
|
||||
var note_count = 0
|
||||
var y_row = y3 + y_spacing * (note_spacing[i]+1)
|
||||
draw_string_centered(TitleFont, center+Vector2(x2-20*f_scale, y_row), notestrs[i])
|
||||
draw_string_centered(titleFont, center+Vector2(x2-20*f_scale, y_row), notestrs[i])
|
||||
for j in len(judgestrs):
|
||||
var score
|
||||
if j == 0:
|
||||
|
@ -371,13 +376,13 @@ func _draw_score_screen(center: Vector2) -> Array:
|
|||
notecount_early += scorescreen_score_data[idx][-j]
|
||||
notecount_late += scorescreen_score_data[idx][j]
|
||||
if (j >= len(judgestrs)-1) and (idx == -1):
|
||||
draw_string_centered(TitleFont, center+Vector2(x2+x_spacing*(j+1), y_row), '^')
|
||||
draw_string_centered(titleFont, center+Vector2(x2+x_spacing*(j+1), y_row), '^')
|
||||
else:
|
||||
draw_string_centered(TitleFont, center+Vector2(x2+x_spacing*(j+1), y_row), str(score))
|
||||
draw_string_centered(titleFont, center+Vector2(x2+x_spacing*(j+1), y_row), str(score))
|
||||
notecount_total += score # Kinda redundant, will probably refactor eventually
|
||||
note_count += score
|
||||
note_score += score * judge_scores[j]
|
||||
draw_string_centered(TitleFont, center+Vector2(x2+x_spacing*(len(judgestrs)+1), y_row), '%2.2f%%'%(note_score/max(note_count, 1)*100.0))
|
||||
draw_string_centered(titleFont, center+Vector2(x2+x_spacing*(len(judgestrs)+1), y_row), '%2.2f%%'%(note_score/max(note_count, 1)*100.0))
|
||||
total_score += note_score * notetype_weights[i]
|
||||
total_scoremax += note_count * notetype_weights[i]
|
||||
|
||||
|
@ -388,31 +393,31 @@ func _draw_score_screen(center: Vector2) -> Array:
|
|||
break
|
||||
else:
|
||||
score_idx += 1
|
||||
ScoreText.position = center+Vector2(x_score, y_score)*f_scale
|
||||
ScoreText.score = Rules.SCORE_STRINGS[score_idx]
|
||||
ScoreText.score_sub = '%2.3f%%'%(overall_score*100.0)
|
||||
ScoreText.update()
|
||||
scoreText.position = center+Vector2(x_score, y_score)*f_scale
|
||||
scoreText.score = Rules.SCORE_STRINGS[score_idx]
|
||||
scoreText.score_sub = '%2.3f%%'%(overall_score*100.0)
|
||||
scoreText.update()
|
||||
|
||||
draw_string_centered(TitleFont, center+Vector2(-150, y3+y_spacing*7), 'Early : Late')
|
||||
draw_string_centered(TitleFont, center+Vector2(-150, y3+y_spacing*8), '%3d%% : %3d%%'%[notecount_early*100/max(notecount_total, 1), notecount_late*100/max(notecount_total, 1)])
|
||||
draw_string_centered(TitleFont, center+Vector2(150, y3+y_spacing*7.5), 'Max Combo: %d'%scorescreen_score_data.get('max_combo', 0)) # Safety for older saves
|
||||
draw_string_centered(titleFont, center+Vector2(-150, y3+y_spacing*7), 'Early : Late')
|
||||
draw_string_centered(titleFont, center+Vector2(-150, y3+y_spacing*8), '%3d%% : %3d%%'%[notecount_early*100/max(notecount_total, 1), notecount_late*100/max(notecount_total, 1)])
|
||||
draw_string_centered(titleFont, center+Vector2(150, y3+y_spacing*7.5), 'Max Combo: %d'%scorescreen_score_data.get('max_combo', 0)) # Safety for older saves
|
||||
|
||||
var txt_offset = Vector2.DOWN*10*f_scale
|
||||
var rect_songs := Rect2(center+Vector2(-100.0, 300.0)*f_scale, Vector2(400.0, 100.0)*f_scale)
|
||||
draw_rect(rect_songs, Color.red)
|
||||
draw_string_centered(TitleFont, get_rect_center(rect_songs), 'Song Select', Color.white, true)
|
||||
draw_string_centered(titleFont, get_rect_center(rect_songs), 'Song Select', Color.white, true)
|
||||
touchrects.append({rect=rect_songs, next_menu=MenuMode.SONG_SELECT})
|
||||
|
||||
var rect_save := Rect2(center+Vector2(-300.0, 300.0)*f_scale, Vector2(180.0, 100.0)*f_scale)
|
||||
if not scorescreen_saved:
|
||||
draw_rect(rect_save, Color(0.0, 0.01, 1.0))
|
||||
draw_string_centered(TitleFont, get_rect_center(rect_save), 'Save', Color.white, true)
|
||||
draw_string_centered(titleFont, get_rect_center(rect_save), 'Save', Color.white, true)
|
||||
touchrects.append({rect=rect_save, action='save'})
|
||||
else:
|
||||
draw_rect(rect_save, Color.darkgray)
|
||||
draw_string_centered(TitleFont, get_rect_center(rect_save), 'Saved', Color.white, true)
|
||||
draw_string_centered(titleFont, get_rect_center(rect_save), 'Saved', Color.white, true)
|
||||
|
||||
draw_string_centered(GenreFont, center+Vector2.UP*410*f_scale, 'Results', Color.aqua)
|
||||
draw_string_centered(genreFont, center+Vector2.UP*410*f_scale, 'Results', Color.aqua)
|
||||
return touchrects
|
||||
|
||||
func _draw_gameplay(center: Vector2) -> Array:
|
||||
|
@ -420,7 +425,7 @@ func _draw_gameplay(center: Vector2) -> Array:
|
|||
|
||||
var rect_songselect := Rect2(center+Vector2(+860.0, 480.0)*f_scale, Vector2(100.0, 50.0)*f_scale)
|
||||
draw_rect(rect_songselect, Color.red)
|
||||
draw_string_centered(TitleFont, get_rect_center(rect_songselect), 'Stop', Color.white, true)
|
||||
draw_string_centered(titleFont, get_rect_center(rect_songselect), 'Stop', Color.white, true)
|
||||
touchrects.append({rect=rect_songselect, action='stop'})
|
||||
return touchrects
|
||||
|
||||
|
@ -432,7 +437,7 @@ func _draw():
|
|||
var outline_px = 3
|
||||
var center = rect_size * 0.5
|
||||
touch_rects = []
|
||||
ScoreText.hide()
|
||||
scoreText.hide()
|
||||
for i in MenuMode:
|
||||
touch_rects.append([])
|
||||
|
||||
|
@ -462,7 +467,7 @@ func _draw():
|
|||
GameTheme.set_screen_filter_alpha(1.0 - progress)
|
||||
MenuMode.SCORE_SCREEN:
|
||||
_draw_score_screen(center_next)
|
||||
ScoreText.show()
|
||||
scoreText.show()
|
||||
else:
|
||||
match menu_mode:
|
||||
MenuMode.SONG_SELECT:
|
||||
|
@ -479,10 +484,10 @@ func _draw():
|
|||
MenuMode.SCORE_SCREEN:
|
||||
GameTheme.set_screen_filter_alpha(score_screen_filter_alpha)
|
||||
touch_rects[menu_mode] = _draw_score_screen(center)
|
||||
ScoreText.show()
|
||||
scoreText.show()
|
||||
|
||||
func set_menu_mode(mode):
|
||||
Receptors.fade(mode == MenuMode.GAMEPLAY)
|
||||
receptors.fade(mode == MenuMode.GAMEPLAY)
|
||||
if mode == MenuMode.GAMEPLAY:
|
||||
PVMusic.stop()
|
||||
rect_clip_content = false
|
||||
|
@ -492,48 +497,51 @@ func set_menu_mode(mode):
|
|||
menu_mode = mode
|
||||
menu_mode_prev_fade_timer = menu_mode_prev_fade_timer_duration
|
||||
|
||||
func play_sound(sound: AudioStream, volume_db: float = 0.0, pitch_scale: float = 1.0):
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, sound, volume_db, pitch_scale)
|
||||
|
||||
func touch_select_song(touchdict):
|
||||
if (self.selected_genre == touchdict.genre_idx) and (self.selected_song_idx == touchdict.song_idx):
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, 0.0)
|
||||
play_sound(snd_interact)
|
||||
# var songslist = genres[genres.keys()[selected_genre]]
|
||||
# selected_song_key = songslist[self.target_song_idx % len(songslist)]
|
||||
set_menu_mode(MenuMode.CHART_SELECT)
|
||||
else:
|
||||
self.selected_genre = touchdict.genre_idx
|
||||
self.target_song_idx = touchdict.song_idx
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, -4.5)
|
||||
play_sound(snd_interact, -4.5)
|
||||
load_preview()
|
||||
|
||||
func touch_select_chart(touchdict):
|
||||
if touchdict.chart_idx == selected_difficulty:
|
||||
if touchdict.enabled:
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, 0.0)
|
||||
play_sound(snd_interact)
|
||||
set_menu_mode(MenuMode.GAMEPLAY)
|
||||
else:
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_error, 0.0)
|
||||
play_sound(snd_error)
|
||||
elif touchdict.chart_idx < 0:
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, -3.0, 0.7)
|
||||
play_sound(snd_interact, -3.0, 0.7)
|
||||
set_menu_mode(MenuMode.SONG_SELECT)
|
||||
else:
|
||||
self.selected_difficulty = touchdict.chart_idx
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, -4.5)
|
||||
play_sound(snd_interact, -4.5)
|
||||
|
||||
func touch_gameplay(touchdict):
|
||||
if touchdict.has('action'):
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, 0.0)
|
||||
play_sound(snd_interact)
|
||||
if touchdict.action == 'stop':
|
||||
NoteHandler.stop()
|
||||
noteHandler.stop()
|
||||
|
||||
func touch_score_screen(touchdict):
|
||||
if touchdict.has('next_menu'):
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, 0.0)
|
||||
play_sound(snd_interact)
|
||||
set_menu_mode(touchdict.next_menu)
|
||||
ScoreText.score = ''
|
||||
ScoreText.score_sub = ''
|
||||
scoreText.score = ''
|
||||
scoreText.score_sub = ''
|
||||
# TODO: time this to coincide with the menu going fully offscreen
|
||||
ScoreText.update()
|
||||
scoreText.update()
|
||||
elif touchdict.has('action'):
|
||||
SoundPlayer.play(SoundPlayer.Type.NON_POSITIONAL, self, snd_interact, 0.0)
|
||||
play_sound(snd_interact)
|
||||
if touchdict.action == 'save':
|
||||
save_score()
|
||||
|
||||
|
@ -573,12 +581,16 @@ func _input(event):
|
|||
MenuMode.SONG_SELECT:
|
||||
if event.is_action_pressed('ui_right'): # Sadly can't use match with this input system
|
||||
self.target_song_idx += 1
|
||||
load_preview()
|
||||
elif event.is_action_pressed('ui_left'):
|
||||
self.target_song_idx -= 1
|
||||
load_preview()
|
||||
elif event.is_action_pressed('ui_up'):
|
||||
selected_genre = posmod(selected_genre - 1, len(genres))
|
||||
load_preview()
|
||||
elif event.is_action_pressed('ui_down'):
|
||||
selected_genre = posmod(selected_genre + 1, len(genres))
|
||||
load_preview()
|
||||
elif event.is_action_pressed('ui_page_up'):
|
||||
selected_difficulty = int(max(0, selected_difficulty - 1))
|
||||
elif event.is_action_pressed('ui_page_down'):
|
|
@ -1,85 +1,58 @@
|
|||
#extends Object
|
||||
extends Node
|
||||
var FileHelpers := preload('res://scripts/FileHelpers.gd')
|
||||
|
||||
const ERROR_CODES := [
|
||||
'OK', 'FAILED', 'ERR_UNAVAILABLE', 'ERR_UNCONFIGURED', 'ERR_UNAUTHORIZED', 'ERR_PARAMETER_RANGE_ERROR',
|
||||
'ERR_OUT_OF_MEMORY', 'ERR_FILE_NOT_FOUND', 'ERR_FILE_BAD_DRIVE', 'ERR_FILE_BAD_PATH','ERR_FILE_NO_PERMISSION',
|
||||
'ERR_FILE_ALREADY_IN_USE', 'ERR_FILE_CANT_OPEN', 'ERR_FILE_CANT_WRITE', 'ERR_FILE_CANT_READ', 'ERR_FILE_UNRECOGNIZED',
|
||||
'ERR_FILE_CORRUPT', 'ERR_FILE_MISSING_DEPENDENCIES', 'ERR_FILE_EOF', 'ERR_CANT_OPEN', 'ERR_CANT_CREATE', 'ERR_QUERY_FAILED',
|
||||
'ERR_ALREADY_IN_USE', 'ERR_LOCKED', 'ERR_TIMEOUT', 'ERR_CANT_CONNECT', 'ERR_CANT_RESOLVE', 'ERR_CONNECTION_ERROR',
|
||||
'ERR_CANT_ACQUIRE_RESOURCE', 'ERR_CANT_FORK', 'ERR_INVALID_DATA', 'ERR_INVALID_PARAMETER', 'ERR_ALREADY_EXISTS',
|
||||
'ERR_DOES_NOT_EXIST', 'ERR_DATABASE_CANT_READ', 'ERR_DATABASE_CANT_WRITE', 'ERR_COMPILATION_FAILED', 'ERR_METHOD_NOT_FOUND',
|
||||
'ERR_LINK_FAILED', 'ERR_SCRIPT_FAILED', 'ERR_CYCLIC_LINK', 'ERR_INVALID_DECLARATION', 'ERR_DUPLICATE_SYMBOL',
|
||||
'ERR_PARSE_ERROR', 'ERR_BUSY', 'ERR_SKIP', 'ERR_HELP', 'ERR_BUG'
|
||||
]
|
||||
var RGT := preload('res://formats/RGT.gd')
|
||||
var SM := preload('res://formats/SM.gd')
|
||||
var SRT := preload('res://formats/SRT.gd')
|
||||
|
||||
const NOT_FOUND := ''
|
||||
|
||||
const default_difficulty_keys = ['Z', 'B', 'A', 'E', 'M', 'R']
|
||||
|
||||
var PATHS : PoolStringArray
|
||||
func update_library_paths() -> void:
|
||||
PATHS = Settings.get_library_paths()
|
||||
|
||||
var userroot := OS.get_user_data_dir().rstrip('/')+'/' if OS.get_name() != 'Android' else '/storage/emulated/0/RhythmGame/'
|
||||
var PATHS := PoolStringArray([userroot, '/media/fridge-q/Games/RTG/slow_userdir/']) # Temporary hardcoded testing
|
||||
# The following would probably work. One huge caveat is that permission needs to be manually granted by the user in app settings as we can't use OS.request_permission('WRITE_EXTERNAL_STORAGE')
|
||||
# '/storage/emulated/0/Android/data/au.ufeff.rhythmgame/'
|
||||
# '/sdcard/Android/data/au.ufeff.rhythmgame/'
|
||||
func _ready() -> void:
|
||||
update_library_paths()
|
||||
print('Library paths: ', PATHS)
|
||||
Settings.connect('config_loaded', self, 'update_library_paths')
|
||||
|
||||
func find_file(name: String) -> String:
|
||||
|
||||
func find_file(name: String, print_notfound:=false) -> String:
|
||||
# Searches through all of the paths to find the file
|
||||
var file := File.new()
|
||||
for root in PATHS:
|
||||
var filename: String = root + name
|
||||
if file.file_exists(filename):
|
||||
return filename
|
||||
return ''
|
||||
if print_notfound:
|
||||
print('File not found in any libraries: ', name)
|
||||
return NOT_FOUND
|
||||
|
||||
func directory_list(directory: String, hidden: bool, sort:=true) -> Dictionary:
|
||||
# Sadly there's no filelist sugar so we make our own
|
||||
var output = {folders=[], files=[], err=OK}
|
||||
var dir = Directory.new()
|
||||
output.err = dir.open(directory)
|
||||
if output.err != OK:
|
||||
print_debug('Failed to open directory: ' + directory + '(Error code '+output.err+')')
|
||||
return output
|
||||
output.err = dir.list_dir_begin(true, !hidden)
|
||||
if output.err != OK:
|
||||
print_debug('Failed to begin listing directory: ' + directory + '(Error code '+output.err+')')
|
||||
return output
|
||||
|
||||
var item = dir.get_next()
|
||||
while (item != ''):
|
||||
if dir.current_is_dir():
|
||||
output['folders'].append(item)
|
||||
else:
|
||||
output['files'].append(item)
|
||||
item = dir.get_next()
|
||||
dir.list_dir_end()
|
||||
var fallback_audiostream: AudioStream = AudioStreamOGGVorbis.new()
|
||||
var fallback_videostream: VideoStream = VideoStreamWebm.new()
|
||||
var fallback_texture := ImageTexture.new()
|
||||
|
||||
if sort:
|
||||
output.folders.sort()
|
||||
output.files.sort()
|
||||
# Maybe convert the Arrays to PoolStringArrays?
|
||||
return output
|
||||
func load_ogg(name: String) -> AudioStream: # Searches through all of the paths to find the file
|
||||
match find_file(name):
|
||||
NOT_FOUND: return fallback_audiostream
|
||||
var filename: return FileHelpers.load_ogg(filename)
|
||||
|
||||
func find_by_extensions(array, extensions=null) -> Dictionary:
|
||||
# Both args can be Array or PoolStringArray
|
||||
# If extensions omitted, do all extensions
|
||||
var output = {}
|
||||
if extensions:
|
||||
for ext in extensions:
|
||||
output[ext] = []
|
||||
for filename in array:
|
||||
for ext in extensions:
|
||||
if filename.ends_with(ext):
|
||||
output[ext].append(filename)
|
||||
else:
|
||||
for filename in array:
|
||||
var ext = filename.rsplit('.', false, 1)[1]
|
||||
if ext in output:
|
||||
output[ext].append(filename)
|
||||
else:
|
||||
output[ext] = [filename]
|
||||
return output
|
||||
func load_video(name: String) -> VideoStream: # Searches through all of the paths to find the file
|
||||
match find_file(name):
|
||||
NOT_FOUND: return fallback_videostream
|
||||
var filename: return FileHelpers.load_video(filename)
|
||||
|
||||
const default_difficulty_keys = ['Z', 'B', 'A', 'E', 'M', 'R']
|
||||
func scan_library():
|
||||
func load_image(name: String) -> ImageTexture: # Searches through all of the paths to find the file
|
||||
match find_file(name, true):
|
||||
NOT_FOUND: return fallback_texture
|
||||
var filename: return FileHelpers.load_image(filename)
|
||||
|
||||
|
||||
func scan_library() -> Dictionary:
|
||||
print('Scanning library')
|
||||
var song_defs = {}
|
||||
var collections = {}
|
||||
|
@ -89,11 +62,11 @@ func scan_library():
|
|||
var rootdir = root + 'songs'
|
||||
var dir = Directory.new()
|
||||
var err = dir.make_dir_recursive(rootdir)
|
||||
if err != OK:
|
||||
if err != OK and err != ERR_ALREADY_EXISTS:
|
||||
print_debug('An error occurred while trying to create the songs directory: ', err)
|
||||
return err
|
||||
|
||||
var songslist = directory_list(rootdir, false)
|
||||
var songslist = FileHelpers.directory_list(rootdir, false)
|
||||
if songslist.err != OK:
|
||||
print('An error occurred when trying to access the songs directory: ', songslist.err)
|
||||
return songslist.err
|
||||
|
@ -138,7 +111,7 @@ func scan_library():
|
|||
genres[song_defs[song_key]['genre']] = [song_key]
|
||||
|
||||
else:
|
||||
var files_by_ext = find_by_extensions(directory_list(full_folder, false).files)
|
||||
var files_by_ext = FileHelpers.find_by_extensions(FileHelpers.directory_list(full_folder, false).files)
|
||||
if 'sm' in files_by_ext:
|
||||
var sm_filename = files_by_ext['sm'][0]
|
||||
print(sm_filename)
|
||||
|
@ -153,463 +126,6 @@ func scan_library():
|
|||
return {song_defs=song_defs, genres=genres}
|
||||
|
||||
|
||||
|
||||
class SRT:
|
||||
const TAP_DURATION := 0.062500
|
||||
const ID_BREAK := 4
|
||||
const ID_HOLD := 2
|
||||
const ID_SLIDE_END := 128
|
||||
const ID3_SLIDE_CHORD := 0 # Straight line
|
||||
const ID3_SLIDE_ARC_CW := 1
|
||||
const ID3_SLIDE_ARC_ACW := 2
|
||||
|
||||
static func load_file(filename):
|
||||
var file = File.new()
|
||||
var err = file.open(filename, File.READ)
|
||||
if err != OK:
|
||||
print(err)
|
||||
return err
|
||||
var metadata := {}
|
||||
var num_taps := 0
|
||||
var num_holds := 0
|
||||
var num_slides := 0
|
||||
var notes := []
|
||||
var beats_per_measure := 4
|
||||
var length = file.get_len()
|
||||
var slide_ids = {}
|
||||
while (file.get_position() < (length-2)):
|
||||
var noteline = file.get_csv_line()
|
||||
var time_hit := (float(noteline[0]) + (float(noteline[1]))) * beats_per_measure
|
||||
var duration := float(noteline[2]) * beats_per_measure
|
||||
var column := int(noteline[3])
|
||||
var id := int(noteline[4])
|
||||
var id2 := int(noteline[5])
|
||||
var id3 := int(noteline[6])
|
||||
|
||||
match id:
|
||||
ID_HOLD:
|
||||
notes.push_back(Note.NoteHold.new(time_hit, column, duration))
|
||||
num_holds += 1
|
||||
ID_BREAK:
|
||||
notes.push_back(Note.NoteTap.new(time_hit, column, true))
|
||||
num_taps += 1
|
||||
ID_SLIDE_END:
|
||||
# id2 is slide ID
|
||||
if id2 in slide_ids:
|
||||
slide_ids[id2].column_release = column
|
||||
slide_ids[id2].update_slide_variables()
|
||||
_:
|
||||
if id2 == 0:
|
||||
notes.push_back(Note.NoteTap.new(time_hit, column))
|
||||
num_taps += 1
|
||||
else:
|
||||
# id2 is slide ID, id3 is slide pattern
|
||||
# In order to properly declare the slide, we need the paired endcap which may not be the next note
|
||||
var slide_type = Note.SlideType.CHORD
|
||||
match id3:
|
||||
ID3_SLIDE_CHORD:
|
||||
slide_type = Note.SlideType.CHORD
|
||||
ID3_SLIDE_ARC_CW:
|
||||
slide_type = Note.SlideType.ARC_CW
|
||||
ID3_SLIDE_ARC_ACW:
|
||||
slide_type = Note.SlideType.ARC_ACW
|
||||
_:
|
||||
print('Unknown slide type: ', id3)
|
||||
var note = Note.NoteStar.new(time_hit, column)
|
||||
num_slides += 1
|
||||
note.duration = duration
|
||||
notes.push_back(note)
|
||||
var slide = Note.NoteSlide.new(time_hit, column, duration, -1, slide_type)
|
||||
notes.push_back(slide)
|
||||
slide_ids[id2] = slide
|
||||
metadata['num_taps'] = num_taps
|
||||
metadata['num_holds'] = num_holds
|
||||
metadata['num_slides'] = num_slides
|
||||
return [metadata, notes]
|
||||
|
||||
|
||||
class RGT:
|
||||
# RhythmGameText formats
|
||||
# .rgts - simplified format cutting out redundant data, should be easy to write charts in
|
||||
# .rgtx - a lossless representation of MM in-memory format
|
||||
# .rgtm - a collection of rgts charts, with a [title] at the start of each one
|
||||
enum Format{RGTS, RGTX, RGTM}
|
||||
const EXTENSIONS = {
|
||||
'rgts': Format.RGTS,
|
||||
'rgtx': Format.RGTX,
|
||||
'rgtm': Format.RGTM,
|
||||
}
|
||||
const NOTE_TYPES = {
|
||||
't': Note.NOTE_TAP,
|
||||
'h': Note.NOTE_HOLD,
|
||||
's': Note.NOTE_STAR,
|
||||
'e': Note.NOTE_SLIDE,
|
||||
'b': Note.NOTE_TAP, # Break
|
||||
'x': Note.NOTE_STAR # Break star
|
||||
}
|
||||
const SLIDE_TYPES = {
|
||||
'0': null, # Seems to be used for stars without slides attached
|
||||
'1': Note.SlideType.CHORD,
|
||||
'2': Note.SlideType.ARC_ACW,
|
||||
'3': Note.SlideType.ARC_CW,
|
||||
'4': Note.SlideType.COMPLEX, # From nekomatsuri master - Loop ACW around center. Size of loop is roughly inscribed in chords of 0-3, 1-4, 2-5... NB: doesn't loop if directly opposite col
|
||||
'5': Note.SlideType.COMPLEX, # CW of above
|
||||
'6': Note.SlideType.COMPLEX, # S zigzag through center
|
||||
'7': Note.SlideType.COMPLEX, # Z zigzag through center
|
||||
'8': Note.SlideType.COMPLEX, # V into center
|
||||
'9': Note.SlideType.COMPLEX, # From nekomatsuri master - Seems to loop around to center ACW to make a + to the end
|
||||
'a': Note.SlideType.COMPLEX, # CW of above
|
||||
'b': Note.SlideType.COMPLEX, # V into column 2 places ACW
|
||||
'c': Note.SlideType.COMPLEX, # V into column 2 places CW
|
||||
'd': Note.SlideType.CHORD_TRIPLE, # Triple cone. Spreads out to the adjacent receptors of the target.
|
||||
'e': Note.SlideType.CHORD, # Not used in any of our charts
|
||||
'f': Note.SlideType.CHORD, # Not used in any of our charts
|
||||
}
|
||||
const SLIDE_IN_R := sin(PI/8) # Circle radius circumscribed by chords 0-3, 1-4, 2-5 etc.
|
||||
|
||||
static func load_file(filename: String):
|
||||
var extension = filename.rsplit('.', false, 1)[1]
|
||||
if not EXTENSIONS.has(extension):
|
||||
return -1
|
||||
var format = EXTENSIONS[extension]
|
||||
var file := File.new()
|
||||
var err := file.open(filename, File.READ)
|
||||
if err != OK:
|
||||
print(err)
|
||||
return err
|
||||
var length = file.get_len()
|
||||
var chart_ids = []
|
||||
var lines = [[]]
|
||||
# This loop will segment the lines as if the file were RGTM
|
||||
while (file.get_position() < (length-1)): # Could probably replace this with file.eof_reached()
|
||||
var line : String = file.get_line()
|
||||
if line.begins_with('['): # Split to a new list for each chart definition
|
||||
chart_ids.append(line.lstrip('[').rstrip(']'))
|
||||
lines.append([])
|
||||
elif !line.empty():
|
||||
lines[-1].push_back(line)
|
||||
file.close()
|
||||
print('Parsing chart: ', filename)
|
||||
|
||||
match format:
|
||||
Format.RGTS:
|
||||
var metadata_and_notes = parse_rgts(lines[0])
|
||||
return metadata_and_notes
|
||||
Format.RGTX:
|
||||
var metadata_and_notes = parse_rgtx(lines[0])
|
||||
return metadata_and_notes
|
||||
Format.RGTM:
|
||||
lines.pop_front() # Anything before the first [header] is meaningless
|
||||
var charts = {}
|
||||
for i in len(lines):
|
||||
charts[chart_ids[i]] = parse_rgts(lines[i])
|
||||
return charts
|
||||
return format
|
||||
|
||||
static func parse_rgtx(lines: PoolStringArray):
|
||||
return [] # To be implemented later
|
||||
|
||||
const beats_per_measure = 4.0 # TODO: Bit of an ugly hack, need to revisit this later
|
||||
static func parse_rgts(lines: PoolStringArray):
|
||||
var metadata := {}
|
||||
var num_taps := 0
|
||||
var num_holds := 0
|
||||
var num_slides := 0
|
||||
var notes := []
|
||||
var slide_ids := {}
|
||||
var slide_stars := {} # Multiple stars might link to one star. We only care about linking for the spin speed.
|
||||
var last_star := []
|
||||
for i in Rules.COLS:
|
||||
last_star.append(null)
|
||||
|
||||
for line in lines:
|
||||
if len(line) < 4: # shortest legal line would be like '1:1t'
|
||||
continue
|
||||
var s = line.split(':')
|
||||
var time := float(s[0]) * beats_per_measure
|
||||
var note_hits := []
|
||||
var note_nonhits := []
|
||||
for i in range(1, len(s)):
|
||||
var n = s[i]
|
||||
var column := int(n[0])
|
||||
var ntype = n[1]
|
||||
n = n.substr(2)
|
||||
|
||||
match ntype:
|
||||
't', 'b': # tap
|
||||
note_hits.append(Note.NoteTap.new(time, column, ntype=='b'))
|
||||
num_taps += 1
|
||||
'h': # hold
|
||||
var duration = float(n) * beats_per_measure
|
||||
note_hits.append(Note.NoteHold.new(time, column, duration))
|
||||
num_holds += 1
|
||||
's', 'x': # slide star
|
||||
var star = Note.NoteStar.new(time, column, ntype=='z')
|
||||
note_hits.append(star)
|
||||
num_slides += 1
|
||||
last_star[column] = star
|
||||
if len(n) > 1: # Not all stars have proper slide info
|
||||
var slide_type = n[0] # hex digit
|
||||
var slide_id = int(n.substr(1))
|
||||
if slide_id > 0:
|
||||
slide_stars[slide_id] = star
|
||||
var slide = Note.NoteSlide.new(time, column)
|
||||
slide_ids[slide_id] = slide
|
||||
note_nonhits.append(slide)
|
||||
'e': # slide end
|
||||
var slide_type = n[0] # numeric digit, left as str just in case
|
||||
var slide_id = int(n.substr(1))
|
||||
if slide_id in slide_ids: # Classic slide end
|
||||
slide_ids[slide_id].time_release = time
|
||||
if slide_id in slide_stars:
|
||||
slide_stars[slide_id].duration = slide_ids[slide_id].duration # Should probably recalc in case start time is different but w/e
|
||||
slide_ids[slide_id].column_release = column
|
||||
slide_ids[slide_id].slide_type = SLIDE_TYPES[slide_type]
|
||||
slide_ids[slide_id].update_slide_variables()
|
||||
if SLIDE_TYPES[slide_type] == Note.SlideType.COMPLEX:
|
||||
var col_hit = slide_ids[slide_id].column
|
||||
var RUV = GameTheme.RADIAL_UNIT_VECTORS
|
||||
slide_ids[slide_id].values.curve2d.add_point(RUV[col_hit]) # Start col
|
||||
match slide_type:
|
||||
'4': # TODO: Loop ACW around center. Size of loop is roughly inscribed in chords of 0-3, 1-4, 2-5... NB: doesn't loop if directly opposite col
|
||||
slide_ids[slide_id].values.curve2d.add_point((RUV[posmod(col_hit-3, Rules.COLS)] + RUV[col_hit]) * 0.5)
|
||||
'5': # TODO: CW of above
|
||||
slide_ids[slide_id].values.curve2d.add_point((RUV[posmod(col_hit+3, Rules.COLS)] + RUV[col_hit]) * 0.5)
|
||||
'6': # S zigzag through center
|
||||
slide_ids[slide_id].values.curve2d.add_point(RUV[posmod(col_hit-2, Rules.COLS)] * SLIDE_IN_R)
|
||||
slide_ids[slide_id].values.curve2d.add_point(RUV[posmod(col_hit+2, Rules.COLS)] * SLIDE_IN_R)
|
||||
'7': # Z zigzag through center
|
||||
slide_ids[slide_id].values.curve2d.add_point(RUV[posmod(col_hit+2, Rules.COLS)] * SLIDE_IN_R)
|
||||
slide_ids[slide_id].values.curve2d.add_point(RUV[posmod(col_hit-2, Rules.COLS)] * SLIDE_IN_R)
|
||||
'8': # V into center
|
||||
slide_ids[slide_id].values.curve2d.add_point(Vector2.ZERO)
|
||||
'9': # TODO: From nekomatsuri master - Seems to loop around to center ACW to make a + to the end
|
||||
slide_ids[slide_id].values.curve2d.add_point(Vector2.ZERO)
|
||||
'a': # TODO: CW of above
|
||||
slide_ids[slide_id].values.curve2d.add_point(Vector2.ZERO)
|
||||
'b': # V into column 2 places ACW
|
||||
slide_ids[slide_id].values.curve2d.add_point(GameTheme.RADIAL_UNIT_VECTORS[posmod(col_hit-2, Rules.COLS)])
|
||||
'c': # V into column 2 places CW
|
||||
slide_ids[slide_id].values.curve2d.add_point(GameTheme.RADIAL_UNIT_VECTORS[posmod(col_hit+2, Rules.COLS)])
|
||||
slide_ids[slide_id].values.curve2d.add_point(GameTheme.RADIAL_UNIT_VECTORS[column]) # End col
|
||||
else: # Naked slide start
|
||||
if last_star[column] != null:
|
||||
slide_stars[slide_id] = last_star[column]
|
||||
else:
|
||||
print_debug('Naked slide with no prior star in column!')
|
||||
var note = Note.NoteSlide.new(time, column)
|
||||
slide_ids[slide_id] = note
|
||||
note_nonhits.append(note)
|
||||
'_':
|
||||
print_debug('Unknown note type: ', ntype)
|
||||
|
||||
if len(note_hits) > 1:
|
||||
for note in note_hits: # Set multihit on each one
|
||||
note.double_hit = true
|
||||
notes += note_hits + note_nonhits
|
||||
metadata['num_taps'] = num_taps
|
||||
metadata['num_holds'] = num_holds
|
||||
metadata['num_slides'] = num_slides
|
||||
return [metadata, notes]
|
||||
|
||||
|
||||
class SM:
|
||||
# Stepmania simfile
|
||||
|
||||
const NOTE_VALUES = {
|
||||
'0': 'None',
|
||||
'1': 'Tap',
|
||||
'2': 'HoldStart',
|
||||
'3': 'HoldRollEnd',
|
||||
'4': 'RollStart',
|
||||
'M': 'Mine',
|
||||
# These three are less likely to show up anywhere, no need to implement
|
||||
'K': 'Keysound',
|
||||
'L': 'Lift',
|
||||
'F': 'Fake',
|
||||
}
|
||||
|
||||
const CHART_DIFFICULTIES = {
|
||||
'Beginner': 0,
|
||||
'Easy': 1,
|
||||
'Medium': 2,
|
||||
'Hard': 3,
|
||||
'Challenge': 4,
|
||||
'Edit': 5,
|
||||
# Some will just write whatever for special difficulties, but we should at least color-code these standard ones
|
||||
}
|
||||
|
||||
const TAG_TRANSLATIONS = {
|
||||
'#TITLE': 'title',
|
||||
'#SUBTITLE': 'subtitle',
|
||||
'#ARTIST': 'artist',
|
||||
'#TITLETRANSLIT': 'title_transliteration',
|
||||
'#SUBTITLETRANSLIT': 'subtitle_transliteration',
|
||||
'#ARTISTTRANSLIT': 'artist_transliteration',
|
||||
'#GENRE': 'genre',
|
||||
'#CREDIT': 'chart_author',
|
||||
'#BANNER': 'image_banner',
|
||||
'#BACKGROUND': 'image_background',
|
||||
# '#LYRICSPATH': '',
|
||||
'#CDTITLE': 'image_cd_title',
|
||||
'#MUSIC': 'audio_filelist',
|
||||
'#OFFSET': 'audio_offsets',
|
||||
'#SAMPLESTART': 'audio_preview_times',
|
||||
'#SAMPLELENGTH': 'audio_preview_times',
|
||||
# '#SELECTABLE': '',
|
||||
'#BPMS': 'bpm_values',
|
||||
# '#STOPS': '',
|
||||
# '#BGCHANGES': '',
|
||||
# '#KEYSOUNDS': '',
|
||||
}
|
||||
|
||||
static func load_chart(lines):
|
||||
var metadata = {}
|
||||
var notes = []
|
||||
|
||||
assert(lines[0].begins_with('#NOTES:'))
|
||||
metadata['chart_type'] = lines[1].strip_edges().rstrip(':')
|
||||
metadata['description'] = lines[2].strip_edges().rstrip(':')
|
||||
metadata['difficulty_str'] = lines[3].strip_edges().rstrip(':')
|
||||
metadata['numerical_meter'] = lines[4].strip_edges().rstrip(':')
|
||||
metadata['groove_radar'] = lines[5].strip_edges().rstrip(':')
|
||||
|
||||
# Measures are separated by lines that start with a comma
|
||||
# Each line has a state for each of the pads, e.g. '0000' for none pressed
|
||||
# The lines become even subdivisions of the measure, so if there's 4 lines everything represents a 1/4 beat, if there's 8 lines everything represents a 1/8 beat etc.
|
||||
# For this reason it's probably best to just have a float for beat-within-measure rather than integer beats.
|
||||
var measures = [[]]
|
||||
for i in range(6, len(lines)):
|
||||
var line = lines[i].strip_edges()
|
||||
if line.begins_with(','):
|
||||
measures.append([])
|
||||
elif line.begins_with(';'):
|
||||
break
|
||||
elif len(line) > 0:
|
||||
measures[-1].append(line)
|
||||
|
||||
var ongoing_holds = {}
|
||||
var num_notes := 0
|
||||
var num_jumps := 0
|
||||
var num_hands := 0
|
||||
var num_holds := 0
|
||||
var num_rolls := 0
|
||||
var num_mines := 0
|
||||
|
||||
for measure in range(len(measures)):
|
||||
var m_lines = measures[measure]
|
||||
var m_length = len(m_lines) # Divide out all lines by this
|
||||
for beat in m_length:
|
||||
var line : String = m_lines[beat]
|
||||
# Jump check at a line-level (check for multiple 1/2/4s)
|
||||
var hits : int = line.count('1') + line.count('2') + line.count('4')
|
||||
# Hand/quad check more complex as need to check hold/roll state as well
|
||||
# TODO: are they exclusive? Does quad override hand override jump? SM5 doesn't have quads and has hands+jumps inclusive
|
||||
var total_pressed : int = hits + len(ongoing_holds)
|
||||
var jump : bool = hits >= 2
|
||||
var hand : bool = total_pressed >= 3
|
||||
# var quad : bool = total_pressed >= 4
|
||||
num_notes += hits
|
||||
num_jumps += int(jump)
|
||||
num_hands += int(hand)
|
||||
var time = measure + beat/float(m_length)
|
||||
for col in len(line):
|
||||
match line[col]:
|
||||
'1':
|
||||
notes.append(Note.NoteTap.new(time, col))
|
||||
'2': # Hold
|
||||
ongoing_holds[col] = len(notes)
|
||||
notes.append(Note.NoteHold.new(time, col, 0.0))
|
||||
num_holds += 1
|
||||
'4': # Roll
|
||||
ongoing_holds[col] = len(notes)
|
||||
notes.append(Note.NoteRoll.new(time, col, 0.0))
|
||||
num_rolls += 1
|
||||
'3': # End Hold/Roll
|
||||
assert(ongoing_holds.has(col))
|
||||
notes[ongoing_holds[col]].set_time_release(time)
|
||||
ongoing_holds.erase(col)
|
||||
'M': # Mine
|
||||
num_mines += 1
|
||||
pass
|
||||
metadata['num_notes'] = num_notes
|
||||
metadata['num_taps'] = num_notes - num_jumps
|
||||
metadata['num_jumps'] = num_jumps
|
||||
metadata['num_hands'] = num_hands
|
||||
metadata['num_holds'] = num_holds
|
||||
metadata['num_rolls'] = num_rolls
|
||||
metadata['num_mines'] = num_mines
|
||||
return [metadata, notes]
|
||||
|
||||
static func load_file(filename: String) -> Array:
|
||||
# Output is [metadata, [[meta0, chart0], ..., [metaN, chartN]]]
|
||||
# Technically, declarations end with a semicolon instead of a linebreak.
|
||||
# This is a PITA to do correctly in GDScript and the files in our collection are well-behaved with linebreaks anyway, so we won't bother.
|
||||
var file := File.new()
|
||||
match file.open(filename, File.READ):
|
||||
OK:
|
||||
pass
|
||||
var err:
|
||||
print_debug('Error loading file: ', err)
|
||||
return []
|
||||
var length = file.get_len()
|
||||
var lines = [[]] # First list will be header, then every subsequent one is a chart
|
||||
while (file.get_position() < (length-1)): # Could probably replace this with file.eof_reached()
|
||||
var line : String = file.get_line()
|
||||
if line.begins_with('#NOTES'): # Split to a new list for each chart definition
|
||||
lines.append([])
|
||||
lines[-1].append(line)
|
||||
file.close()
|
||||
|
||||
var metadata = {}
|
||||
for line in lines[0]:
|
||||
var tokens = line.rstrip(';').split(':')
|
||||
if TAG_TRANSLATIONS.has(tokens[0]):
|
||||
metadata[TAG_TRANSLATIONS[tokens[0]]] = tokens[1]
|
||||
elif len(tokens) >= 2:
|
||||
metadata[tokens[0]] = tokens[1]
|
||||
var charts = []
|
||||
|
||||
for i in range(1, len(lines)):
|
||||
charts.append(load_chart(lines[i]))
|
||||
|
||||
return [metadata, charts]
|
||||
|
||||
|
||||
class Test:
|
||||
static func stress_pattern():
|
||||
var notes = []
|
||||
for bar in range(8):
|
||||
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 1))
|
||||
for i in range(1, 8):
|
||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/2.0), (bar + i)%8))
|
||||
notes.push_back(Note.NoteTap.new(bar*4 + (7/2.0), (bar + 3)%8))
|
||||
for bar in range(8, 16):
|
||||
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 2))
|
||||
for i in range(1, 8):
|
||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/2.0), (bar + i)%8))
|
||||
notes.push_back(Note.NoteTap.new(bar*4 + ((i+0.5)/2.0), (bar + i)%8))
|
||||
notes.push_back(Note.make_slide(bar*4 + ((i+1)/2.0), 1, (bar + i)%8, 0))
|
||||
for bar in range(16, 24):
|
||||
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 2))
|
||||
notes.push_back(Note.NoteHold.new(bar*4, (bar+1)%8, 1))
|
||||
for i in range(2, 8):
|
||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/2.0), (bar + i)%8))
|
||||
notes.push_back(Note.NoteHold.new(bar*4 + ((i+1)/2.0), (bar + i)%8, 0.5))
|
||||
for bar in range(24, 32):
|
||||
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 1))
|
||||
for i in range(1, 32):
|
||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/8.0), (bar + i)%8))
|
||||
if (i%2) > 0:
|
||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/8.0), (bar + i + 4)%8))
|
||||
for bar in range(32, 48):
|
||||
notes.push_back(Note.NoteHold.new(bar*4, bar%8, 1))
|
||||
for i in range(1, 32):
|
||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/8.0), (bar + i)%8))
|
||||
notes.push_back(Note.NoteTap.new(bar*4 + (i/8.0), (bar + i + 3)%8))
|
||||
return notes
|
||||
|
||||
|
||||
func load_folder(folder, filename='song'):
|
||||
var file = File.new()
|
||||
var err = file.open('%s/%s.json' % [folder, filename], File.READ)
|
||||
|
@ -627,6 +143,7 @@ func load_folder(folder, filename='song'):
|
|||
result.directory = folder
|
||||
return result
|
||||
|
||||
|
||||
func load_filelist(filelist: Array, directory=''):
|
||||
var charts = {}
|
||||
var key := 0
|
||||
|
@ -634,7 +151,7 @@ func load_filelist(filelist: Array, directory=''):
|
|||
var extension: String = name.rsplit('.', true, 1)[-1]
|
||||
name = directory.rstrip('/') + '/' + name
|
||||
var filename = find_file(name)
|
||||
if filename != '':
|
||||
if filename != NOT_FOUND:
|
||||
match extension:
|
||||
'rgtm': # multiple charts
|
||||
var res = RGT.load_file(filename)
|
||||
|
@ -651,69 +168,21 @@ func load_filelist(filelist: Array, directory=''):
|
|||
'sm': # Stepmania, multiple charts
|
||||
var res = SM.load_file(filename)
|
||||
for chart in res[1]:
|
||||
var diff = chart[0].difficulty_str
|
||||
charts[diff] = chart[1]
|
||||
var diff = chart.difficulty_str
|
||||
charts[diff] = chart.notes
|
||||
_:
|
||||
pass
|
||||
return charts
|
||||
|
||||
func direct_load_ogg(filename: String) -> AudioStreamOGGVorbis:
|
||||
# Loads the ogg file with that exact filename
|
||||
var audiostream = AudioStreamOGGVorbis.new()
|
||||
var oggfile = File.new()
|
||||
oggfile.open(filename, File.READ)
|
||||
audiostream.set_data(oggfile.get_buffer(oggfile.get_len()))
|
||||
oggfile.close()
|
||||
return audiostream
|
||||
|
||||
var fallback_audiostream = AudioStreamOGGVorbis.new()
|
||||
func load_ogg(name: String) -> AudioStreamOGGVorbis:
|
||||
# Searches through all of the paths to find the file
|
||||
match find_file(name):
|
||||
'': return fallback_audiostream
|
||||
var filename: return direct_load_ogg(filename)
|
||||
|
||||
var fallback_videostream = VideoStreamWebm.new()
|
||||
func load_video(name: String):
|
||||
match find_file(name):
|
||||
'': return fallback_videostream
|
||||
var filename:
|
||||
return load(filename)
|
||||
# var videostream = VideoStreamGDNative.new()
|
||||
# videostream.set_file(filename1)
|
||||
# return videostream
|
||||
|
||||
func direct_load_image(filename: String) -> ImageTexture:
|
||||
var tex := ImageTexture.new()
|
||||
var img := Image.new()
|
||||
img.load(filename)
|
||||
tex.create_from_image(img)
|
||||
return tex
|
||||
|
||||
var fallback_texture := ImageTexture.new()
|
||||
func load_image(name: String) -> ImageTexture:
|
||||
var filename = find_file(name)
|
||||
if filename != '':
|
||||
return direct_load_image(filename)
|
||||
print('File not found: ', name)
|
||||
return fallback_texture
|
||||
|
||||
|
||||
func init_directory(directory: String):
|
||||
var dir = Directory.new()
|
||||
var err = dir.make_dir_recursive(directory)
|
||||
if err != OK:
|
||||
print('An error occurred while trying to create the scores directory: ', err, ERROR_CODES[err])
|
||||
return err
|
||||
|
||||
func save_json(filename: String, data: Dictionary):
|
||||
filename = userroot + filename
|
||||
filename = Settings.user_data_dir + filename
|
||||
var dir = filename.rsplit('/', true, 1)[0]
|
||||
match FileLoader.init_directory(dir):
|
||||
match FileHelpers.init_directory(dir):
|
||||
OK:
|
||||
pass
|
||||
var err:
|
||||
print_debug('Error making directory for JSON file: ', err, ERROR_CODES[err])
|
||||
print_debug('Error making directory for JSON file: ', err, FileHelpers.ERROR_CODES[err])
|
||||
return err
|
||||
var json = JSON.print(data)
|
||||
var file = File.new()
|
||||
|
@ -723,9 +192,10 @@ func save_json(filename: String, data: Dictionary):
|
|||
file.close()
|
||||
return OK
|
||||
var err:
|
||||
print_debug('Error saving JSON file: ', err, ERROR_CODES[err])
|
||||
print_debug('Error saving JSON file: ', err, FileHelpers.ERROR_CODES[err])
|
||||
return err
|
||||
|
||||
|
||||
func load_json(filename: String):
|
||||
var file = File.new()
|
||||
var err
|
||||
|
@ -734,7 +204,7 @@ func load_json(filename: String):
|
|||
if file.file_exists(filename1):
|
||||
err = file.open(filename1, File.READ)
|
||||
if err != OK:
|
||||
print('An error occurred while trying to open file: ', filename1, err, ERROR_CODES[err])
|
||||
print('An error occurred while trying to open file: ', filename1, err, FileHelpers.ERROR_CODES[err])
|
||||
continue # return err
|
||||
var result_json = JSON.parse(file.get_as_text())
|
||||
file.close()
|
||||
|
|
|
@ -2,6 +2,7 @@ extends Node
|
|||
|
||||
const difficulty_translations = {'01': 'Z', '02': 'B', '03': 'A', '04': 'E', '05': 'M', '06': 'R', '10': '宴'} # A bit redundant now but might be useful later for other hacks
|
||||
|
||||
|
||||
class MultilangStr:
|
||||
# Automatically propogate higher langs to lower ones if lower ones are missing.
|
||||
# e.g. if we don't have a proper english title, return the transliterated one instead
|
||||
|
@ -28,6 +29,7 @@ class MultilangStr:
|
|||
func _to_string() -> String:
|
||||
return self[GameTheme.display_language]
|
||||
|
||||
|
||||
class Song:
|
||||
var title: MultilangStr
|
||||
var subtitle: MultilangStr
|
||||
|
@ -98,6 +100,7 @@ var genre_songs = [] # Dictionaries of key: Song
|
|||
var tile_tex_cache = {} # We'll need some way of managing this later since holding all the tiles in memory might be expensive
|
||||
var charts_cache = {}
|
||||
|
||||
|
||||
func add_song(key: String, data: Dictionary):
|
||||
if not data.has('index'):
|
||||
data['index'] = key
|
||||
|
@ -109,6 +112,7 @@ func add_song(key: String, data: Dictionary):
|
|||
genre_songs.append({})
|
||||
genre_songs[genre_ids[song.genre]][key] = song
|
||||
|
||||
|
||||
func get_song_tile_texture(song_key):
|
||||
if song_key in tile_tex_cache:
|
||||
return tile_tex_cache[song_key]
|
||||
|
@ -118,6 +122,7 @@ func get_song_tile_texture(song_key):
|
|||
else:
|
||||
print_debug('Invalid song_key: ', song_key)
|
||||
|
||||
|
||||
func get_song_charts(song_key):
|
||||
if song_key in charts_cache:
|
||||
return charts_cache[song_key]
|
||||
|
@ -143,5 +148,6 @@ func get_song_charts(song_key):
|
|||
else:
|
||||
print_debug('Invalid song_key: ', song_key)
|
||||
|
||||
|
||||
func initialize():
|
||||
pass
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#extends Object
|
||||
tool
|
||||
extends Node
|
||||
|
||||
#class_name Note
|
||||
|
@ -13,6 +13,7 @@ const RELEASE_SCORE_TYPES := {
|
|||
NOTE_ROLL: -NOTE_ROLL
|
||||
}
|
||||
|
||||
|
||||
class NoteBase extends Resource:
|
||||
var time_hit: float setget set_time_hit
|
||||
var time_death: float
|
||||
|
@ -26,26 +27,31 @@ class NoteBase extends Resource:
|
|||
time_hit = value
|
||||
time_death = time_hit + DEATH_DELAY
|
||||
|
||||
|
||||
class NoteHittableBase extends NoteBase:
|
||||
const hittable := true
|
||||
|
||||
|
||||
class NoteTapBase extends NoteHittableBase:
|
||||
func _init(time_hit: float, column: int, is_break:=false):
|
||||
self.time_hit = time_hit
|
||||
self.column = column
|
||||
self.is_break = is_break
|
||||
|
||||
|
||||
class NoteTap extends NoteTapBase:
|
||||
var type := NOTE_TAP
|
||||
func _init(time_hit: float, column: int, is_break:=false).(time_hit, column, is_break):
|
||||
pass
|
||||
|
||||
|
||||
class NoteStar extends NoteTapBase: # Fancy charts have naked slides which necessitates separation of Star and Slide :(
|
||||
var type := NOTE_STAR
|
||||
var duration := 1.0 # This is required for the spin speed
|
||||
func _init(time_hit: float, column: int, is_break:=false).(time_hit, column, is_break):
|
||||
pass
|
||||
|
||||
|
||||
class NoteHoldBase extends NoteHittableBase:
|
||||
var time_release: float setget set_time_release
|
||||
var time_released := INF
|
||||
|
@ -72,16 +78,19 @@ class NoteHoldBase extends NoteHittableBase:
|
|||
time_release = time_hit + duration
|
||||
time_death = time_release + DEATH_DELAY
|
||||
|
||||
|
||||
class NoteHold extends NoteHoldBase:
|
||||
var type := NOTE_HOLD
|
||||
func _init(time_hit: float, column: int, duration: float).(time_hit, column, duration):
|
||||
pass
|
||||
|
||||
|
||||
class NoteRoll extends NoteHoldBase:
|
||||
var type := NOTE_ROLL
|
||||
func _init(time_hit: float, column: int, duration: float).(time_hit, column, duration):
|
||||
pass
|
||||
|
||||
|
||||
class NoteSlide extends NoteBase: # Fancy charts have naked slides which necessitates separation of Star and Slide :(
|
||||
const hittable := false
|
||||
var type := NOTE_SLIDE
|
||||
|
@ -204,6 +213,7 @@ class NoteSlide extends NoteBase: # Fancy charts have naked slides which necess
|
|||
return values.curve2d.get_baked_length()
|
||||
return 0.0
|
||||
|
||||
|
||||
static func copy_note(note: NoteBase):
|
||||
# Honestly disappointed I couldn't find a better, more OOP solution for this.
|
||||
var newnote: NoteBase
|
||||
|
@ -223,16 +233,20 @@ static func copy_note(note: NoteBase):
|
|||
newnote.double_hit = note.double_hit
|
||||
return newnote
|
||||
|
||||
|
||||
static func make_slide(time_hit: float, duration: float, column: int, column_release: int, slide_type:=SlideType.CHORD) -> NoteSlide:
|
||||
return NoteSlide.new(time_hit, column, duration, column_release, slide_type)
|
||||
|
||||
|
||||
static func make_touch(time_hit: float, location: Vector2) -> Dictionary:
|
||||
return {type=NOTE_TOUCH, time_hit=time_hit, time_death=time_hit+DEATH_DELAY, location=location, double_hit=false}
|
||||
|
||||
|
||||
static func make_touch_hold(time_hit: float, duration: float, location: Vector2) -> Dictionary:
|
||||
var time_release := time_hit + duration
|
||||
return {type=NOTE_TOUCH_HOLD, time_hit=time_hit, time_release=time_release, time_death=time_release+DEATH_DELAY, location=location, double_hit=false}
|
||||
|
||||
|
||||
static func process_note_list(note_array: Array, check_doubles:=true):
|
||||
# Preprocess double hits, assign Slide IDs
|
||||
# If this were performance-critical, we'd single iterate it
|
||||
|
@ -260,3 +274,52 @@ static func process_note_list(note_array: Array, check_doubles:=true):
|
|||
note_array[i].slide_id = slide_id
|
||||
slide_id += 1
|
||||
|
||||
|
||||
# These should probably get their own singleton later
|
||||
const ORBIT_INNER_RADIUS = sin(deg2rad(22.5)) # ~0.38
|
||||
const ORBIT_KAPPA = (sqrt(2)-1) * 4.0 / 3.0 # This is the length of control points along a tangent to approximate a circle (multiply by desired radius)
|
||||
|
||||
|
||||
static func curve2d_make_orbit(curve2d, rad_in, rad_out, ccw, rad_max_arc:=PI*0.25, kappa:=ORBIT_KAPPA, inner_radius:=ORBIT_INNER_RADIUS):
|
||||
var d_sign = -1 if ccw else 1
|
||||
var rad_2 = rad_in+PI*3/8*d_sign
|
||||
var rad_2t = rad_2+PI*0.5*d_sign
|
||||
var rad_3 = rad_out-PI*3/8*d_sign
|
||||
var rad_3t = rad_3-PI*0.5*d_sign
|
||||
|
||||
var a_diff = wrapf((rad_3-rad_2)*d_sign, 0.0001, TAU+0.0001)
|
||||
var n = ceil(a_diff/rad_max_arc)
|
||||
var ad = a_diff/n
|
||||
var k = kappa*inner_radius*(2*ad/PI) # Not geometrically correct scaling but reasonable for now
|
||||
|
||||
# curve2d.add_point(polar2cartesian(1.0, rad_in))
|
||||
curve2d.add_point(polar2cartesian(inner_radius, rad_2), Vector2.ZERO, polar2cartesian(k, rad_2t))
|
||||
for i in range(1, n):
|
||||
var ang = rad_2 + i*ad*d_sign
|
||||
curve2d.add_point(polar2cartesian(inner_radius, ang), polar2cartesian(k, ang-PI/2*d_sign), polar2cartesian(k, ang+PI/2*d_sign))
|
||||
|
||||
curve2d.add_point(polar2cartesian(inner_radius, rad_3), polar2cartesian(k, rad_3t))
|
||||
# curve2d.add_point(polar2cartesian(1.0, rad_out))
|
||||
|
||||
|
||||
static func curve2d_make_sideorbit(curve2d: Curve2D, rad_in: float, rad_out: float, ccw: bool, rad_max_arc:=PI*0.25, kappa:=ORBIT_KAPPA, inner_radius:=ORBIT_INNER_RADIUS):
|
||||
var d_sign := -1 if ccw else 1
|
||||
var sideorbit_center := polar2cartesian(inner_radius, rad_in-PI*0.5*d_sign)
|
||||
var rad_orbit_in := rad_in + PI*0.5*d_sign
|
||||
var orbcenter_to_out := polar2cartesian(1.0, rad_out) - sideorbit_center
|
||||
var rad_orbit_out := orbcenter_to_out.angle() - acos(inner_radius/orbcenter_to_out.length())*d_sign
|
||||
var pos_orbit_out := sideorbit_center + polar2cartesian(inner_radius, rad_orbit_out)
|
||||
|
||||
var a_diff = wrapf((rad_orbit_out-rad_orbit_in)*d_sign, 0.0001, TAU+0.0001)
|
||||
var n = ceil(a_diff/rad_max_arc)
|
||||
var ad = a_diff/n
|
||||
var k = kappa*inner_radius*(2*ad/PI) # Not geometrically correct scaling but reasonable for now
|
||||
|
||||
# curve2d.add_point(polar2cartesian(1.0, rad_in))
|
||||
curve2d.add_point(Vector2.ZERO, Vector2.ZERO, polar2cartesian(k, rad_in+PI))
|
||||
for i in range(1, n):
|
||||
var ang = rad_orbit_in + i*ad*d_sign
|
||||
curve2d.add_point(sideorbit_center + polar2cartesian(inner_radius, ang), polar2cartesian(k, ang-PI/2*d_sign), polar2cartesian(k, ang+PI/2*d_sign))
|
||||
|
||||
curve2d.add_point(pos_orbit_out, polar2cartesian(k, rad_orbit_out-PI*0.5*d_sign))
|
||||
# curve2d.add_point(polar2cartesian(1.0, rad_out))
|
||||
|
|
|
@ -2,8 +2,42 @@
|
|||
# This is mostly used so that signals can be used to respond to settings changes
|
||||
extends Node
|
||||
|
||||
signal config_loaded
|
||||
signal subsampling_changed(xy)
|
||||
|
||||
# Newer Android versions require storage access permissions to be requested at runtime.
|
||||
# There is no way around this, so this code needs to be updated to work.
|
||||
var android_permissions_granted := false
|
||||
const REQUIRED_ANDROID_PERMISSIONS := PoolStringArray(['MANAGE_EXTERNAL_FILES', 'READ_EXTERNAL_FILES', 'WRITE_EXTERNAL_FILES'])
|
||||
func check_android_permissions() -> bool:
|
||||
if self.android_permissions_granted:
|
||||
return true
|
||||
var permissions_granted := OS.get_granted_permissions()
|
||||
for perm in REQUIRED_ANDROID_PERMISSIONS:
|
||||
if not (perm in permissions_granted):
|
||||
return false
|
||||
self.android_permissions_granted = true
|
||||
return true
|
||||
func update_android_permissions() -> void:
|
||||
if not OS.has_feature('Android'):
|
||||
return
|
||||
if self.check_android_permissions():
|
||||
return
|
||||
OS.request_permissions()
|
||||
|
||||
func _on_request_permissions_result(permission: String, granted: bool) -> void:
|
||||
print('_on_request_permissions_result: %s = %s' % [permission, granted])
|
||||
# if granted and (permission in REQUIRED_ANDROID_PERMISSIONS):
|
||||
# self.call_deferred('update_view')
|
||||
|
||||
const ANDROID_USERDIR := '/storage/emulated/0/RhythmGame/'
|
||||
# The following would probably work. One huge caveat is that permission needs to be manually granted by the user in app settings as we can't use OS.request_permission('WRITE_EXTERNAL_STORAGE')
|
||||
# '/storage/emulated/0/Android/data/au.ufeff.rhythmgame/'
|
||||
# '/sdcard/Android/data/au.ufeff.rhythmgame/'
|
||||
var user_data_dir := OS.get_user_data_dir().rstrip('/')+'/' if OS.get_name() != 'Android' else ANDROID_USERDIR
|
||||
var SETTINGS_FILENAME = user_data_dir + 'settings.cfg'
|
||||
var config : ConfigFile
|
||||
|
||||
var subsampling: Vector2 setget SSXY_set, SSXY_get
|
||||
var subsampling_x: float setget SSX_set, SSX_get
|
||||
var subsampling_y: float setget SSY_set, SSY_get
|
||||
|
@ -26,17 +60,38 @@ func SSY_get() -> float:
|
|||
func SSXY_get() -> Vector2:
|
||||
return Vector2(self.subsampling_x, self.subsampling_y)
|
||||
|
||||
func get_library_paths() -> PoolStringArray:
|
||||
var paths = [user_data_dir]
|
||||
var additional_paths = config.get_value('libraries', 'additional_paths', [])
|
||||
# Ensure paths are valid and have trailing slash
|
||||
for p in additional_paths:
|
||||
if p is String:
|
||||
paths.append(p.rstrip('/')+'/')
|
||||
return PoolStringArray(paths)
|
||||
|
||||
|
||||
const SETTINGS_FILENAME = 'user://settings.conf'
|
||||
func set_additional_library_paths(entries):
|
||||
config.set_value('libraries', 'additional_paths', PoolStringArray(entries))
|
||||
save_settings()
|
||||
|
||||
func load_settings():
|
||||
var config := ConfigFile.new()
|
||||
config = ConfigFile.new()
|
||||
config.set_value('libraries', 'additional_paths', [])
|
||||
match config.load(SETTINGS_FILENAME):
|
||||
OK:
|
||||
pass
|
||||
self.subsampling = Vector2(
|
||||
config.get_value('rendering', 'subsampling_x', self.subsampling_x),
|
||||
config.get_value('rendering', 'subsampling_y', self.subsampling_y)
|
||||
)
|
||||
emit_signal('config_loaded')
|
||||
ERR_FILE_NOT_FOUND:
|
||||
save_settings()
|
||||
print('Loaded settings from ' + SETTINGS_FILENAME)
|
||||
|
||||
func save_settings():
|
||||
pass
|
||||
config.save(SETTINGS_FILENAME)
|
||||
print('Saved settings to ' + SETTINGS_FILENAME)
|
||||
|
||||
func _ready():
|
||||
load_settings()
|
||||
get_tree().connect('on_request_permissions_result', self, '_on_request_permissions_result')
|
||||
self.update_android_permissions()
|
||||
|
|
Loading…
Reference in New Issue