First commit 🎉
This commit is contained in:
commit
43ea213f9b
728 changed files with 37080 additions and 0 deletions
4
.editorconfig
Normal file
4
.editorconfig
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# Normalize EOL for all files that Git considers text files.
|
||||||
|
* text=auto eol=lf
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Godot 4+ specific ignores
|
||||||
|
.godot/
|
||||||
|
/android/
|
674
LICENSE
Normal file
674
LICENSE
Normal file
|
@ -0,0 +1,674 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
21
addons/popochiu/LICENSE
Normal file
21
addons/popochiu/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 Mateo Robayo Rogríguez
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,332 @@
|
||||||
|
@tool
|
||||||
|
extends HBoxContainer
|
||||||
|
## Used to show new buttons in the EditorPlugin.CONTAINER_CANVAS_EDITOR_MENU (the top bar in the
|
||||||
|
## 2D editor) to select specific nodes in PopochiuClickable objects.
|
||||||
|
|
||||||
|
var _active_popochiu_object: Node = null
|
||||||
|
var _shown_helpers := []
|
||||||
|
|
||||||
|
@onready var btn_baseline: Button = %BtnBaseline
|
||||||
|
@onready var btn_walk_to_point: Button = %BtnWalkToPoint
|
||||||
|
@onready var btn_look_at_point: Button = %BtnLookAtPoint
|
||||||
|
@onready var btn_dialog_pos: Button = %BtnDialogPos
|
||||||
|
@onready var btn_interaction_polygon: Button = %BtnInteractionPolygon
|
||||||
|
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _ready() -> void:
|
||||||
|
# Gizmos are always visible at editor load, so we'll set the buttons down
|
||||||
|
# to sync the status (hardcoded, not very good but enough for now)
|
||||||
|
_reset_buttons_state()
|
||||||
|
|
||||||
|
# Connect to child signals
|
||||||
|
btn_baseline.pressed.connect(_toggle_baseline_visibility)
|
||||||
|
btn_walk_to_point.pressed.connect(_toggle_walk_to_point_visibility)
|
||||||
|
btn_look_at_point.pressed.connect(_toggle_look_at_point_visibility)
|
||||||
|
btn_dialog_pos.pressed.connect(_toggle_dialog_pos_visibility)
|
||||||
|
btn_interaction_polygon.pressed.connect(_select_interaction_polygon)
|
||||||
|
|
||||||
|
# Connect to singleton signals
|
||||||
|
EditorInterface.get_selection().selection_changed.connect(_on_selection_changed)
|
||||||
|
EditorInterface.get_editor_settings().settings_changed.connect(_on_gizmo_settings_changed)
|
||||||
|
|
||||||
|
_set_toolbar_buttons_color()
|
||||||
|
hide()
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _toggle_walk_to_point_visibility() -> void:
|
||||||
|
PopochiuEditorHelper.signal_bus.gizmo_visibility_changed.emit(
|
||||||
|
PopochiuGizmoClickablePlugin.WALK_TO_POINT,
|
||||||
|
btn_walk_to_point.button_pressed
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _toggle_look_at_point_visibility() -> void:
|
||||||
|
PopochiuEditorHelper.signal_bus.gizmo_visibility_changed.emit(
|
||||||
|
PopochiuGizmoClickablePlugin.LOOK_AT_POINT,
|
||||||
|
btn_look_at_point.button_pressed
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _toggle_baseline_visibility() -> void:
|
||||||
|
PopochiuEditorHelper.signal_bus.gizmo_visibility_changed.emit(
|
||||||
|
PopochiuGizmoClickablePlugin.BASELINE,
|
||||||
|
btn_baseline.button_pressed
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _toggle_dialog_pos_visibility() -> void:
|
||||||
|
PopochiuEditorHelper.signal_bus.gizmo_visibility_changed.emit(
|
||||||
|
PopochiuGizmoClickablePlugin.DIALOG_POS,
|
||||||
|
btn_dialog_pos.button_pressed
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _select_interaction_polygon() -> void:
|
||||||
|
# Since we are going to select the interaction polygon node
|
||||||
|
# inside the node, let's hide the gizmos buttons
|
||||||
|
btn_walk_to_point.hide()
|
||||||
|
btn_baseline.hide()
|
||||||
|
|
||||||
|
# If we are editing the polygon, go back and select the parent node
|
||||||
|
# then stop execution.
|
||||||
|
var selected_node := EditorInterface.get_selection().get_selected_nodes()[0]
|
||||||
|
if PopochiuEditorHelper.is_popochiu_obj_polygon(
|
||||||
|
selected_node
|
||||||
|
):
|
||||||
|
EditorInterface.get_selection().add_node(selected_node.get_parent())
|
||||||
|
_on_selection_changed()
|
||||||
|
return
|
||||||
|
|
||||||
|
# If we are editing a popochiu object holding a polygon, let's move on.
|
||||||
|
|
||||||
|
# This variable will hold the reference to the polygon we need to edit.
|
||||||
|
var obj_polygon: Node2D = null
|
||||||
|
|
||||||
|
# Let's find the node holding the polygon
|
||||||
|
# Since different Popochiu Objects have different polygons (NavigationRegion2D
|
||||||
|
# for Walkable Areas, InteractionPolygon2D for props, etc...) we tagged them
|
||||||
|
# by a special metadata
|
||||||
|
obj_polygon = PopochiuEditorHelper.get_first_child_by_group(
|
||||||
|
_active_popochiu_object,
|
||||||
|
PopochiuEditorHelper.POPOCHIU_OBJECT_POLYGON_GROUP
|
||||||
|
)
|
||||||
|
|
||||||
|
if obj_polygon == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
EditorInterface.get_selection().clear()
|
||||||
|
EditorInterface.get_selection().add_node(obj_polygon)
|
||||||
|
obj_polygon.show()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_gizmo_settings_changed() -> void:
|
||||||
|
# Pretty self explanatory
|
||||||
|
_set_walkable_areas_visibility()
|
||||||
|
_set_toolbar_buttons_color()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_selection_changed() -> void:
|
||||||
|
# Always reset the walkable areas visibility depending on the user preferences
|
||||||
|
# Doing this immediately so, if this function exits early, the visibility is conditioned
|
||||||
|
# by the editor settings (partially fixes #325).
|
||||||
|
_set_walkable_areas_visibility()
|
||||||
|
|
||||||
|
# Make sure this function works only if the user is editing a
|
||||||
|
# supported scene
|
||||||
|
if not PopochiuEditorHelper.is_popochiu_object(
|
||||||
|
EditorInterface.get_edited_scene_root()
|
||||||
|
):
|
||||||
|
hide()
|
||||||
|
return
|
||||||
|
|
||||||
|
# If we have no selection in the tree (the user clicked on an
|
||||||
|
# empty area or pressed ESC), we hide the toolbar.
|
||||||
|
if EditorInterface.get_selection().get_selected_nodes().is_empty():
|
||||||
|
if _active_popochiu_object != null:
|
||||||
|
# TODO: this is not a helper function, because we want to get
|
||||||
|
# rid of this ASAP. The same logic is also in the function
|
||||||
|
# _set_polygons_visibility() in the base Popochiu object
|
||||||
|
# factory, and should be removed as well.
|
||||||
|
for node in _active_popochiu_object.get_children():
|
||||||
|
if PopochiuEditorHelper.is_popochiu_obj_polygon(node):
|
||||||
|
node.hide()
|
||||||
|
# This "if" solves "!p_node->is_inside_tree()" internal Godot error
|
||||||
|
# The line inside is the logic we need to make this block work
|
||||||
|
if EditorInterface.get_edited_scene_root() == _active_popochiu_object:
|
||||||
|
EditorInterface.get_selection().add_node.call_deferred(_active_popochiu_object)
|
||||||
|
# Reset the clickable reference and hide the toolbar
|
||||||
|
# (restart from a blank state)
|
||||||
|
_active_popochiu_object = null
|
||||||
|
hide()
|
||||||
|
# NOTE: Here we used to pop all the buttons up, by invoking _reset_buttons_state() but
|
||||||
|
# this is undesirable, since it overrides the user's visibility choices for the session.
|
||||||
|
# Leaving this comment here for future reference.
|
||||||
|
|
||||||
|
# Reset the walkable areas visibility depending on the user preferences
|
||||||
|
# Doing here because clicking on an empty area would hide the walkable areas
|
||||||
|
# ignoring the editor settings (fixes #325)
|
||||||
|
_set_walkable_areas_visibility()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
# We identify which PopochiuClickable we are working on in the editor.
|
||||||
|
|
||||||
|
# Case 1:
|
||||||
|
# There is only one selected node in the editor. It can be anything the user
|
||||||
|
# clicked on, or the polygon selected by clicking the toolbar button.
|
||||||
|
# (The user can never select the polygon directly because the node is not visible
|
||||||
|
# in the scene tree)
|
||||||
|
if EditorInterface.get_selection().get_selected_nodes().size() == 1:
|
||||||
|
var selected_node = EditorInterface.get_selection().get_selected_nodes()[0]
|
||||||
|
if PopochiuEditorHelper.is_popochiu_obj_polygon(selected_node):
|
||||||
|
_active_popochiu_object = selected_node.get_parent()
|
||||||
|
elif PopochiuEditorHelper.is_popochiu_room_object(selected_node):
|
||||||
|
var polygon = null
|
||||||
|
if is_instance_valid(_active_popochiu_object):
|
||||||
|
polygon = PopochiuEditorHelper.get_first_child_by_group(
|
||||||
|
_active_popochiu_object,
|
||||||
|
PopochiuEditorHelper.POPOCHIU_OBJECT_POLYGON_GROUP
|
||||||
|
)
|
||||||
|
if (polygon != null):
|
||||||
|
polygon.hide()
|
||||||
|
btn_interaction_polygon.set_pressed_no_signal(false)
|
||||||
|
_active_popochiu_object = selected_node
|
||||||
|
else:
|
||||||
|
_active_popochiu_object = null
|
||||||
|
|
||||||
|
# Case 2:
|
||||||
|
# We have more than one node selected. This can happen because the user selected
|
||||||
|
# more than one node explicitly (holding shift, or ctrl), or because the user selected
|
||||||
|
# one node in the scene while editing the polygon.
|
||||||
|
# In this case, since the polygon was selected programmatically and it's not in the scene
|
||||||
|
# tree, Godot will NOT remove it from selection and we need to do it by hand.
|
||||||
|
elif EditorInterface.get_selection().get_selected_nodes().size() > 1:
|
||||||
|
for node in EditorInterface.get_selection().get_selected_nodes():
|
||||||
|
if PopochiuEditorHelper.is_popochiu_obj_polygon(node):
|
||||||
|
node.hide()
|
||||||
|
EditorInterface.get_selection().remove_node.call_deferred(node)
|
||||||
|
btn_interaction_polygon.set_pressed_no_signal(false)
|
||||||
|
|
||||||
|
# Reset the walkable areas visibility depending on the user preferences
|
||||||
|
# Doing this also at the end because the state can be reset by one of the steps
|
||||||
|
# above.
|
||||||
|
_set_walkable_areas_visibility()
|
||||||
|
|
||||||
|
# Always reset the button visibility depending on the state of the internal variables
|
||||||
|
_set_buttons_visibility()
|
||||||
|
|
||||||
|
|
||||||
|
## Handles the editor config that allows the WAs polygons to be always visible,
|
||||||
|
## not only during editing.
|
||||||
|
func _set_walkable_areas_visibility() -> void:
|
||||||
|
for child in PopochiuEditorHelper.get_all_children(
|
||||||
|
EditorInterface.get_edited_scene_root().find_child("WalkableAreas")
|
||||||
|
):
|
||||||
|
# Not a polygon? Skip
|
||||||
|
if not PopochiuEditorHelper.is_popochiu_obj_polygon(child):
|
||||||
|
continue
|
||||||
|
# Should we show all the polygons? Show and go to the next one
|
||||||
|
if PopochiuEditorConfig.get_editor_setting(
|
||||||
|
PopochiuEditorConfig.GIZMOS_ALWAYS_SHOW_WA
|
||||||
|
):
|
||||||
|
child.show()
|
||||||
|
continue
|
||||||
|
# If we are editing the polygon, make sure it stays visible!
|
||||||
|
if child in EditorInterface.get_selection().get_selected_nodes():
|
||||||
|
child.show()
|
||||||
|
continue
|
||||||
|
# OK, we know we must hide this polygon now!
|
||||||
|
child.hide()
|
||||||
|
|
||||||
|
|
||||||
|
## Sets all the buttons color so that they are the same as the gizmos
|
||||||
|
## or make them theme-standard if the use so prefer (see editor settings)
|
||||||
|
func _set_toolbar_buttons_color() -> void:
|
||||||
|
if not PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_COLOR_TOOLBAR_BUTTONS):
|
||||||
|
# Reset button colors
|
||||||
|
_reset_toolbar_button_color(btn_baseline)
|
||||||
|
_reset_toolbar_button_color(btn_walk_to_point)
|
||||||
|
_reset_toolbar_button_color(btn_look_at_point)
|
||||||
|
_reset_toolbar_button_color(btn_dialog_pos)
|
||||||
|
_reset_toolbar_button_color(btn_interaction_polygon)
|
||||||
|
# Done
|
||||||
|
return
|
||||||
|
|
||||||
|
_set_toolbar_button_color(
|
||||||
|
btn_baseline,
|
||||||
|
PopochiuEditorConfig.get_editor_setting(
|
||||||
|
PopochiuEditorConfig.GIZMOS_BASELINE_COLOR)
|
||||||
|
)
|
||||||
|
_set_toolbar_button_color(
|
||||||
|
btn_walk_to_point,
|
||||||
|
PopochiuEditorConfig.get_editor_setting(
|
||||||
|
PopochiuEditorConfig.GIZMOS_WALK_TO_POINT_COLOR)
|
||||||
|
)
|
||||||
|
_set_toolbar_button_color(
|
||||||
|
btn_look_at_point,
|
||||||
|
PopochiuEditorConfig.get_editor_setting(
|
||||||
|
PopochiuEditorConfig.GIZMOS_LOOK_AT_POINT_COLOR)
|
||||||
|
)
|
||||||
|
_set_toolbar_button_color(
|
||||||
|
btn_dialog_pos,
|
||||||
|
PopochiuEditorConfig.get_editor_setting(
|
||||||
|
PopochiuEditorConfig.GIZMOS_DIALOG_POS_COLOR)
|
||||||
|
)
|
||||||
|
_set_toolbar_button_color(
|
||||||
|
btn_interaction_polygon,
|
||||||
|
Color.RED # no config for this at the moment
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
## Internal helper to reduce code duplication
|
||||||
|
func _set_toolbar_button_color(btn, color) -> void:
|
||||||
|
btn.add_theme_color_override("icon_normal_color", color)
|
||||||
|
btn.add_theme_color_override("icon_hover_color", color.lightened(1.0))
|
||||||
|
btn.add_theme_color_override("icon_focused_color", color.lightened(1.0))
|
||||||
|
btn.add_theme_color_override("icon_pressed_color", color.darkened(0.2))
|
||||||
|
btn.add_theme_color_override("icon_hover_pressed_color", color.lightened(1.0))
|
||||||
|
|
||||||
|
|
||||||
|
## Internal helper to reduce code duplication
|
||||||
|
func _reset_toolbar_button_color(btn) -> void:
|
||||||
|
btn.remove_theme_color_override("icon_normal_color")
|
||||||
|
btn.remove_theme_color_override("icon_hover_color")
|
||||||
|
btn.remove_theme_color_override("icon_focused_color")
|
||||||
|
btn.remove_theme_color_override("icon_pressed_color")
|
||||||
|
btn.remove_theme_color_override("icon_hover_pressed_color")
|
||||||
|
|
||||||
|
|
||||||
|
func _set_buttons_visibility() -> void:
|
||||||
|
# Let's assume the buttons are all hidden...
|
||||||
|
hide()
|
||||||
|
btn_baseline.hide()
|
||||||
|
btn_walk_to_point.hide()
|
||||||
|
btn_look_at_point.hide()
|
||||||
|
btn_dialog_pos.hide()
|
||||||
|
btn_interaction_polygon.hide()
|
||||||
|
|
||||||
|
# If we are not editing a Popochiu object, nothing to do
|
||||||
|
if not PopochiuEditorHelper.is_popochiu_room_object(_active_popochiu_object):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Now we know we have to show the toolbar
|
||||||
|
show()
|
||||||
|
|
||||||
|
# Every Popochiu Object always shows the polygon editing button when edited
|
||||||
|
# unless we are in a room scene and selected a character
|
||||||
|
if not (
|
||||||
|
PopochiuEditorHelper.is_character(_active_popochiu_object)
|
||||||
|
and PopochiuEditorHelper.is_editing_room()
|
||||||
|
):
|
||||||
|
btn_interaction_polygon.show()
|
||||||
|
|
||||||
|
# If the selected node in the editor is actually a popochiu object polygon
|
||||||
|
# We don't have to show the other buttons, only the polygon editing toggle
|
||||||
|
if PopochiuEditorHelper.is_popochiu_obj_polygon(
|
||||||
|
EditorInterface.get_selection().get_selected_nodes()[0]
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
# If we are in a room scene, we may have selected a room object of sort, so check
|
||||||
|
# for the various types and hide the ones we don't need
|
||||||
|
if PopochiuEditorHelper.is_room(EditorInterface.get_edited_scene_root()):
|
||||||
|
# If we are editing a clickable object, let's show gizmos buttons too
|
||||||
|
if _active_popochiu_object is PopochiuClickable:
|
||||||
|
btn_baseline.show()
|
||||||
|
btn_walk_to_point.show()
|
||||||
|
btn_look_at_point.show()
|
||||||
|
|
||||||
|
# If we are in a Character scene, show polygon and dialogpos gizmo button
|
||||||
|
elif PopochiuEditorHelper.is_character(EditorInterface.get_edited_scene_root()):
|
||||||
|
btn_dialog_pos.show()
|
||||||
|
|
||||||
|
|
||||||
|
# Make all buttons pop-up
|
||||||
|
func _reset_buttons_state() -> void:
|
||||||
|
btn_baseline.set_pressed_no_signal(true)
|
||||||
|
btn_walk_to_point.set_pressed_no_signal(true)
|
||||||
|
btn_look_at_point.set_pressed_no_signal(true)
|
||||||
|
btn_dialog_pos.set_pressed_no_signal(true)
|
|
@ -0,0 +1 @@
|
||||||
|
uid://15ys464umb3h
|
|
@ -0,0 +1,65 @@
|
||||||
|
[gd_scene load_steps=7 format=3 uid="uid://wd748u1vdybq"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/popochiu/editor/canvas_editor_menu/popochiu_canvas_editor_menu.gd" id="1_vs7c6"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://b3sku5v1n23ni" path="res://addons/popochiu/icons/btn_baseline.svg" id="2_w3cau"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://dmt2epjmlpv56" path="res://addons/popochiu/icons/btn_walk_to_point.svg" id="3_ifql1"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://skjlvpct7ah7" path="res://addons/popochiu/icons/btn_look_at_point.svg" id="4_bge33"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://cekffh7bsuanp" path="res://addons/popochiu/icons/btn_dialog_position.svg" id="5_803mj"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://cyun4rylrtrvm" path="res://addons/popochiu/icons/btn_interaction_polygon.svg" id="6_6oxl8"]
|
||||||
|
|
||||||
|
[node name="PopochiuCanvasEditorMenu" type="HBoxContainer"]
|
||||||
|
visible = false
|
||||||
|
offset_right = 40.0
|
||||||
|
offset_bottom = 40.0
|
||||||
|
script = ExtResource("1_vs7c6")
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Popochiu "
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="BtnBaseline" type="Button" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Baseline"
|
||||||
|
theme_type_variation = &"FlatButton"
|
||||||
|
toggle_mode = true
|
||||||
|
button_pressed = true
|
||||||
|
icon = ExtResource("2_w3cau")
|
||||||
|
|
||||||
|
[node name="BtnWalkToPoint" type="Button" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Walk-To Point"
|
||||||
|
theme_type_variation = &"FlatButton"
|
||||||
|
toggle_mode = true
|
||||||
|
button_pressed = true
|
||||||
|
icon = ExtResource("3_ifql1")
|
||||||
|
|
||||||
|
[node name="BtnLookAtPoint" type="Button" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Look-At Point"
|
||||||
|
theme_type_variation = &"FlatButton"
|
||||||
|
toggle_mode = true
|
||||||
|
button_pressed = true
|
||||||
|
icon = ExtResource("4_bge33")
|
||||||
|
|
||||||
|
[node name="BtnDialogPos" type="Button" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Dialog Position"
|
||||||
|
theme_type_variation = &"FlatButton"
|
||||||
|
toggle_mode = true
|
||||||
|
button_pressed = true
|
||||||
|
icon = ExtResource("5_803mj")
|
||||||
|
|
||||||
|
[node name="BtnInteractionPolygon" type="Button" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Interaction Polygon"
|
||||||
|
theme_type_variation = &"FlatButton"
|
||||||
|
toggle_mode = true
|
||||||
|
icon = ExtResource("6_6oxl8")
|
291
addons/popochiu/editor/config/config.gd
Normal file
291
addons/popochiu/editor/config/config.gd
Normal file
|
@ -0,0 +1,291 @@
|
||||||
|
@tool
|
||||||
|
class_name PopochiuConfig
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
enum DialogStyle {
|
||||||
|
ABOVE_CHARACTER,
|
||||||
|
PORTRAIT,
|
||||||
|
CAPTION,
|
||||||
|
#PORTRAIT_ABOVE_CHARACTER, # TODO: Create a GUI node to make this option available
|
||||||
|
#BUBBLE_ABOVE_CHARACTER, # TODO: Create a GUI node to make this option available
|
||||||
|
}
|
||||||
|
|
||||||
|
# Thanks to @drbloop for providing the bases of the new approach for moving the popochiu settings to
|
||||||
|
# Godot's ProjectSettings instead of using a Resource file.
|
||||||
|
# ---- GUI -----------------------------------------------------------------------------------------
|
||||||
|
const SCALE_GUI = "popochiu/gui/experimental_scale_gui"
|
||||||
|
const FADE_COLOR = "popochiu/gui/fade_color"
|
||||||
|
const SKIP_CUTSCENE_TIME = "popochiu/gui/skip_cutscene_time"
|
||||||
|
const TL_FIRST_ROOM = "popochiu/gui/show_transition_layer_in_first_room"
|
||||||
|
|
||||||
|
# ---- Dialogs -------------------------------------------------------------------------------------
|
||||||
|
const TEXT_SPEED = "popochiu/dialogs/text_speed"
|
||||||
|
const AUTO_CONTINUE_TEXT = "popochiu/dialogs/auto_continue_text"
|
||||||
|
const USE_TRANSLATIONS = "popochiu/dialogs/use_translations"
|
||||||
|
const GIBBERISH_SPOKEN_TEXT = 'popochiu/dialogs/gibberish_spoken_text'
|
||||||
|
const GIBBERISH_DIALOG_OPTIONS = 'popochiu/dialogs/gibberish_dialog_options'
|
||||||
|
const DIALOG_STYLE = "popochiu/dialogs/dialog_style"
|
||||||
|
|
||||||
|
# ---- Inventory -----------------------------------------------------------------------------------
|
||||||
|
const INVENTORY_LIMIT = "popochiu/inventory/inventory_limit"
|
||||||
|
const INVENTORY_ITEMS_ON_START = "popochiu/inventory/items_on_start"
|
||||||
|
|
||||||
|
# ---- Aseprite Importing --------------------------------------------------------------------------
|
||||||
|
const ASEPRITE_IMPORT_ANIMATION = "popochiu/aseprite_import/import_animation_by_default"
|
||||||
|
const ASEPRITE_LOOP_ANIMATION = "popochiu/aseprite_import/loop_animation_by_default"
|
||||||
|
const ASEPRITE_PROPS_VISIBLE = "popochiu/aseprite_import/new_props_visible_by_default"
|
||||||
|
const ASEPRITE_PROPS_CLICKABLE = "popochiu/aseprite_import/new_props_clickable_by_default"
|
||||||
|
const ASEPRITE_WIPE_OLD_ANIMATIONS = "popochiu/aseprite_import/wipe_old_animations"
|
||||||
|
|
||||||
|
# ---- Pixel game ----------------------------------------------------------------------------------
|
||||||
|
const PIXEL_ART_TEXTURES = "popochiu/pixel/pixel_art_textures"
|
||||||
|
const PIXEL_PERFECT = "popochiu/pixel/pixel_perfect"
|
||||||
|
|
||||||
|
# ---- Audio ---------------------------------------------------------------------------------------
|
||||||
|
const PREFIX_CHARACTER = "popochiu/audio/prefix_character"
|
||||||
|
const MUSIC_PREFIXES = "popochiu/audio/music_prefixes"
|
||||||
|
const SOUND_EFFECT_PREFIXES = "popochiu/audio/sound_effect_prefixes"
|
||||||
|
const VOICE_PREFIXES = "popochiu/audio/voice_prefixes"
|
||||||
|
const UI_PREFIXES = "popochiu/audio/ui_prefixes"
|
||||||
|
|
||||||
|
# ---- DEV -----------------------------------------------------------------------------------------
|
||||||
|
const DEV_USE_ADDON_TEMPLATE = "popochiu/dev/use_addon_template"
|
||||||
|
|
||||||
|
static var defaults := {
|
||||||
|
SCALE_GUI: false,
|
||||||
|
FADE_COLOR: Color.BLACK,
|
||||||
|
SKIP_CUTSCENE_TIME: 0.2,
|
||||||
|
TL_FIRST_ROOM: false,
|
||||||
|
TEXT_SPEED: 0.1,
|
||||||
|
AUTO_CONTINUE_TEXT: false,
|
||||||
|
USE_TRANSLATIONS: false,
|
||||||
|
GIBBERISH_SPOKEN_TEXT: false,
|
||||||
|
GIBBERISH_DIALOG_OPTIONS: false,
|
||||||
|
DIALOG_STYLE: DialogStyle.ABOVE_CHARACTER,
|
||||||
|
INVENTORY_LIMIT: 0,
|
||||||
|
INVENTORY_ITEMS_ON_START: [],
|
||||||
|
ASEPRITE_IMPORT_ANIMATION: true,
|
||||||
|
ASEPRITE_LOOP_ANIMATION: true,
|
||||||
|
ASEPRITE_PROPS_VISIBLE: true,
|
||||||
|
ASEPRITE_PROPS_CLICKABLE: true,
|
||||||
|
ASEPRITE_WIPE_OLD_ANIMATIONS: true,
|
||||||
|
PIXEL_ART_TEXTURES: false,
|
||||||
|
PIXEL_PERFECT: false,
|
||||||
|
PREFIX_CHARACTER: "_",
|
||||||
|
MUSIC_PREFIXES: "mx,",
|
||||||
|
SOUND_EFFECT_PREFIXES: "sfx,",
|
||||||
|
VOICE_PREFIXES: "vo,",
|
||||||
|
UI_PREFIXES: "ui,",
|
||||||
|
DEV_USE_ADDON_TEMPLATE: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
static func initialize_project_settings():
|
||||||
|
# ---- GUI -------------------------------------------------------------------------------------
|
||||||
|
_initialize_project_setting(SCALE_GUI, TYPE_BOOL)
|
||||||
|
_initialize_project_setting(FADE_COLOR, TYPE_COLOR)
|
||||||
|
_initialize_project_setting(SKIP_CUTSCENE_TIME, TYPE_FLOAT)
|
||||||
|
_initialize_project_setting(TL_FIRST_ROOM, TYPE_BOOL)
|
||||||
|
|
||||||
|
# ---- Dialogs ---------------------------------------------------------------------------------
|
||||||
|
_initialize_project_setting(TEXT_SPEED, TYPE_FLOAT, PROPERTY_HINT_RANGE, "0.0,0.1")
|
||||||
|
_initialize_project_setting(AUTO_CONTINUE_TEXT, TYPE_BOOL)
|
||||||
|
#_initialize_project_setting(USE_TRANSLATIONS, TYPE_BOOL)
|
||||||
|
#_initialize_project_setting(
|
||||||
|
#DIALOG_STYLE,
|
||||||
|
#TYPE_INT,
|
||||||
|
#PROPERTY_HINT_ENUM,
|
||||||
|
## TODO: Add other options: Portrait Above Character, Bubble Above Character
|
||||||
|
#"Above Character,Portrait,Caption"
|
||||||
|
#)
|
||||||
|
_initialize_project_setting(GIBBERISH_SPOKEN_TEXT, TYPE_BOOL)
|
||||||
|
_initialize_project_setting(GIBBERISH_DIALOG_OPTIONS, TYPE_BOOL)
|
||||||
|
|
||||||
|
# ---- Inventory -------------------------------------------------------------------------------
|
||||||
|
_initialize_project_setting(INVENTORY_LIMIT, TYPE_INT)
|
||||||
|
_initialize_project_setting(
|
||||||
|
INVENTORY_ITEMS_ON_START,
|
||||||
|
TYPE_ARRAY,
|
||||||
|
PROPERTY_HINT_TYPE_STRING,
|
||||||
|
"%d:" % [TYPE_STRING]
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---- Aseprite Importing ----------------------------------------------------------------------
|
||||||
|
_initialize_project_setting(ASEPRITE_IMPORT_ANIMATION, TYPE_BOOL)
|
||||||
|
_initialize_project_setting(ASEPRITE_LOOP_ANIMATION, TYPE_BOOL)
|
||||||
|
_initialize_project_setting(ASEPRITE_PROPS_VISIBLE, TYPE_BOOL)
|
||||||
|
_initialize_project_setting(ASEPRITE_PROPS_CLICKABLE, TYPE_BOOL)
|
||||||
|
_initialize_project_setting(ASEPRITE_WIPE_OLD_ANIMATIONS, TYPE_BOOL)
|
||||||
|
|
||||||
|
# ---- Pixel game ------------------------------------------------------------------------------
|
||||||
|
_initialize_project_setting(PIXEL_ART_TEXTURES, TYPE_BOOL)
|
||||||
|
_initialize_project_setting(PIXEL_PERFECT, TYPE_BOOL)
|
||||||
|
|
||||||
|
# ---- Audio -----------------------------------------------------------------------------------
|
||||||
|
_initialize_project_setting(PREFIX_CHARACTER, TYPE_STRING)
|
||||||
|
_initialize_project_setting(MUSIC_PREFIXES, TYPE_STRING)
|
||||||
|
_initialize_project_setting(SOUND_EFFECT_PREFIXES, TYPE_STRING)
|
||||||
|
_initialize_project_setting(VOICE_PREFIXES, TYPE_STRING)
|
||||||
|
_initialize_project_setting(UI_PREFIXES, TYPE_STRING)
|
||||||
|
|
||||||
|
# ---- DEV -------------------------------------------------------------------------------------
|
||||||
|
_initialize_advanced_project_setting(DEV_USE_ADDON_TEMPLATE, TYPE_BOOL)
|
||||||
|
|
||||||
|
ProjectSettings.save()
|
||||||
|
|
||||||
|
|
||||||
|
static func set_project_setting(key: String, value) -> void:
|
||||||
|
ProjectSettings.set_setting(key, value)
|
||||||
|
ProjectSettings.save()
|
||||||
|
|
||||||
|
|
||||||
|
# ---- GUI -----------------------------------------------------------------------------------------
|
||||||
|
static func is_scale_gui() -> bool:
|
||||||
|
return _get_project_setting(SCALE_GUI)
|
||||||
|
|
||||||
|
|
||||||
|
static func get_fade_color() -> Color:
|
||||||
|
return _get_project_setting(FADE_COLOR)
|
||||||
|
|
||||||
|
|
||||||
|
static func get_skip_cutscene_time() -> float:
|
||||||
|
return _get_project_setting(SKIP_CUTSCENE_TIME)
|
||||||
|
|
||||||
|
static func should_show_tl_in_first_room() -> bool:
|
||||||
|
return _get_project_setting(TL_FIRST_ROOM)
|
||||||
|
|
||||||
|
|
||||||
|
# ---- Dialogs -------------------------------------------------------------------------------------
|
||||||
|
static func get_text_speed() -> float:
|
||||||
|
return _get_project_setting(TEXT_SPEED)
|
||||||
|
|
||||||
|
|
||||||
|
static func is_auto_continue_text() -> bool:
|
||||||
|
return _get_project_setting(AUTO_CONTINUE_TEXT)
|
||||||
|
|
||||||
|
|
||||||
|
static func is_use_translations() -> bool:
|
||||||
|
return _get_project_setting(USE_TRANSLATIONS)
|
||||||
|
|
||||||
|
|
||||||
|
static func get_dialog_style() -> int:
|
||||||
|
return _get_project_setting(DIALOG_STYLE)
|
||||||
|
|
||||||
|
|
||||||
|
static func should_talk_gibberish() -> bool:
|
||||||
|
return _get_project_setting(GIBBERISH_SPOKEN_TEXT)
|
||||||
|
|
||||||
|
|
||||||
|
static func should_dialog_options_be_gibberish() -> bool:
|
||||||
|
return _get_project_setting(GIBBERISH_DIALOG_OPTIONS)
|
||||||
|
|
||||||
|
|
||||||
|
# ---- Inventory -----------------------------------------------------------------------------------
|
||||||
|
static func get_inventory_limit() -> int:
|
||||||
|
return _get_project_setting(INVENTORY_LIMIT)
|
||||||
|
|
||||||
|
|
||||||
|
static func set_inventory_items_on_start(items: Array) -> void:
|
||||||
|
set_project_setting(INVENTORY_ITEMS_ON_START, items)
|
||||||
|
|
||||||
|
|
||||||
|
static func get_inventory_items_on_start() -> Array:
|
||||||
|
return _get_project_setting(INVENTORY_ITEMS_ON_START)
|
||||||
|
|
||||||
|
|
||||||
|
# ---- Aseprite Importing --------------------------------------------------------------------------
|
||||||
|
static func is_default_animation_import_enabled() -> bool:
|
||||||
|
return _get_project_setting(ASEPRITE_IMPORT_ANIMATION)
|
||||||
|
|
||||||
|
|
||||||
|
static func is_default_animation_loop_enabled() -> bool:
|
||||||
|
return _get_project_setting(ASEPRITE_LOOP_ANIMATION)
|
||||||
|
|
||||||
|
|
||||||
|
static func is_default_animation_prop_visible() -> bool:
|
||||||
|
return _get_project_setting(ASEPRITE_PROPS_VISIBLE)
|
||||||
|
|
||||||
|
|
||||||
|
static func is_default_animation_prop_clickable() -> bool:
|
||||||
|
return _get_project_setting(ASEPRITE_PROPS_CLICKABLE)
|
||||||
|
|
||||||
|
|
||||||
|
static func is_default_wipe_old_anims_enabled() -> bool:
|
||||||
|
return _get_project_setting(ASEPRITE_WIPE_OLD_ANIMATIONS)
|
||||||
|
|
||||||
|
|
||||||
|
# ---- Pixel game ----------------------------------------------------------------------------------
|
||||||
|
static func set_pixel_art_textures(use_pixel_art_textures: bool) -> void:
|
||||||
|
set_project_setting(PIXEL_ART_TEXTURES, use_pixel_art_textures)
|
||||||
|
|
||||||
|
|
||||||
|
static func is_pixel_art_textures() -> bool:
|
||||||
|
return _get_project_setting(PIXEL_ART_TEXTURES)
|
||||||
|
|
||||||
|
|
||||||
|
static func is_pixel_perfect() -> bool:
|
||||||
|
return _get_project_setting(PIXEL_PERFECT)
|
||||||
|
|
||||||
|
|
||||||
|
# ---- Audio ---------------------------------------------------------------------------------------
|
||||||
|
static func get_prefix_character() -> String:
|
||||||
|
return _get_project_setting(PREFIX_CHARACTER)
|
||||||
|
|
||||||
|
|
||||||
|
static func get_music_prefixes() -> String:
|
||||||
|
return _get_project_setting(MUSIC_PREFIXES)
|
||||||
|
|
||||||
|
|
||||||
|
static func get_sound_effect_prefixes() -> String:
|
||||||
|
return _get_project_setting(SOUND_EFFECT_PREFIXES)
|
||||||
|
|
||||||
|
|
||||||
|
static func get_voice_prefixes() -> String:
|
||||||
|
return _get_project_setting(VOICE_PREFIXES)
|
||||||
|
|
||||||
|
|
||||||
|
static func get_ui_prefixes() -> String:
|
||||||
|
return _get_project_setting(UI_PREFIXES)
|
||||||
|
|
||||||
|
|
||||||
|
# ---- DEV -----------------------------------------------------------------------------------------
|
||||||
|
static func is_use_addon_template() -> bool:
|
||||||
|
return _get_project_setting(DEV_USE_ADDON_TEMPLATE)
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
static func _initialize_project_setting(
|
||||||
|
key: String, type: int, hint := PROPERTY_HINT_NONE, hint_string := ""
|
||||||
|
) -> void:
|
||||||
|
_create_setting(key, type, hint, hint_string)
|
||||||
|
ProjectSettings.set_as_basic(key, true)
|
||||||
|
|
||||||
|
|
||||||
|
static func _initialize_advanced_project_setting(
|
||||||
|
key: String, type: int, hint := PROPERTY_HINT_NONE, hint_string := ""
|
||||||
|
) -> void:
|
||||||
|
_create_setting(key, type, hint, hint_string)
|
||||||
|
|
||||||
|
|
||||||
|
static func _create_setting(
|
||||||
|
key: String, type: int, hint := PROPERTY_HINT_NONE, hint_string := ""
|
||||||
|
) -> void:
|
||||||
|
ProjectSettings.set_setting(key, ProjectSettings.get_setting(key, defaults[key]))
|
||||||
|
ProjectSettings.set_initial_value(key, defaults[key])
|
||||||
|
ProjectSettings.add_property_info({
|
||||||
|
"name": key,
|
||||||
|
"type": type,
|
||||||
|
"hint": hint,
|
||||||
|
"hint_string": hint_string,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
static func _get_project_setting(key: String):
|
||||||
|
var p = ProjectSettings.get_setting(key)
|
||||||
|
return p if p != null else defaults[key]
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
1
addons/popochiu/editor/config/config.gd.uid
Normal file
1
addons/popochiu/editor/config/config.gd.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://blq0g140jdg7d
|
128
addons/popochiu/editor/config/editor_config.gd
Normal file
128
addons/popochiu/editor/config/editor_config.gd
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
@tool
|
||||||
|
class_name PopochiuEditorConfig
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
enum Icons { COLLAPSED, EXPANDED }
|
||||||
|
|
||||||
|
# ASEPRITE IMPORTER --------------------------------------------------------------------------------
|
||||||
|
const ASEPRITE_IMPORTER_ENABLED = "popochiu/import/aseprite/enable_aseprite_importer"
|
||||||
|
const ASEPRITE_COMMAND_PATH = "popochiu/import/aseprite/command_path"
|
||||||
|
const ASEPRITE_REMOVE_JSON_FILE = "popochiu/import/aseprite/remove_json_file"
|
||||||
|
|
||||||
|
# GIZMOS -------------------------------------------------------------------------------------------
|
||||||
|
const GIZMOS_FONT_SIZE = "popochiu/gizmos/font_size"
|
||||||
|
const GIZMOS_BASELINE_COLOR = "popochiu/gizmos/baseline_color"
|
||||||
|
const GIZMOS_WALK_TO_POINT_COLOR = "popochiu/gizmos/walk_to_point_color"
|
||||||
|
const GIZMOS_LOOK_AT_POINT_COLOR = "popochiu/gizmos/look_at_point_color"
|
||||||
|
const GIZMOS_DIALOG_POS_COLOR = "popochiu/gizmos/dialog_position_color"
|
||||||
|
const GIZMOS_COLOR_TOOLBAR_BUTTONS = "popochiu/gizmos/apply_colors_to_toolbar_buttons"
|
||||||
|
const GIZMOS_HANDLER_SIZE = "popochiu/gizmos/handler_size"
|
||||||
|
const GIZMOS_SHOW_CONNECTORS = "popochiu/gizmos/show_connectors"
|
||||||
|
const GIZMOS_SHOW_OUTLINE = "popochiu/gizmos/show_handler_outline"
|
||||||
|
const GIZMOS_SHOW_NODE_NAME = "popochiu/gizmos/show_node_name"
|
||||||
|
const GIZMOS_ALWAYS_SHOW_WA = "popochiu/gizmos/always_show_walkable_areas"
|
||||||
|
|
||||||
|
# Settings default values
|
||||||
|
static var defaults := {
|
||||||
|
ASEPRITE_IMPORTER_ENABLED: false,
|
||||||
|
ASEPRITE_COMMAND_PATH: _default_aseprite_command(),
|
||||||
|
ASEPRITE_REMOVE_JSON_FILE: true,
|
||||||
|
GIZMOS_FONT_SIZE: _default_font_size(),
|
||||||
|
GIZMOS_BASELINE_COLOR: Color.CYAN,
|
||||||
|
GIZMOS_WALK_TO_POINT_COLOR: Color.GREEN,
|
||||||
|
GIZMOS_LOOK_AT_POINT_COLOR: Color.RED,
|
||||||
|
GIZMOS_DIALOG_POS_COLOR: Color.MAGENTA,
|
||||||
|
GIZMOS_COLOR_TOOLBAR_BUTTONS: true,
|
||||||
|
GIZMOS_HANDLER_SIZE: 32,
|
||||||
|
GIZMOS_SHOW_CONNECTORS: true,
|
||||||
|
GIZMOS_SHOW_OUTLINE: true,
|
||||||
|
GIZMOS_SHOW_NODE_NAME: true,
|
||||||
|
GIZMOS_ALWAYS_SHOW_WA: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
static var editor_settings: EditorSettings
|
||||||
|
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
static func initialize_editor_settings():
|
||||||
|
editor_settings = EditorInterface.get_editor_settings()
|
||||||
|
|
||||||
|
# Aseprite importer
|
||||||
|
_initialize_editor_setting(ASEPRITE_IMPORTER_ENABLED, TYPE_BOOL)
|
||||||
|
_initialize_editor_setting(ASEPRITE_COMMAND_PATH, TYPE_STRING)
|
||||||
|
_initialize_editor_setting(ASEPRITE_REMOVE_JSON_FILE, TYPE_BOOL)
|
||||||
|
# Gizmos
|
||||||
|
_initialize_editor_setting(GIZMOS_BASELINE_COLOR, TYPE_COLOR)
|
||||||
|
_initialize_editor_setting(GIZMOS_WALK_TO_POINT_COLOR, TYPE_COLOR)
|
||||||
|
_initialize_editor_setting(GIZMOS_LOOK_AT_POINT_COLOR, TYPE_COLOR)
|
||||||
|
_initialize_editor_setting(GIZMOS_DIALOG_POS_COLOR, TYPE_COLOR)
|
||||||
|
_initialize_editor_setting(GIZMOS_COLOR_TOOLBAR_BUTTONS, TYPE_BOOL)
|
||||||
|
_initialize_editor_setting(GIZMOS_HANDLER_SIZE, TYPE_INT, PROPERTY_HINT_RANGE, "4,64")
|
||||||
|
_initialize_editor_setting(GIZMOS_FONT_SIZE, TYPE_INT, PROPERTY_HINT_RANGE, "4,64")
|
||||||
|
_initialize_editor_setting(GIZMOS_SHOW_CONNECTORS, TYPE_BOOL)
|
||||||
|
_initialize_editor_setting(GIZMOS_SHOW_OUTLINE, TYPE_BOOL)
|
||||||
|
_initialize_editor_setting(GIZMOS_SHOW_NODE_NAME, TYPE_BOOL)
|
||||||
|
_initialize_editor_setting(GIZMOS_ALWAYS_SHOW_WA, TYPE_BOOL)
|
||||||
|
|
||||||
|
|
||||||
|
static func get_icon(icon: Icons) -> Texture2D:
|
||||||
|
match icon:
|
||||||
|
Icons.COLLAPSED:
|
||||||
|
return EditorInterface.get_base_control().get_theme_icon(
|
||||||
|
"GuiTreeArrowRight", "EditorIcons"
|
||||||
|
)
|
||||||
|
Icons.EXPANDED:
|
||||||
|
return EditorInterface.get_base_control().get_theme_icon(
|
||||||
|
"GuiTreeArrowDown", "EditorIcons"
|
||||||
|
)
|
||||||
|
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
# ASEPRITE IMPORTER --------------------------------------------------------------------------------
|
||||||
|
static func aseprite_importer_enabled() -> bool:
|
||||||
|
return get_editor_setting(ASEPRITE_IMPORTER_ENABLED)
|
||||||
|
|
||||||
|
|
||||||
|
static func get_command() -> String:
|
||||||
|
return get_editor_setting(ASEPRITE_COMMAND_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
static func should_remove_source_files() -> bool:
|
||||||
|
return get_editor_setting(ASEPRITE_REMOVE_JSON_FILE)
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
static func _default_aseprite_command() -> String:
|
||||||
|
return 'aseprite'
|
||||||
|
|
||||||
|
|
||||||
|
static func _default_font_size() -> int:
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
return EditorInterface.get_editor_theme().default_font_size
|
||||||
|
return 16
|
||||||
|
|
||||||
|
|
||||||
|
static func _initialize_editor_setting(
|
||||||
|
key: String, type: int, hint: int = PROPERTY_HINT_NONE, hint_string : String = ""
|
||||||
|
) -> void:
|
||||||
|
if editor_settings.has_setting(key): return
|
||||||
|
|
||||||
|
editor_settings.set_setting(key, defaults[key])
|
||||||
|
editor_settings.set_initial_value(key, defaults[key], false)
|
||||||
|
editor_settings.add_property_info({
|
||||||
|
"name": key,
|
||||||
|
"type": type,
|
||||||
|
"hint": hint,
|
||||||
|
"hint_string": hint_string
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
static func get_editor_setting(key: String):
|
||||||
|
var e = editor_settings.get_setting(key)
|
||||||
|
return e if e != null else defaults[e]
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
1
addons/popochiu/editor/config/editor_config.gd.uid
Normal file
1
addons/popochiu/editor/config/editor_config.gd.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://c2xn0h6paw2xy
|
70
addons/popochiu/editor/config/local_obj_config.gd
Normal file
70
addons/popochiu/editor/config/local_obj_config.gd
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
@tool
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
|
||||||
|
const LOCAL_OBJ_CONFIG_META_NAME = "_popochiu_aseprite_config_"
|
||||||
|
const LOCAL_OBJ_CONFIG_MARKER = "popochiu_aseprite_config"
|
||||||
|
const SEPARATOR = "|="
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░
|
||||||
|
static func encode(object: Dictionary):
|
||||||
|
var text = "%s\n" % LOCAL_OBJ_CONFIG_MARKER
|
||||||
|
|
||||||
|
for prop in object:
|
||||||
|
text += "%s%s%s\n" % [prop, SEPARATOR, object[prop]]
|
||||||
|
|
||||||
|
return Marshalls.utf8_to_base64(text)
|
||||||
|
|
||||||
|
|
||||||
|
static func decode(string: String):
|
||||||
|
var decoded = _decode_base64(string)
|
||||||
|
if not _is_valid_config(decoded):
|
||||||
|
return null
|
||||||
|
|
||||||
|
print(decoded)
|
||||||
|
|
||||||
|
var cfg = decoded.split("\n")
|
||||||
|
var config = {}
|
||||||
|
for c in cfg:
|
||||||
|
var parts = c.split(SEPARATOR, 1)
|
||||||
|
if parts.size() == 2:
|
||||||
|
var key = parts[0].strip_edges()
|
||||||
|
var value = parts[1].strip_edges()
|
||||||
|
|
||||||
|
#Convert bool properties
|
||||||
|
if key in ["only_visible_layers", "wipe_old_anims", "op_exp"]:
|
||||||
|
match value:
|
||||||
|
"True":
|
||||||
|
config[key] = true
|
||||||
|
"False":
|
||||||
|
config[key] = false
|
||||||
|
_:
|
||||||
|
config[key] = false
|
||||||
|
else:
|
||||||
|
config[key] = value
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
static func load_config(node:Node):
|
||||||
|
# Check if node is not null to avoid showing error messages in Output when inspecting nodes in
|
||||||
|
# the Debugger
|
||||||
|
if node and node.has_meta(LOCAL_OBJ_CONFIG_META_NAME):
|
||||||
|
return node.get_meta(LOCAL_OBJ_CONFIG_META_NAME)
|
||||||
|
|
||||||
|
|
||||||
|
static func save_config(node:Node, cfg:Dictionary):
|
||||||
|
node.set_meta(LOCAL_OBJ_CONFIG_META_NAME, cfg)
|
||||||
|
|
||||||
|
|
||||||
|
# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░
|
||||||
|
static func _decode_base64(string: String):
|
||||||
|
if string != "":
|
||||||
|
return Marshalls.base64_to_utf8(string)
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
static func _is_valid_config(cfg) -> bool:
|
||||||
|
return cfg != null and cfg.begins_with(LOCAL_OBJ_CONFIG_MARKER)
|
1
addons/popochiu/editor/config/local_obj_config.gd.uid
Normal file
1
addons/popochiu/editor/config/local_obj_config.gd.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://d26rqfocnhh7v
|
75
addons/popochiu/editor/config/result_codes.gd
Normal file
75
addons/popochiu/editor/config/result_codes.gd
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
@tool
|
||||||
|
extends RefCounted
|
||||||
|
class_name ResultCodes
|
||||||
|
|
||||||
|
enum {
|
||||||
|
## Base codes
|
||||||
|
FAILURE, # generic failure state
|
||||||
|
SUCCESS, # generic success state
|
||||||
|
## Aseprite importer errors
|
||||||
|
ERR_ASEPRITE_CMD_NOT_FULL_PATH,
|
||||||
|
ERR_ASEPRITE_CMD_NOT_FOUND,
|
||||||
|
ERR_SOURCE_FILE_NOT_FOUND,
|
||||||
|
ERR_OUTPUT_FOLDER_NOT_FOUND,
|
||||||
|
ERR_ASEPRITE_EXPORT_FAILED,
|
||||||
|
ERR_UNKNOWN_EXPORT_MODE,
|
||||||
|
ERR_NO_VALID_LAYERS_FOUND,
|
||||||
|
ERR_INVALID_ASEPRITE_SPRITESHEET,
|
||||||
|
ERR_NO_ANIMATION_PLAYER_FOUND,
|
||||||
|
ERR_NO_SPRITE_FOUND,
|
||||||
|
ERR_UNNAMED_TAG_DETECTED,
|
||||||
|
ERR_TAGS_OPTIONS_ARRAY_EMPTY,
|
||||||
|
## Popochiu Object factories errors
|
||||||
|
ERR_CANT_CREATE_OBJ_FOLDER,
|
||||||
|
ERR_CANT_CREATE_OBJ_STATE,
|
||||||
|
ERR_CANT_OPEN_OBJ_SCRIPT_TEMPLATE,
|
||||||
|
ERR_CANT_CREATE_OBJ_SCRIPT,
|
||||||
|
ERR_CANT_SAVE_OBJ_SCENE,
|
||||||
|
ERR_CANT_SAVE_OBJ_RESOURCE,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░
|
||||||
|
static func get_error_message(code: int):
|
||||||
|
## TODO: these messages are a bit dull, having params would be better.
|
||||||
|
## Maybe add a param argument
|
||||||
|
match code:
|
||||||
|
# Aseprite importers error messages
|
||||||
|
ERR_ASEPRITE_CMD_NOT_FULL_PATH:
|
||||||
|
return "Aseprite command not found at given path. Please check \"Editor Settings > Popochiu > Import > Command Path\" to hold the FULL path to a valid Aseprite executable."
|
||||||
|
ERR_ASEPRITE_CMD_NOT_FOUND:
|
||||||
|
return "Aseprite command failed. Please, check if the right command is in your PATH or configured through \"Editor Settings > Popochiu > Import > Command Path\"."
|
||||||
|
ERR_SOURCE_FILE_NOT_FOUND:
|
||||||
|
return "Source file does not exist"
|
||||||
|
ERR_OUTPUT_FOLDER_NOT_FOUND:
|
||||||
|
return "Output location does not exist"
|
||||||
|
ERR_ASEPRITE_EXPORT_FAILED:
|
||||||
|
return "Unable to import file"
|
||||||
|
ERR_INVALID_ASEPRITE_SPRITESHEET:
|
||||||
|
return "Aseprite generated invalid data file"
|
||||||
|
ERR_NO_VALID_LAYERS_FOUND:
|
||||||
|
return "No valid layers found"
|
||||||
|
ERR_NO_ANIMATION_PLAYER_FOUND:
|
||||||
|
return "No AnimationPlayer found in target node"
|
||||||
|
ERR_NO_SPRITE_FOUND:
|
||||||
|
return "No sprite found in target node"
|
||||||
|
ERR_UNNAMED_TAG_DETECTED:
|
||||||
|
return "Unnamed tag detected"
|
||||||
|
ERR_TAGS_OPTIONS_ARRAY_EMPTY:
|
||||||
|
return "Tags options array is empty"
|
||||||
|
# Popochiu object factories error messages
|
||||||
|
ERR_CANT_CREATE_OBJ_FOLDER:
|
||||||
|
return "Can't create folder to host new Popochiu object"
|
||||||
|
ERR_CANT_CREATE_OBJ_STATE:
|
||||||
|
return "Can't create new Popochiu object's state resource (_state.tres, _state.gd)"
|
||||||
|
ERR_CANT_OPEN_OBJ_SCRIPT_TEMPLATE:
|
||||||
|
return "Can't open script template for new Popochiu object"
|
||||||
|
ERR_CANT_CREATE_OBJ_SCRIPT:
|
||||||
|
return "Can't create new Popochiu object's script file (.gd)"
|
||||||
|
ERR_CANT_SAVE_OBJ_SCENE:
|
||||||
|
return "Can't create new Popochiu object's scene (.tscn)"
|
||||||
|
ERR_CANT_SAVE_OBJ_RESOURCE:
|
||||||
|
return "Can't create new Popochiu object's resource (.tres)"
|
||||||
|
# Generic error message
|
||||||
|
_:
|
||||||
|
return "Import failed with code %d" % code
|
1
addons/popochiu/editor/config/result_codes.gd.uid
Normal file
1
addons/popochiu/editor/config/result_codes.gd.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://cbxik5o1u00hf
|
237
addons/popochiu/editor/factories/factory_base_popochiu_obj.gd
Normal file
237
addons/popochiu/editor/factories/factory_base_popochiu_obj.gd
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
const BASE_STATE_TEMPLATE := "res://addons/popochiu/engine/templates/%s_state_template.gd"
|
||||||
|
const BASE_SCRIPT_TEMPLATE := "res://addons/popochiu/engine/templates/%s_template.gd"
|
||||||
|
const BASE_SCENE_PATH := "res://addons/popochiu/engine/objects/%s/popochiu_%s.tscn"
|
||||||
|
const EMPTY_SCRIPT := "res://addons/popochiu/engine/templates/empty_script_template.gd"
|
||||||
|
|
||||||
|
# The following variables are setup on creation Names variants and name parameter passed to the
|
||||||
|
# create method.
|
||||||
|
var _path_template := "" # always set by child class
|
||||||
|
var _snake_name := ""
|
||||||
|
var _pascal_name := ""
|
||||||
|
var _path_base := ""
|
||||||
|
var _path_scene = ""
|
||||||
|
var _path_resource = ""
|
||||||
|
var _path_state = ""
|
||||||
|
var _path_script := ""
|
||||||
|
# The following variables are setup by the sub-class constructor to define the type of object to be
|
||||||
|
# processed
|
||||||
|
# TODO: reduce this to just "type", too much redundancy
|
||||||
|
var _type := -1
|
||||||
|
var _type_label := ""
|
||||||
|
var _type_target := ""
|
||||||
|
var _type_method: Callable
|
||||||
|
# The following variables are references to the elements generated for the creation of the new
|
||||||
|
# Popochiu object, such as resources, scenes, scripts, state scripts, etc
|
||||||
|
var _scene: Node
|
||||||
|
var _resource: Resource
|
||||||
|
var _state_resource: Resource
|
||||||
|
var _script: Resource
|
||||||
|
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
func get_obj_scene() -> Node:
|
||||||
|
return _scene
|
||||||
|
|
||||||
|
|
||||||
|
func get_snake_name() -> String:
|
||||||
|
return _snake_name
|
||||||
|
|
||||||
|
|
||||||
|
func get_obj_resource() -> Resource:
|
||||||
|
return _resource
|
||||||
|
|
||||||
|
|
||||||
|
func get_state_resource() -> Resource:
|
||||||
|
return _state_resource
|
||||||
|
|
||||||
|
|
||||||
|
func get_obj_script() -> Resource:
|
||||||
|
return _script
|
||||||
|
|
||||||
|
|
||||||
|
func get_scene_path() -> String:
|
||||||
|
return _path_scene
|
||||||
|
|
||||||
|
|
||||||
|
func get_type() -> int:
|
||||||
|
return _type
|
||||||
|
|
||||||
|
|
||||||
|
func get_type_method() -> Callable:
|
||||||
|
return _type_method
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _setup_name(obj_name: String) -> void:
|
||||||
|
_pascal_name = obj_name.to_pascal_case()
|
||||||
|
_snake_name = obj_name.to_snake_case()
|
||||||
|
_path_base = _path_template % [_snake_name, _snake_name]
|
||||||
|
_path_script = _path_base + ".gd"
|
||||||
|
_path_state = _path_base + "_state.gd"
|
||||||
|
_path_resource = _path_base + ".tres"
|
||||||
|
_path_scene = _path_base + ".tscn"
|
||||||
|
|
||||||
|
|
||||||
|
func _create_obj_folder() -> int:
|
||||||
|
# TODO: Remove created files if the creation process failed.
|
||||||
|
if DirAccess.make_dir_recursive_absolute(_path_base.get_base_dir()) != OK:
|
||||||
|
PopochiuUtils.print_error(
|
||||||
|
"Could not create %s directory: %s" %
|
||||||
|
[_path_base.get_base_dir(), _pascal_name]
|
||||||
|
)
|
||||||
|
return ResultCodes.ERR_CANT_CREATE_OBJ_FOLDER
|
||||||
|
return ResultCodes.SUCCESS
|
||||||
|
|
||||||
|
|
||||||
|
func _create_state_resource() -> int:
|
||||||
|
var state_template: Script = load(
|
||||||
|
BASE_STATE_TEMPLATE % _type_label
|
||||||
|
).duplicate()
|
||||||
|
|
||||||
|
if ResourceSaver.save(state_template, _path_state) != OK:
|
||||||
|
PopochiuUtils.print_error(
|
||||||
|
"Could not create %s state script: %s" %
|
||||||
|
[_type_label, _pascal_name]
|
||||||
|
)
|
||||||
|
return ResultCodes.FAILURE
|
||||||
|
|
||||||
|
_state_resource = load(_path_state).new()
|
||||||
|
_state_resource.script_name = _pascal_name
|
||||||
|
_state_resource.scene = _path_scene
|
||||||
|
_state_resource.resource_name = _pascal_name
|
||||||
|
|
||||||
|
if ResourceSaver.save(_state_resource, _path_resource) != OK:
|
||||||
|
PopochiuUtils.print_error(
|
||||||
|
"Could not create state resource for %s: %s" %
|
||||||
|
[_type_label, _pascal_name]
|
||||||
|
)
|
||||||
|
return ResultCodes.ERR_CANT_CREATE_OBJ_STATE
|
||||||
|
|
||||||
|
return ResultCodes.SUCCESS
|
||||||
|
|
||||||
|
|
||||||
|
func _copy_script_template() -> int:
|
||||||
|
var _script: Script = load(
|
||||||
|
BASE_SCRIPT_TEMPLATE % _type_label
|
||||||
|
).duplicate()
|
||||||
|
|
||||||
|
if ResourceSaver.save( _script, _path_script) != OK:
|
||||||
|
PopochiuUtils.print_error(
|
||||||
|
"Could not create %s script: %s" %
|
||||||
|
[_type_label, _path_script]
|
||||||
|
)
|
||||||
|
return ResultCodes.ERR_CANT_CREATE_OBJ_SCRIPT
|
||||||
|
|
||||||
|
return ResultCodes.SUCCESS
|
||||||
|
|
||||||
|
|
||||||
|
## Create the script for the object based on the template of its type.
|
||||||
|
func _create_script_from_template() -> int:
|
||||||
|
var script_template_file = FileAccess.open(
|
||||||
|
BASE_SCRIPT_TEMPLATE % _type_label, FileAccess.READ
|
||||||
|
)
|
||||||
|
|
||||||
|
if script_template_file == null:
|
||||||
|
PopochiuUtils.print_error(
|
||||||
|
"Could not read script template from %s" %
|
||||||
|
[BASE_SCRIPT_TEMPLATE % _type_label]
|
||||||
|
)
|
||||||
|
return ResultCodes.ERR_CANT_OPEN_OBJ_SCRIPT_TEMPLATE
|
||||||
|
|
||||||
|
var new_code: String = script_template_file.get_as_text()
|
||||||
|
script_template_file.close()
|
||||||
|
|
||||||
|
new_code = new_code.replace(
|
||||||
|
"%s_state_template" % _type_label,
|
||||||
|
"%s_%s_state" % [_type_label, _snake_name]
|
||||||
|
)
|
||||||
|
new_code = new_code.replace(
|
||||||
|
"Data = null",
|
||||||
|
'Data = load("%s.tres")' % _path_base
|
||||||
|
)
|
||||||
|
new_code = new_code.replace("PopochiuUtils.e", "E")
|
||||||
|
|
||||||
|
_script = load(EMPTY_SCRIPT).duplicate()
|
||||||
|
_script.source_code = new_code
|
||||||
|
|
||||||
|
if ResourceSaver.save( _script, _path_script) != OK:
|
||||||
|
PopochiuUtils.print_error(
|
||||||
|
"Could not create %s script: %s" %
|
||||||
|
[_type_label, _path_script]
|
||||||
|
)
|
||||||
|
return ResultCodes.ERR_CANT_CREATE_OBJ_SCRIPT
|
||||||
|
|
||||||
|
return ResultCodes.SUCCESS
|
||||||
|
|
||||||
|
|
||||||
|
func _save_obj_scene(obj: Node) -> int:
|
||||||
|
var packed_scene: PackedScene = PackedScene.new()
|
||||||
|
packed_scene.pack(obj)
|
||||||
|
if ResourceSaver.save(packed_scene, _path_scene) != OK:
|
||||||
|
PopochiuUtils.print_error(
|
||||||
|
"Could not create %s: %s" %
|
||||||
|
[_type_label, _path_script]
|
||||||
|
)
|
||||||
|
return ResultCodes.ERR_CANT_SAVE_OBJ_SCENE
|
||||||
|
# Load the scene to be get by the calling code
|
||||||
|
# Instancing the created .tscn file fixes #58
|
||||||
|
_scene = (load(_path_scene) as PackedScene).instantiate(PackedScene.GEN_EDIT_STATE_INSTANCE)
|
||||||
|
|
||||||
|
return ResultCodes.SUCCESS
|
||||||
|
|
||||||
|
|
||||||
|
func _save_obj_resource(obj: Resource) -> int:
|
||||||
|
if ResourceSaver.save(obj, _path_resource) != OK:
|
||||||
|
PopochiuUtils.print_error(
|
||||||
|
"Could not create %s: %s" %
|
||||||
|
[_type_label, _pascal_name]
|
||||||
|
)
|
||||||
|
return ResultCodes.ERR_CANT_SAVE_OBJ_RESOURCE
|
||||||
|
|
||||||
|
# Load the resource to be get by the calling code
|
||||||
|
_resource = load(_path_resource)
|
||||||
|
|
||||||
|
return ResultCodes.SUCCESS
|
||||||
|
|
||||||
|
|
||||||
|
## Makes a copy of the base scene for the object (e.g. popochiu_room.tscn,
|
||||||
|
## popochiu_inventory_item.tscn, popochiu_prop.tscn).
|
||||||
|
func _load_obj_base_scene() -> Node:
|
||||||
|
var obj = (
|
||||||
|
load(BASE_SCENE_PATH % [_type_label, _type_label]) as PackedScene
|
||||||
|
).instantiate(PackedScene.GEN_EDIT_STATE_MAIN_INHERITED)
|
||||||
|
|
||||||
|
# The script is assigned first so that other properties will not be
|
||||||
|
# overwritten by that assignment.
|
||||||
|
if _script != null:
|
||||||
|
obj.set_script(load(_path_script))
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
func _add_resource_to_popochiu() -> void:
|
||||||
|
# Add the created obj to Popochiu's correct list
|
||||||
|
var resource := ResourceLoader.load(_path_resource)
|
||||||
|
if PopochiuResources.set_data_value(
|
||||||
|
_type_target,
|
||||||
|
resource.script_name,
|
||||||
|
resource.resource_path
|
||||||
|
) != OK:
|
||||||
|
PopochiuUtils.print_error(
|
||||||
|
"Could not add the created %s to Popochiu: %s" %
|
||||||
|
[_type_label, _pascal_name]
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add the object to the proper singleton
|
||||||
|
PopochiuResources.update_autoloads(true)
|
||||||
|
|
||||||
|
# Update the related list in the dock
|
||||||
|
PopochiuEditorHelper.signal_bus.main_object_added.emit(_type, _pascal_name)
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dsauwqcvbelb1
|
|
@ -0,0 +1,98 @@
|
||||||
|
class_name PopochiuRoomObjFactory
|
||||||
|
extends "res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd"
|
||||||
|
|
||||||
|
const CHILD_VISIBLE_IN_ROOM_META = "_popochiu_obj_factory_child_visible_in_room_"
|
||||||
|
|
||||||
|
# The following variable is setup by the sub-class constructor to
|
||||||
|
# define the holder node for the new room object (Props, Hotspots, etc)
|
||||||
|
var _obj_room_group := ""
|
||||||
|
# The following variables are setup by the _setup_room method
|
||||||
|
var _room: Node2D = null
|
||||||
|
var _room_path := ""
|
||||||
|
var _room_dir := ""
|
||||||
|
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
func get_group() -> String:
|
||||||
|
return _obj_room_group
|
||||||
|
|
||||||
|
|
||||||
|
func create_from(node: Node, room: PopochiuRoom) -> int:
|
||||||
|
_setup_room(room)
|
||||||
|
_setup_name(node.name)
|
||||||
|
|
||||||
|
var param := _get_param(node)
|
||||||
|
param.room = room
|
||||||
|
param.obj_name = node.name
|
||||||
|
param.is_visible = node.visible
|
||||||
|
param.should_setup_room_and_name = false
|
||||||
|
param.should_add_to_room = false
|
||||||
|
param.should_create_script = !FileAccess.file_exists(_path_script)
|
||||||
|
|
||||||
|
return call("create", param)
|
||||||
|
|
||||||
|
|
||||||
|
func get_new_instance() -> PopochiuRoomObjFactory:
|
||||||
|
return new()
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _setup_room(room: PopochiuRoom) -> void:
|
||||||
|
_room = room
|
||||||
|
_room_path = _room.scene_file_path
|
||||||
|
_room_dir = _room_path.get_base_dir()
|
||||||
|
# Adding room path to room object path template
|
||||||
|
_path_template = _room_dir + _path_template
|
||||||
|
|
||||||
|
|
||||||
|
# This function adds a child to the new object scene
|
||||||
|
# marking it as "visible in room scene"
|
||||||
|
func _add_visible_child(child: Node) -> void:
|
||||||
|
child.set_meta(CHILD_VISIBLE_IN_ROOM_META, true)
|
||||||
|
_scene.add_child(child)
|
||||||
|
|
||||||
|
|
||||||
|
func _add_resource_to_room() -> void:
|
||||||
|
# Add the newly created obj to its room
|
||||||
|
_room.get_node(_obj_room_group).add_child(_scene)
|
||||||
|
|
||||||
|
# Set the ownership for the node plus all it's children
|
||||||
|
# (this address colliders, polygons, etc)
|
||||||
|
_scene.owner = _room
|
||||||
|
for child in _scene.get_children():
|
||||||
|
if child.has_meta(CHILD_VISIBLE_IN_ROOM_META):
|
||||||
|
child.owner = _room
|
||||||
|
child.remove_meta(CHILD_VISIBLE_IN_ROOM_META)
|
||||||
|
|
||||||
|
# Center the object on the scene
|
||||||
|
_scene.position = Vector2(
|
||||||
|
ProjectSettings.get_setting(PopochiuResources.DISPLAY_WIDTH),
|
||||||
|
ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT)
|
||||||
|
) / 2.0
|
||||||
|
|
||||||
|
# Save the room scene (it's open in the editor)
|
||||||
|
EditorInterface.save_scene()
|
||||||
|
|
||||||
|
|
||||||
|
func _get_param(_node: Node) -> PopochiuRoomObjFactoryParam:
|
||||||
|
return PopochiuRoomObjFactoryParam.new()
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Subclass ###################################################################################
|
||||||
|
class PopochiuRoomObjFactoryParam extends RefCounted:
|
||||||
|
var obj_name: String
|
||||||
|
var room: PopochiuRoom
|
||||||
|
var is_visible := true
|
||||||
|
var should_setup_room_and_name := true
|
||||||
|
var should_create_script := true
|
||||||
|
var should_add_to_room := true
|
||||||
|
## Property used to store the vectors stored in the [member CollisionPolygon2D.polygon] for
|
||||||
|
## [PopochiuProp], [PopochiuHotspot], and [PopochiuRegion].
|
||||||
|
var interaction_polygon := PackedVector2Array()
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://ch67numd388l
|
|
@ -0,0 +1,64 @@
|
||||||
|
extends "res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd"
|
||||||
|
class_name PopochiuCharacterFactory
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _init() -> void:
|
||||||
|
_type = PopochiuResources.Types.CHARACTER
|
||||||
|
_type_label = "character"
|
||||||
|
_type_target = "characters"
|
||||||
|
_path_template = PopochiuResources.CHARACTERS_PATH.path_join("%s/character_%s")
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
func create(obj_name: String, is_pc := false) -> int:
|
||||||
|
# If everything goes well, this won't change.
|
||||||
|
var result_code := ResultCodes.SUCCESS
|
||||||
|
|
||||||
|
# Setup the class variables that depends on the object name
|
||||||
|
_setup_name(obj_name)
|
||||||
|
|
||||||
|
# Create the folder
|
||||||
|
result_code = _create_obj_folder()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# Create the state Resource and a script
|
||||||
|
# so devs can add extra properties to that state
|
||||||
|
result_code = _create_state_resource()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# Create the script populating the template with the right references
|
||||||
|
result_code = _create_script_from_template()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# ---- LOCAL CODE ------------------------------------------------------------------------------
|
||||||
|
# Create the instance
|
||||||
|
var new_obj: PopochiuCharacter = _load_obj_base_scene()
|
||||||
|
|
||||||
|
new_obj.name = "Character" + _pascal_name
|
||||||
|
new_obj.script_name = _pascal_name
|
||||||
|
new_obj.description = _pascal_name.capitalize()
|
||||||
|
new_obj.cursor = PopochiuResources.CURSOR_TYPE.TALK
|
||||||
|
|
||||||
|
if PopochiuConfig.is_pixel_art_textures():
|
||||||
|
new_obj.get_node("Sprite2D").texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||||
|
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Save the scene (.tscn)
|
||||||
|
result_code = _save_obj_scene(new_obj)
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# Add the object to Popochiu dock list, plus open it in the editor
|
||||||
|
_add_resource_to_popochiu()
|
||||||
|
|
||||||
|
# ---- LOCAL CODE ------------------------------------------------------------------------------
|
||||||
|
# Set as PC
|
||||||
|
if is_pc:
|
||||||
|
PopochiuEditorHelper.signal_bus.pc_changed.emit(new_obj.script_name)
|
||||||
|
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
return result_code
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dstbnm6v0t6ot
|
49
addons/popochiu/editor/factories/factory_popochiu_dialog.gd
Normal file
49
addons/popochiu/editor/factories/factory_popochiu_dialog.gd
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
extends "res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd"
|
||||||
|
class_name PopochiuDialogFactory
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _init() -> void:
|
||||||
|
_type = PopochiuResources.Types.DIALOG
|
||||||
|
_type_label = "dialog"
|
||||||
|
_type_target = "dialogs"
|
||||||
|
_path_template = PopochiuResources.DIALOGS_PATH.path_join("%s/dialog_%s")
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
func create(obj_name: String) -> int:
|
||||||
|
# If everything goes well, this won't change.
|
||||||
|
var result_code := ResultCodes.SUCCESS
|
||||||
|
|
||||||
|
# Setup the class variables that depends on the object name
|
||||||
|
_setup_name(obj_name)
|
||||||
|
|
||||||
|
# Create the folder
|
||||||
|
result_code = _create_obj_folder()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# Create the script
|
||||||
|
result_code = _copy_script_template()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# ---- LOCAL CODE ------------------------------------------------------------------------------
|
||||||
|
# Create the resource (dialogs are not scenes)
|
||||||
|
var new_obj := PopochiuDialog.new()
|
||||||
|
new_obj.set_script(load(_path_script))
|
||||||
|
|
||||||
|
new_obj.script_name = _pascal_name
|
||||||
|
new_obj.resource_name = _pascal_name
|
||||||
|
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Save resource (dialogs are not scenes)
|
||||||
|
result_code = _save_obj_resource(new_obj)
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# Add the object to Popochiu dock list, plus open it in the editor
|
||||||
|
_add_resource_to_popochiu()
|
||||||
|
|
||||||
|
return result_code
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://ddk6pykrms2tq
|
|
@ -0,0 +1,76 @@
|
||||||
|
class_name PopochiuHotspotFactory
|
||||||
|
extends PopochiuRoomObjFactory
|
||||||
|
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _init() -> void:
|
||||||
|
_type = PopochiuResources.Types.HOTSPOT
|
||||||
|
_type_label = "hotspot"
|
||||||
|
_type_method = PopochiuEditorHelper.is_hotspot
|
||||||
|
_obj_room_group = "Hotspots"
|
||||||
|
_path_template = "/hotspots/%s/hotspot_%s"
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
func create(param: PopochiuHotspotFactoryParam) -> int:
|
||||||
|
# If everything goes well, this won't change.
|
||||||
|
var result_code := ResultCodes.SUCCESS
|
||||||
|
|
||||||
|
if param.should_setup_room_and_name:
|
||||||
|
_setup_room(param.room)
|
||||||
|
_setup_name(param.obj_name)
|
||||||
|
|
||||||
|
# Create the folder
|
||||||
|
result_code = _create_obj_folder()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# Create the script
|
||||||
|
if param.should_create_script:
|
||||||
|
result_code = _copy_script_template()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# ---- LOCAL CODE ------------------------------------------------------------------------------
|
||||||
|
# Create the instance
|
||||||
|
var new_obj: PopochiuHotspot = _load_obj_base_scene()
|
||||||
|
|
||||||
|
new_obj.set_script(ResourceLoader.load(_path_script))
|
||||||
|
|
||||||
|
new_obj.name = _pascal_name
|
||||||
|
new_obj.script_name = _pascal_name
|
||||||
|
new_obj.description = _snake_name.capitalize()
|
||||||
|
new_obj.cursor = PopochiuResources.CURSOR_TYPE.ACTIVE
|
||||||
|
new_obj.interaction_polygon = param.interaction_polygon
|
||||||
|
|
||||||
|
# Save the hotspot scene (.tscn) and put it into _scene class property
|
||||||
|
result_code = _save_obj_scene(new_obj)
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
if param.should_add_to_room:
|
||||||
|
# Add the object to its room
|
||||||
|
_add_resource_to_room()
|
||||||
|
|
||||||
|
return result_code
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _get_param(node: Node) -> PopochiuRoomObjFactoryParam:
|
||||||
|
var param := PopochiuHotspotFactoryParam.new()
|
||||||
|
param.is_interactive = node.clickable
|
||||||
|
# TODO: Remove this line once the last gizmos PR is merged
|
||||||
|
param.interaction_polygon = node.interaction_polygon
|
||||||
|
return param
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Subclass ###################################################################################
|
||||||
|
class PopochiuHotspotFactoryParam extends PopochiuRoomObjFactory.PopochiuRoomObjFactoryParam:
|
||||||
|
var is_interactive := true
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://bcnd7an67ulyf
|
|
@ -0,0 +1,60 @@
|
||||||
|
extends "res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd"
|
||||||
|
class_name PopochiuInventoryItemFactory
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _init() -> void:
|
||||||
|
_type = PopochiuResources.Types.INVENTORY_ITEM
|
||||||
|
_type_label = "inventory_item"
|
||||||
|
_type_target = "inventory_items"
|
||||||
|
_path_template = PopochiuResources.INVENTORY_ITEMS_PATH.path_join("%s/inventory_item_%s")
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
func create(obj_name: String) -> int:
|
||||||
|
# If everything goes well, this won't change.
|
||||||
|
var result_code := ResultCodes.SUCCESS
|
||||||
|
|
||||||
|
# Setup the class variables that depends on the object name
|
||||||
|
_setup_name(obj_name)
|
||||||
|
|
||||||
|
# Create the folder
|
||||||
|
result_code = _create_obj_folder()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# Create the state Resource and a script
|
||||||
|
# so devs can add extra properties to that state
|
||||||
|
result_code = _create_state_resource()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# Create the script populating the template with the right references
|
||||||
|
result_code = _create_script_from_template()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# ---- LOCAL CODE ------------------------------------------------------------------------------
|
||||||
|
# Create the instance
|
||||||
|
var new_obj: PopochiuInventoryItem = _load_obj_base_scene()
|
||||||
|
|
||||||
|
new_obj.name = "Item" + _pascal_name
|
||||||
|
new_obj.script_name = _pascal_name
|
||||||
|
new_obj.description = _pascal_name.capitalize()
|
||||||
|
new_obj.cursor = PopochiuResources.CURSOR_TYPE.USE
|
||||||
|
new_obj.size_flags_vertical = new_obj.SIZE_SHRINK_CENTER
|
||||||
|
|
||||||
|
if PopochiuConfig.is_pixel_art_textures():
|
||||||
|
new_obj.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||||
|
|
||||||
|
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Save the scene (.tscn)
|
||||||
|
result_code = _save_obj_scene(new_obj)
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# Add the object to Popochiu dock list, plus open it in the editor
|
||||||
|
_add_resource_to_popochiu()
|
||||||
|
|
||||||
|
return result_code
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://bym4xs81a2ph4
|
44
addons/popochiu/editor/factories/factory_popochiu_marker.gd
Normal file
44
addons/popochiu/editor/factories/factory_popochiu_marker.gd
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
class_name PopochiuMarkerFactory
|
||||||
|
extends PopochiuRoomObjFactory
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _init() -> void:
|
||||||
|
_type = PopochiuResources.Types.MARKER
|
||||||
|
_type_label = "marker"
|
||||||
|
_type_method = PopochiuEditorHelper.is_marker
|
||||||
|
_obj_room_group = "Markers"
|
||||||
|
_path_template = "/markers/%s/marker_%s"
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
#region Public #####################################################################################
|
||||||
|
func create(param: PopochiuRoomObjFactoryParam) -> int:
|
||||||
|
# If everything goes well, this won't change.
|
||||||
|
var result_code := ResultCodes.SUCCESS
|
||||||
|
|
||||||
|
if param.should_setup_room_and_name:
|
||||||
|
_setup_room(param.room)
|
||||||
|
_setup_name(param.obj_name)
|
||||||
|
|
||||||
|
# Create the folder
|
||||||
|
result_code = _create_obj_folder()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# ---- LOCAL CODE ------------------------------------------------------------------------------
|
||||||
|
# Create the instance
|
||||||
|
var new_obj: Marker2D = Marker2D.new()
|
||||||
|
new_obj.name = _pascal_name
|
||||||
|
|
||||||
|
# Save the marker scene (.tscn) and put it into _scene class property
|
||||||
|
result_code = _save_obj_scene(new_obj)
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
if param.should_add_to_room:
|
||||||
|
# Add the object to its room
|
||||||
|
_add_resource_to_room()
|
||||||
|
|
||||||
|
return result_code
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://ckjo1lcmxjlh6
|
86
addons/popochiu/editor/factories/factory_popochiu_prop.gd
Normal file
86
addons/popochiu/editor/factories/factory_popochiu_prop.gd
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
class_name PopochiuPropFactory
|
||||||
|
extends PopochiuRoomObjFactory
|
||||||
|
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _init() -> void:
|
||||||
|
_type = PopochiuResources.Types.PROP
|
||||||
|
_type_label = "prop"
|
||||||
|
_type_method = PopochiuEditorHelper.is_prop
|
||||||
|
_obj_room_group = "Props"
|
||||||
|
_path_template = "/props/%s/prop_%s"
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
func create(param: PopochiuPropFactoryParam) -> int:
|
||||||
|
# If everything goes well, this won't change.
|
||||||
|
var result_code := ResultCodes.SUCCESS
|
||||||
|
|
||||||
|
if param.should_setup_room_and_name:
|
||||||
|
_setup_room(param.room)
|
||||||
|
_setup_name(param.obj_name)
|
||||||
|
|
||||||
|
# Create the folder
|
||||||
|
result_code = _create_obj_folder()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# Create the script (if the prop is interactive)
|
||||||
|
if param.should_create_script:
|
||||||
|
result_code = _copy_script_template()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# ---- LOCAL CODE ------------------------------------------------------------------------------
|
||||||
|
# Create the instance
|
||||||
|
var new_obj: PopochiuProp = _load_obj_base_scene()
|
||||||
|
|
||||||
|
new_obj.set_script(ResourceLoader.load(_path_script))
|
||||||
|
|
||||||
|
new_obj.name = _pascal_name
|
||||||
|
new_obj.script_name = _pascal_name
|
||||||
|
new_obj.description = _snake_name.capitalize()
|
||||||
|
new_obj.cursor = PopochiuResources.CURSOR_TYPE.ACTIVE
|
||||||
|
new_obj.clickable = param.is_interactive
|
||||||
|
new_obj.visible = param.is_visible
|
||||||
|
new_obj.interaction_polygon = param.interaction_polygon
|
||||||
|
|
||||||
|
if PopochiuConfig.is_pixel_art_textures():
|
||||||
|
new_obj.get_node("Sprite2D").texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||||
|
|
||||||
|
if _snake_name in ["bg", "background"]:
|
||||||
|
new_obj.baseline =\
|
||||||
|
-ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT) / 2.0
|
||||||
|
new_obj.z_index = -1
|
||||||
|
|
||||||
|
# Save the scene (.tscn) and put it into _scene class property
|
||||||
|
result_code = _save_obj_scene(new_obj)
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
if param.should_add_to_room:
|
||||||
|
# Add the object to its room
|
||||||
|
_add_resource_to_room()
|
||||||
|
|
||||||
|
return result_code
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _get_param(node: Node) -> PopochiuRoomObjFactoryParam:
|
||||||
|
var param := PopochiuPropFactoryParam.new()
|
||||||
|
param.is_interactive = node.clickable
|
||||||
|
# TODO: Remove this line once the last gizmos PR is merged
|
||||||
|
param.interaction_polygon = node.interaction_polygon
|
||||||
|
return param
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Subclass ###################################################################################
|
||||||
|
class PopochiuPropFactoryParam extends PopochiuRoomObjFactory.PopochiuRoomObjFactoryParam:
|
||||||
|
var is_interactive := false
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://b8ajrxi1mp5up
|
71
addons/popochiu/editor/factories/factory_popochiu_region.gd
Normal file
71
addons/popochiu/editor/factories/factory_popochiu_region.gd
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
class_name PopochiuRegionFactory
|
||||||
|
extends PopochiuRoomObjFactory
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _init() -> void:
|
||||||
|
_type = PopochiuResources.Types.REGION
|
||||||
|
_type_label = "region"
|
||||||
|
_type_method = PopochiuEditorHelper.is_region
|
||||||
|
_obj_room_group = "Regions"
|
||||||
|
_path_template = "/regions/%s/region_%s"
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
func create(param: PopochiuRoomObjFactoryParam) -> int:
|
||||||
|
# If everything goes well, this won't change.
|
||||||
|
var result_code := ResultCodes.SUCCESS
|
||||||
|
|
||||||
|
if param.should_setup_room_and_name:
|
||||||
|
_setup_room(param.room)
|
||||||
|
_setup_name(param.obj_name)
|
||||||
|
|
||||||
|
# Create the folder
|
||||||
|
result_code = _create_obj_folder()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# Create the script
|
||||||
|
if param.should_create_script:
|
||||||
|
result_code = _copy_script_template()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# ---- LOCAL CODE ------------------------------------------------------------------------------
|
||||||
|
# Create the instance
|
||||||
|
var new_obj: PopochiuRegion = _load_obj_base_scene()
|
||||||
|
new_obj.set_script(ResourceLoader.load(_path_script))
|
||||||
|
new_obj.name = _pascal_name
|
||||||
|
new_obj.script_name = _pascal_name
|
||||||
|
new_obj.description = _snake_name.capitalize()
|
||||||
|
new_obj.interaction_polygon = param.interaction_polygon
|
||||||
|
|
||||||
|
# Save the scene (.tscn) and put it into _scene class property
|
||||||
|
result_code = _save_obj_scene(new_obj)
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
if param.should_add_to_room:
|
||||||
|
# Add the object to its room
|
||||||
|
_add_resource_to_room()
|
||||||
|
|
||||||
|
return result_code
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _get_param(node: Node) -> PopochiuRoomObjFactoryParam:
|
||||||
|
var param := PopochiuRegionFactoryParam.new()
|
||||||
|
param.interaction_polygon = node.interaction_polygon
|
||||||
|
|
||||||
|
return param
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Subclass ###################################################################################
|
||||||
|
class PopochiuRegionFactoryParam extends PopochiuRoomObjFactory.PopochiuRoomObjFactoryParam:
|
||||||
|
var should_create_interaction_polygon := true
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dphtxosh168sb
|
63
addons/popochiu/editor/factories/factory_popochiu_room.gd
Normal file
63
addons/popochiu/editor/factories/factory_popochiu_room.gd
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
extends "res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd"
|
||||||
|
class_name PopochiuRoomFactory
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _init() -> void:
|
||||||
|
_type = PopochiuResources.Types.ROOM
|
||||||
|
_type_label = "room"
|
||||||
|
_type_target = "rooms"
|
||||||
|
_path_template = PopochiuResources.ROOMS_PATH.path_join("%s/room_%s")
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
func create(obj_name: String, set_as_main := false) -> int:
|
||||||
|
# If everything goes well, this won't change.
|
||||||
|
var result_code := ResultCodes.SUCCESS
|
||||||
|
|
||||||
|
# Setup the class variables that depends on the object name
|
||||||
|
_setup_name(obj_name)
|
||||||
|
|
||||||
|
# Create the folder
|
||||||
|
result_code = _create_obj_folder()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# Create the state Resource and a script
|
||||||
|
# so devs can add extra properties to that state
|
||||||
|
result_code = _create_state_resource()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# Create the script populating the template with the right references
|
||||||
|
result_code = _create_script_from_template()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# ---- LOCAL CODE ------------------------------------------------------------------------------
|
||||||
|
# Create the instance
|
||||||
|
var new_obj: PopochiuRoom = _load_obj_base_scene()
|
||||||
|
|
||||||
|
new_obj.name = "Room" + _pascal_name
|
||||||
|
new_obj.script_name = _pascal_name
|
||||||
|
new_obj.width = ProjectSettings.get_setting(PopochiuResources.DISPLAY_WIDTH)
|
||||||
|
new_obj.height = ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT)
|
||||||
|
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Save the scene (.tscn)
|
||||||
|
result_code = _save_obj_scene(new_obj)
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# Add the object to Popochiu dock list, plus open it in the editor
|
||||||
|
_add_resource_to_popochiu()
|
||||||
|
|
||||||
|
# ---- LOCAL CODE ------------------------------------------------------------------------------
|
||||||
|
# Set as main room
|
||||||
|
# Changed _set_as_main_check.pressed to _set_as_main_check.button_pressed
|
||||||
|
# in order to fix #56
|
||||||
|
if set_as_main:
|
||||||
|
PopochiuEditorHelper.signal_bus.main_scene_changed.emit(_scene.scene_file_path)
|
||||||
|
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
return result_code
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://b7kd0q631qqt6
|
|
@ -0,0 +1,94 @@
|
||||||
|
class_name PopochiuWalkableAreaFactory
|
||||||
|
extends PopochiuRoomObjFactory
|
||||||
|
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _init() -> void:
|
||||||
|
_type = PopochiuResources.Types.WALKABLE_AREA
|
||||||
|
_type_label = "walkable_area"
|
||||||
|
_type_method = PopochiuEditorHelper.is_walkable_area
|
||||||
|
_obj_room_group = "WalkableAreas"
|
||||||
|
_path_template = "/walkable_areas/%s/walkable_area_%s"
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
func create(param: PopochiuWalkableAreaFactoryParam) -> int:
|
||||||
|
# If everything goes well, this won't change.
|
||||||
|
var result_code := ResultCodes.SUCCESS
|
||||||
|
|
||||||
|
if param.should_setup_room_and_name:
|
||||||
|
_setup_room(param.room)
|
||||||
|
_setup_name(param.obj_name)
|
||||||
|
|
||||||
|
# Create the folder
|
||||||
|
result_code = _create_obj_folder()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# Create the script
|
||||||
|
if param.should_create_script:
|
||||||
|
result_code = _copy_script_template()
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
|
||||||
|
# ---- LOCAL CODE ------------------------------------------------------------------------------
|
||||||
|
# Create the instance
|
||||||
|
var new_obj: PopochiuWalkableArea = _load_obj_base_scene()
|
||||||
|
|
||||||
|
new_obj.set_script(ResourceLoader.load(_path_script))
|
||||||
|
|
||||||
|
new_obj.name = _pascal_name
|
||||||
|
new_obj.script_name = _pascal_name
|
||||||
|
new_obj.description = _snake_name.capitalize()
|
||||||
|
|
||||||
|
# Find the NavigationRegion2D for the WA and populate it with a default rectangle polygon
|
||||||
|
var perimeter := new_obj.find_child("Perimeter")
|
||||||
|
var polygon := NavigationPolygon.new()
|
||||||
|
polygon.add_outline(PackedVector2Array([
|
||||||
|
Vector2(-10, -10), Vector2(10, -10), Vector2(10, 10), Vector2(-10, 10)
|
||||||
|
]))
|
||||||
|
NavigationServer2D.bake_from_source_geometry_data(
|
||||||
|
polygon, NavigationMeshSourceGeometryData2D.new()
|
||||||
|
)
|
||||||
|
polygon.agent_radius = 0.0
|
||||||
|
perimeter.navigation_polygon = polygon
|
||||||
|
|
||||||
|
if not param.navigation_polygon.is_empty():
|
||||||
|
new_obj.interaction_polygon = param.navigation_polygon
|
||||||
|
new_obj.clear_and_bake(perimeter.navigation_polygon)
|
||||||
|
|
||||||
|
# Show the WA perimeter, depending on user prefs
|
||||||
|
perimeter.visible = PopochiuEditorConfig.get_editor_setting(
|
||||||
|
PopochiuEditorConfig.GIZMOS_ALWAYS_SHOW_WA
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save the scene (.tscn) and put it into _scene class property
|
||||||
|
result_code = _save_obj_scene(new_obj)
|
||||||
|
if result_code != ResultCodes.SUCCESS: return result_code
|
||||||
|
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
if param.should_add_to_room:
|
||||||
|
# Add the object to its room
|
||||||
|
_add_resource_to_room()
|
||||||
|
|
||||||
|
return result_code
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _get_param(node: Node) -> PopochiuRoomObjFactoryParam:
|
||||||
|
var param := PopochiuWalkableAreaFactoryParam.new()
|
||||||
|
param.navigation_polygon = node.interaction_polygon
|
||||||
|
|
||||||
|
return param
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Subclass ###################################################################################
|
||||||
|
class PopochiuWalkableAreaFactoryParam extends PopochiuRoomObjFactory.PopochiuRoomObjFactoryParam:
|
||||||
|
var navigation_polygon := []
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://c66i7vwbgxym8
|
274
addons/popochiu/editor/gizmos/gizmo2d.gd
Normal file
274
addons/popochiu/editor/gizmos/gizmo2d.gd
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
@tool
|
||||||
|
class_name Gizmo2D
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
# Gizmo types
|
||||||
|
enum {
|
||||||
|
GIZMO_POS, # square marker that represents (x,y) coordinates
|
||||||
|
GIZMO_HPOS, # vertical line that represents a horizontal coordinate
|
||||||
|
GIZMO_VPOS # horizontal line that represents a vertical coordinate
|
||||||
|
}
|
||||||
|
|
||||||
|
# Public vars
|
||||||
|
# Convenience accessors
|
||||||
|
var target_node: Node2D:
|
||||||
|
set = set_target_node,
|
||||||
|
get = get_target_node
|
||||||
|
var target_property: String:
|
||||||
|
set = set_target_property,
|
||||||
|
get = get_target_property
|
||||||
|
var position:
|
||||||
|
get = get_position
|
||||||
|
# Behavior flags
|
||||||
|
var show_connector: bool = true # Show gizmo-to-node connectors
|
||||||
|
var show_outlines: bool = true
|
||||||
|
var show_target_name: bool = true # Show target node name
|
||||||
|
var visible: bool = true # Gizmo visibility
|
||||||
|
|
||||||
|
# Private vars
|
||||||
|
# Context
|
||||||
|
var _type: int
|
||||||
|
var _target_node: Node2D
|
||||||
|
var _target_property: String
|
||||||
|
# Appearance
|
||||||
|
var _size: Vector2 # Gizmo width and height
|
||||||
|
var _color: Color # Gizmo color
|
||||||
|
var _label: String # A label to be painted near the Gizmo
|
||||||
|
var _font: Font # Label font
|
||||||
|
var _font_size: int # Label font size
|
||||||
|
# State
|
||||||
|
var _handle: Rect2 # Gizmo handle
|
||||||
|
var _current_position: Vector2 # The position the gizmo is representing in every moment
|
||||||
|
var _current_color: Color
|
||||||
|
var _is_grabbed: bool = false # Gizmo is moving
|
||||||
|
var _grab_center_pos: Vector2 # Starting center position when grabbing
|
||||||
|
var _grab_mouse_pos: Vector2 # Starting mouse position when grabbing
|
||||||
|
|
||||||
|
|
||||||
|
#region Virtual ####################################################################################
|
||||||
|
|
||||||
|
func _init(
|
||||||
|
node: Node,
|
||||||
|
property: String,
|
||||||
|
label: String,
|
||||||
|
type: int,
|
||||||
|
):
|
||||||
|
_target_node = node
|
||||||
|
_target_property = property
|
||||||
|
_type = type
|
||||||
|
_label = label
|
||||||
|
|
||||||
|
set_theme(
|
||||||
|
Color.AQUA,
|
||||||
|
24,
|
||||||
|
EditorInterface.get_editor_theme().default_font,
|
||||||
|
EditorInterface.get_editor_theme().default_font_size
|
||||||
|
)
|
||||||
|
|
||||||
|
_current_color = _color
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SetGet #####################################################################################
|
||||||
|
|
||||||
|
func set_theme(
|
||||||
|
color: Color,
|
||||||
|
size: int,
|
||||||
|
font: Font,
|
||||||
|
font_size: int
|
||||||
|
):
|
||||||
|
_color = color
|
||||||
|
_size = Vector2(size, size)
|
||||||
|
_font = font
|
||||||
|
_font_size = font_size
|
||||||
|
|
||||||
|
func set_target_node(node: Node2D):
|
||||||
|
_target_node = node
|
||||||
|
|
||||||
|
func get_target_node() -> Node2D:
|
||||||
|
return _target_node
|
||||||
|
|
||||||
|
func set_target_property(property: String):
|
||||||
|
_target_property = property
|
||||||
|
|
||||||
|
func get_target_property() -> String:
|
||||||
|
if _target_property:
|
||||||
|
return _target_property
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
|
||||||
|
func _draw_outlines(viewport: Control):
|
||||||
|
viewport.draw_rect(
|
||||||
|
_handle,
|
||||||
|
Color.BLACK, false, 4
|
||||||
|
)
|
||||||
|
|
||||||
|
viewport.draw_string_outline(
|
||||||
|
_font,
|
||||||
|
_handle.position + Vector2(0, _size.y + 2 + _font.get_ascent(_font_size)),
|
||||||
|
_label, HORIZONTAL_ALIGNMENT_CENTER,
|
||||||
|
- 1,
|
||||||
|
_font_size,
|
||||||
|
6,
|
||||||
|
Color.BLACK
|
||||||
|
)
|
||||||
|
|
||||||
|
if show_target_name:
|
||||||
|
viewport.draw_string_outline(
|
||||||
|
_font,
|
||||||
|
_handle.position + Vector2(0, -_font.get_descent(_font_size)),
|
||||||
|
_target_node.name,
|
||||||
|
HORIZONTAL_ALIGNMENT_CENTER,
|
||||||
|
- 1,
|
||||||
|
_font_size,
|
||||||
|
6,
|
||||||
|
Color.BLACK
|
||||||
|
)
|
||||||
|
|
||||||
|
func _draw_gizmo(viewport: Control):
|
||||||
|
|
||||||
|
# Draw the handle (on top of the line, if it's present)
|
||||||
|
viewport.draw_rect(
|
||||||
|
_handle,
|
||||||
|
_current_color
|
||||||
|
)
|
||||||
|
|
||||||
|
# Draw gizmo-to-node connector, if active
|
||||||
|
if show_connector:
|
||||||
|
viewport.draw_dashed_line(
|
||||||
|
(_target_node.get_viewport_transform() * _target_node.get_global_transform()).origin,
|
||||||
|
_handle.get_center(),
|
||||||
|
_current_color.darkened(0.2),
|
||||||
|
2,
|
||||||
|
4
|
||||||
|
)
|
||||||
|
viewport.draw_circle(
|
||||||
|
_handle.get_center(),
|
||||||
|
3,
|
||||||
|
_current_color.darkened(0.2)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Draw the label, if it's set and non empty
|
||||||
|
if _label:
|
||||||
|
viewport.draw_string(
|
||||||
|
_font,
|
||||||
|
_handle.position + Vector2(0, _size.y + 2 + _font.get_ascent(_font_size)),
|
||||||
|
_label, HORIZONTAL_ALIGNMENT_CENTER,
|
||||||
|
- 1,
|
||||||
|
_font_size,
|
||||||
|
_current_color
|
||||||
|
)
|
||||||
|
|
||||||
|
if show_target_name:
|
||||||
|
viewport.draw_string(
|
||||||
|
_font,
|
||||||
|
_handle.position + Vector2(0, -_font.get_descent(_font_size)),
|
||||||
|
_target_node.name,
|
||||||
|
HORIZONTAL_ALIGNMENT_CENTER,
|
||||||
|
- 1,
|
||||||
|
_font_size,
|
||||||
|
_current_color
|
||||||
|
)
|
||||||
|
|
||||||
|
func _can_draw():
|
||||||
|
return (visible and _target_node != null and _target_node.is_visible_in_tree())
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
|
||||||
|
func draw(viewport: Control, coord: Variant) -> void:
|
||||||
|
# Handmade coordinates type overloading
|
||||||
|
if not (coord is Vector2 or coord is int):
|
||||||
|
return
|
||||||
|
# Check if the gizmo can be drawn
|
||||||
|
if not _can_draw():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Coordinates normalization (to vector) for horizontal or vertical gizmos
|
||||||
|
# Both axis are set to the same value, then ignore one or the other
|
||||||
|
# depending on the gizmo type
|
||||||
|
if coord is int:
|
||||||
|
coord = Vector2(coord, coord)
|
||||||
|
|
||||||
|
# Calculate the GLOBAL coordinates of the center of the square handle
|
||||||
|
# This only takes into account the node offset discarding its transform basis
|
||||||
|
# (representing rotation, skew and scale) then it applies the viewport transform
|
||||||
|
# to take into account the zoom level
|
||||||
|
var center = _target_node.get_viewport_transform() * (_target_node.get_global_transform().origin + Vector2(coord))
|
||||||
|
|
||||||
|
# Set handle color
|
||||||
|
_current_color = _color
|
||||||
|
# Highlight handle if held by the mouse click
|
||||||
|
if _is_grabbed:
|
||||||
|
_current_color = _color.lightened(0.5)
|
||||||
|
|
||||||
|
# Draw an horizontal or vertical line if the gizmo is one-dimensional
|
||||||
|
match _type:
|
||||||
|
GIZMO_VPOS:
|
||||||
|
var viewport_width = EditorInterface.get_editor_viewport_2d().size.x
|
||||||
|
center.x = viewport_width / 2
|
||||||
|
viewport.draw_line(
|
||||||
|
Vector2(0, center.y),
|
||||||
|
Vector2(viewport_width, center.y),
|
||||||
|
_current_color,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
GIZMO_HPOS:
|
||||||
|
var viewport_height = EditorInterface.get_editor_viewport_2d().size.y
|
||||||
|
center.y = viewport_height / 2
|
||||||
|
viewport.draw_line(
|
||||||
|
Vector2(center.x, 0),
|
||||||
|
Vector2(center.x, viewport_height),
|
||||||
|
_current_color,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initialize the handle in the right position
|
||||||
|
_handle = Rect2(center - _size / 2, _size)
|
||||||
|
|
||||||
|
if show_outlines:
|
||||||
|
_draw_outlines(viewport)
|
||||||
|
|
||||||
|
_draw_gizmo(viewport)
|
||||||
|
|
||||||
|
func drag_to(pos: Vector2):
|
||||||
|
# Distance between the mouse position and the gizmo center
|
||||||
|
var d = _grab_center_pos - _grab_mouse_pos
|
||||||
|
# Gizmo center position in global coordinates
|
||||||
|
var current_gizmo_pos = pos + d
|
||||||
|
# Distance between gizmo center position in 2D world node coordinates and
|
||||||
|
# node position ignoring its transform basis (representing rotation, skew and scale)
|
||||||
|
_current_position = _target_node.get_viewport_transform().affine_inverse() * current_gizmo_pos - (target_node.get_global_transform().origin)
|
||||||
|
|
||||||
|
func release():
|
||||||
|
_is_grabbed = false
|
||||||
|
|
||||||
|
func grab(pos: Vector2):
|
||||||
|
_is_grabbed = true
|
||||||
|
_grab_mouse_pos = pos
|
||||||
|
_grab_center_pos = _handle.get_center()
|
||||||
|
|
||||||
|
func cancel():
|
||||||
|
_is_grabbed = false
|
||||||
|
|
||||||
|
func has_point(pos: Vector2):
|
||||||
|
return visible and _handle.abs().has_point(pos)
|
||||||
|
|
||||||
|
func get_position():
|
||||||
|
match _type:
|
||||||
|
GIZMO_POS:
|
||||||
|
return _current_position
|
||||||
|
GIZMO_HPOS:
|
||||||
|
return _current_position.x
|
||||||
|
GIZMO_VPOS:
|
||||||
|
return _current_position.y
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
1
addons/popochiu/editor/gizmos/gizmo2d.gd.uid
Normal file
1
addons/popochiu/editor/gizmos/gizmo2d.gd.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://71g54gbvyv7l
|
278
addons/popochiu/editor/gizmos/gizmo_clickable_plugin.gd
Normal file
278
addons/popochiu/editor/gizmos/gizmo_clickable_plugin.gd
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
@tool
|
||||||
|
class_name PopochiuGizmoClickablePlugin
|
||||||
|
extends EditorPlugin
|
||||||
|
|
||||||
|
# TODO: move these out of the plugin and into Popochiu (enums) or PopochiuClickable
|
||||||
|
enum {
|
||||||
|
WALK_TO_POINT,
|
||||||
|
LOOK_AT_POINT,
|
||||||
|
BASELINE,
|
||||||
|
DIALOG_POS
|
||||||
|
}
|
||||||
|
|
||||||
|
# Private vars
|
||||||
|
# State
|
||||||
|
var _target_node: Node2D
|
||||||
|
var _undo: EditorUndoRedoManager
|
||||||
|
var _gizmos: Array
|
||||||
|
var _active_gizmos: Array
|
||||||
|
var _grabbed_gizmo: Gizmo2D
|
||||||
|
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
|
||||||
|
func _enter_tree() -> void:
|
||||||
|
# TODO: remove the following 2 lines when the plugin is connected to the appropriate signal
|
||||||
|
# e.g. popochiu_ready
|
||||||
|
PopochiuEditorConfig.initialize_editor_settings()
|
||||||
|
PopochiuConfig.initialize_project_settings()
|
||||||
|
|
||||||
|
# Initialization of the plugin goes here.
|
||||||
|
_undo = get_undo_redo()
|
||||||
|
_gizmos.insert(WALK_TO_POINT, _init_popochiu_gizmo(WALK_TO_POINT))
|
||||||
|
_gizmos.insert(LOOK_AT_POINT, _init_popochiu_gizmo(LOOK_AT_POINT))
|
||||||
|
_gizmos.insert(BASELINE, _init_popochiu_gizmo(BASELINE))
|
||||||
|
_gizmos.insert(DIALOG_POS, _init_popochiu_gizmo(DIALOG_POS))
|
||||||
|
|
||||||
|
EditorInterface.get_editor_settings().settings_changed.connect(_on_gizmo_settings_changed)
|
||||||
|
PopochiuEditorHelper.signal_bus.gizmo_visibility_changed.connect(_on_gizmo_visibility_changed)
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Virtual ####################################################################################
|
||||||
|
|
||||||
|
func _edit(object: Object) -> void:
|
||||||
|
if object == null or object.get_class() == "EditorDebuggerRemoteObject":
|
||||||
|
return
|
||||||
|
|
||||||
|
_target_node = object
|
||||||
|
_active_gizmos.clear()
|
||||||
|
|
||||||
|
if EditorInterface.get_edited_scene_root() is PopochiuCharacter:
|
||||||
|
_active_gizmos.append(_gizmos[DIALOG_POS])
|
||||||
|
elif EditorInterface.get_edited_scene_root() is PopochiuRoom:
|
||||||
|
_active_gizmos.append(_gizmos[WALK_TO_POINT])
|
||||||
|
_active_gizmos.append(_gizmos[LOOK_AT_POINT])
|
||||||
|
_active_gizmos.append(_gizmos[BASELINE])
|
||||||
|
|
||||||
|
for gizmo in _active_gizmos:
|
||||||
|
gizmo.set_target_node(_target_node)
|
||||||
|
|
||||||
|
if not EditorInterface.get_inspector().property_edited.is_connected(_on_property_changed):
|
||||||
|
EditorInterface.get_inspector().property_edited.connect(_on_property_changed)
|
||||||
|
update_overlays()
|
||||||
|
|
||||||
|
|
||||||
|
func _forward_canvas_draw_over_viewport(viewport_control: Control) -> void:
|
||||||
|
for gizmo in _active_gizmos:
|
||||||
|
gizmo.draw(viewport_control, _target_node.get(gizmo.target_property))
|
||||||
|
|
||||||
|
|
||||||
|
func _handles(object: Object) -> bool:
|
||||||
|
return object is PopochiuClickable
|
||||||
|
|
||||||
|
|
||||||
|
func _forward_canvas_gui_input(event: InputEvent) -> bool:
|
||||||
|
if not _target_node or not _target_node.is_visible_in_tree():
|
||||||
|
return false
|
||||||
|
|
||||||
|
# For left mouse buttons, try to grab or release, depending on state
|
||||||
|
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
|
||||||
|
# Grab
|
||||||
|
if not _grabbed_gizmo and event.is_pressed():
|
||||||
|
return _try_grab_gizmo(event)
|
||||||
|
# Release
|
||||||
|
elif _grabbed_gizmo and event.is_released():
|
||||||
|
return _release_gizmo(event)
|
||||||
|
|
||||||
|
# For mouse movement, drag the grabbed gizmo
|
||||||
|
if event is InputEventMouseMotion:
|
||||||
|
return _drag_gizmo(event)
|
||||||
|
|
||||||
|
# For ESC key or comparable events, cancel the dragging if in place
|
||||||
|
if event.is_action_pressed("ui_cancel"):
|
||||||
|
return _cancel_dragging_gizmo(event)
|
||||||
|
|
||||||
|
## Nothing to handle outside the cases above
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
|
||||||
|
func _on_property_changed(property: String):
|
||||||
|
update_overlays()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_gizmo_settings_changed() -> void:
|
||||||
|
var gizmo_id = 0
|
||||||
|
var default_font = EditorInterface.get_editor_theme().default_font
|
||||||
|
|
||||||
|
for gizmo in _gizmos:
|
||||||
|
match gizmo_id:
|
||||||
|
WALK_TO_POINT:
|
||||||
|
gizmo.set_theme(
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_WALK_TO_POINT_COLOR),
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_HANDLER_SIZE),
|
||||||
|
default_font,
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_FONT_SIZE)
|
||||||
|
)
|
||||||
|
LOOK_AT_POINT:
|
||||||
|
gizmo.set_theme(
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_LOOK_AT_POINT_COLOR),
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_HANDLER_SIZE),
|
||||||
|
default_font,
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_FONT_SIZE)
|
||||||
|
)
|
||||||
|
BASELINE:
|
||||||
|
gizmo.set_theme(
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_BASELINE_COLOR),
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_HANDLER_SIZE),
|
||||||
|
default_font,
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_FONT_SIZE)
|
||||||
|
)
|
||||||
|
DIALOG_POS:
|
||||||
|
gizmo.set_theme(
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_DIALOG_POS_COLOR),
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_HANDLER_SIZE),
|
||||||
|
default_font,
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_FONT_SIZE)
|
||||||
|
)
|
||||||
|
|
||||||
|
gizmo.show_connector = PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_SHOW_CONNECTORS)
|
||||||
|
gizmo.show_outlines = PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_SHOW_OUTLINE)
|
||||||
|
gizmo.show_target_name = PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_SHOW_NODE_NAME)
|
||||||
|
gizmo_id += 1
|
||||||
|
|
||||||
|
update_overlays()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_gizmo_visibility_changed(gizmo_id:int, visibility:bool):
|
||||||
|
if gizmo_id < _gizmos.size():
|
||||||
|
_gizmos[gizmo_id].visible = visibility
|
||||||
|
update_overlays()
|
||||||
|
|
||||||
|
func _update_properties():
|
||||||
|
if _grabbed_gizmo and _grabbed_gizmo.target_property:
|
||||||
|
_target_node.set(
|
||||||
|
_grabbed_gizmo.target_property,
|
||||||
|
_grabbed_gizmo.get_position()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _init_popochiu_gizmo(gizmo_id: int) -> Gizmo2D:
|
||||||
|
var gizmo: Gizmo2D
|
||||||
|
var default_font = EditorInterface.get_editor_theme().default_font
|
||||||
|
|
||||||
|
match gizmo_id:
|
||||||
|
WALK_TO_POINT:
|
||||||
|
gizmo = Gizmo2D.new(_target_node, "walk_to_point", "Walk To Point", Gizmo2D.GIZMO_POS)
|
||||||
|
gizmo.set_theme(
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_WALK_TO_POINT_COLOR),
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_HANDLER_SIZE),
|
||||||
|
default_font,
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_FONT_SIZE)
|
||||||
|
)
|
||||||
|
LOOK_AT_POINT:
|
||||||
|
gizmo = Gizmo2D.new(_target_node, "look_at_point", "Look At Point", Gizmo2D.GIZMO_POS)
|
||||||
|
gizmo.set_theme(
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_LOOK_AT_POINT_COLOR),
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_HANDLER_SIZE),
|
||||||
|
default_font,
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_FONT_SIZE)
|
||||||
|
)
|
||||||
|
BASELINE:
|
||||||
|
gizmo = Gizmo2D.new(_target_node, "baseline", "Baseline", Gizmo2D.GIZMO_VPOS)
|
||||||
|
gizmo.set_theme(
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_BASELINE_COLOR),
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_HANDLER_SIZE),
|
||||||
|
default_font,
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_FONT_SIZE)
|
||||||
|
)
|
||||||
|
DIALOG_POS:
|
||||||
|
gizmo = Gizmo2D.new(_target_node, "dialog_pos", "Dialog Position", Gizmo2D.GIZMO_POS)
|
||||||
|
gizmo.set_theme(
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_DIALOG_POS_COLOR),
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_HANDLER_SIZE),
|
||||||
|
default_font,
|
||||||
|
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_FONT_SIZE)
|
||||||
|
)
|
||||||
|
return gizmo
|
||||||
|
|
||||||
|
|
||||||
|
func _try_grab_gizmo(event: InputEventMouseButton) -> bool:
|
||||||
|
# Check if the mouse click happened on a gizmo
|
||||||
|
# The order is reversed to the topmost gizmo
|
||||||
|
# (the last been drawn) is selected
|
||||||
|
for i in range(_active_gizmos.size() - 1, -1, -1):
|
||||||
|
if not _active_gizmos[i].has_point(event.position):
|
||||||
|
continue
|
||||||
|
_grabbed_gizmo = _active_gizmos[i]
|
||||||
|
break
|
||||||
|
|
||||||
|
# If user clicked on no gizmos
|
||||||
|
# ignore the event
|
||||||
|
if not _grabbed_gizmo:
|
||||||
|
return false
|
||||||
|
|
||||||
|
# hold the gizmo with the mouse
|
||||||
|
_grabbed_gizmo.grab(event.position)
|
||||||
|
_undo.create_action("Move gizmo")
|
||||||
|
_undo.add_undo_property(
|
||||||
|
_grabbed_gizmo.target_node,
|
||||||
|
_grabbed_gizmo.target_property,
|
||||||
|
_grabbed_gizmo.target_node.get(_grabbed_gizmo.target_property)
|
||||||
|
)
|
||||||
|
update_overlays()
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _release_gizmo(event: InputEvent) -> bool:
|
||||||
|
# If there is no gizmo to release
|
||||||
|
# ignore the event
|
||||||
|
if not _grabbed_gizmo:
|
||||||
|
return false
|
||||||
|
|
||||||
|
_grabbed_gizmo.release()
|
||||||
|
_undo.add_do_property(
|
||||||
|
_grabbed_gizmo.target_node,
|
||||||
|
_grabbed_gizmo.target_property,
|
||||||
|
_grabbed_gizmo.target_node.get(_grabbed_gizmo.target_property)
|
||||||
|
)
|
||||||
|
_undo.commit_action()
|
||||||
|
update_overlays()
|
||||||
|
_grabbed_gizmo = null
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _drag_gizmo(event: InputEvent) -> bool:
|
||||||
|
# If no gizmo to drag
|
||||||
|
# ignore the event
|
||||||
|
if not _grabbed_gizmo:
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Drag the gizmo
|
||||||
|
_grabbed_gizmo.drag_to(event.position)
|
||||||
|
_update_properties()
|
||||||
|
update_overlays()
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _cancel_dragging_gizmo(event: InputEvent) -> bool:
|
||||||
|
# If ESC/Cancel happens but we're not dragging
|
||||||
|
# ignore the event
|
||||||
|
if not _grabbed_gizmo:
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Cancel the action
|
||||||
|
_grabbed_gizmo.cancel()
|
||||||
|
_undo.commit_action()
|
||||||
|
_undo.get_history_undo_redo(_undo.get_object_history_id(_grabbed_gizmo.target_node)).undo()
|
||||||
|
update_overlays()
|
||||||
|
_grabbed_gizmo = null
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://1t0c81s6wd7c
|
7
addons/popochiu/editor/gizmos/plugin.cfg
Normal file
7
addons/popochiu/editor/gizmos/plugin.cfg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[plugin]
|
||||||
|
|
||||||
|
name="PopochiuGizmoClickable"
|
||||||
|
description="Provides viewport-drawn interactive 2D gizmos to Popochiu objects in the editor."
|
||||||
|
author="Carenalgas Dev Team"
|
||||||
|
version="2.0"
|
||||||
|
script="gizmo_clickable_plugin.gd"
|
323
addons/popochiu/editor/helpers/popochiu_editor_helper.gd
Normal file
323
addons/popochiu/editor/helpers/popochiu_editor_helper.gd
Normal file
|
@ -0,0 +1,323 @@
|
||||||
|
@tool
|
||||||
|
class_name PopochiuEditorHelper
|
||||||
|
extends Resource
|
||||||
|
## Utils class for Editor related things.
|
||||||
|
|
||||||
|
# ---- Strings, paths, scenes, and other values ----------------------------------------------------
|
||||||
|
const POPUPS_FOLDER = "res://addons/popochiu/editor/popups/"
|
||||||
|
const CREATE_OBJECT_FOLDER = "res://addons/popochiu/editor/popups/create_object/"
|
||||||
|
const CREATE_ROOM = preload(CREATE_OBJECT_FOLDER + "create_room/create_room.tscn")
|
||||||
|
const CREATE_CHARACTER = preload(CREATE_OBJECT_FOLDER + "create_character/create_character.tscn")
|
||||||
|
const CREATE_INVENTORY_ITEM = preload(
|
||||||
|
CREATE_OBJECT_FOLDER + "create_inventory_item/create_inventory_item.tscn"
|
||||||
|
)
|
||||||
|
const CREATE_DIALOG = preload(CREATE_OBJECT_FOLDER + "create_dialog/create_dialog.tscn")
|
||||||
|
const CREATE_PROP = preload(CREATE_OBJECT_FOLDER + "create_prop/create_prop.tscn")
|
||||||
|
const CREATE_HOTSPOT = preload(CREATE_OBJECT_FOLDER + "create_hotspot/create_hotspot.tscn")
|
||||||
|
const CREATE_WALKABLE_AREA = preload(
|
||||||
|
CREATE_OBJECT_FOLDER + "create_walkable_area/create_walkable_area.tscn"
|
||||||
|
)
|
||||||
|
const CREATE_REGION = preload(CREATE_OBJECT_FOLDER + "create_region/create_region.tscn")
|
||||||
|
const CREATE_MARKER = preload(CREATE_OBJECT_FOLDER + "create_marker/create_marker.tscn")
|
||||||
|
const DELETE_CONFIRMATION_SCENE = preload(
|
||||||
|
POPUPS_FOLDER + "delete_confirmation/delete_confirmation.tscn"
|
||||||
|
)
|
||||||
|
const PROGRESS_DIALOG_SCENE = preload(POPUPS_FOLDER + "progress/progress.tscn")
|
||||||
|
const SETUP_SCENE = preload("res://addons/popochiu/editor/popups/setup/setup.tscn")
|
||||||
|
# ---- Identifiers ---------------------------------------------------------------------------------
|
||||||
|
const POPOCHIU_OBJECT_POLYGON_GROUP = "popochiu_object_polygon"
|
||||||
|
const MIGRATIONS_PANEL_SCENE = preload(
|
||||||
|
"res://addons/popochiu/editor/popups/migrations_panel/migrations_panel.tscn"
|
||||||
|
)
|
||||||
|
# ---- Classes -------------------------------------------------------------------------------------
|
||||||
|
const PopochiuSignalBus = preload("res://addons/popochiu/editor/helpers/popochiu_signal_bus.gd")
|
||||||
|
const DeleteConfirmation = preload(POPUPS_FOLDER + "delete_confirmation/delete_confirmation.gd")
|
||||||
|
const Progress = preload(POPUPS_FOLDER + "progress/progress.gd")
|
||||||
|
const CreateObject = preload(CREATE_OBJECT_FOLDER + "create_object.gd")
|
||||||
|
const MigrationsPanel = preload(
|
||||||
|
"res://addons/popochiu/editor/popups/migrations_panel/migrations_panel.gd"
|
||||||
|
)
|
||||||
|
|
||||||
|
static var signal_bus := PopochiuSignalBus.new()
|
||||||
|
static var ei := EditorInterface
|
||||||
|
static var undo_redo: EditorUndoRedoManager = null
|
||||||
|
static var dock: Panel = null
|
||||||
|
|
||||||
|
static var _room_scene_path_template := PopochiuResources.ROOMS_PATH.path_join("%s/room_%s.tscn")
|
||||||
|
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
static func select_node(node: Node) -> void:
|
||||||
|
ei.get_selection().clear()
|
||||||
|
ei.get_selection().add_node(node)
|
||||||
|
|
||||||
|
|
||||||
|
static func show_popup(popup_name: String) -> void:
|
||||||
|
PopochiuUtils.print_normal(popup_name)
|
||||||
|
|
||||||
|
|
||||||
|
static func add_resource_to_popochiu(target: String, resource: Resource) -> int:
|
||||||
|
return PopochiuResources.set_data_value(target, resource.script_name, resource.resource_path)
|
||||||
|
|
||||||
|
|
||||||
|
static func show_delete_confirmation(
|
||||||
|
content: DeleteConfirmation, min_size := Vector2i(640, 160)
|
||||||
|
) -> void:
|
||||||
|
var dialog := ConfirmationDialog.new()
|
||||||
|
dialog.title = content.title
|
||||||
|
|
||||||
|
dialog.confirmed.connect(
|
||||||
|
func () -> void:
|
||||||
|
if content.on_confirmed:
|
||||||
|
content.on_confirmed.call()
|
||||||
|
|
||||||
|
dialog.queue_free()
|
||||||
|
)
|
||||||
|
dialog.canceled.connect(
|
||||||
|
func () -> void:
|
||||||
|
if content.on_canceled:
|
||||||
|
content.on_canceled.call()
|
||||||
|
|
||||||
|
dialog.queue_free()
|
||||||
|
)
|
||||||
|
dialog.about_to_popup.connect(content.on_about_to_popup)
|
||||||
|
dialog.add_child(content)
|
||||||
|
|
||||||
|
await show_dialog(dialog, min_size)
|
||||||
|
|
||||||
|
|
||||||
|
static func show_progress(min_size := Vector2i(640, 80)) -> Progress:
|
||||||
|
var dialog := AcceptDialog.new()
|
||||||
|
var content: Progress = PROGRESS_DIALOG_SCENE.instantiate()
|
||||||
|
|
||||||
|
dialog.borderless = true
|
||||||
|
dialog.add_child(content)
|
||||||
|
dialog.get_ok_button().hide()
|
||||||
|
await show_dialog(dialog, min_size)
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
static func show_creation_popup(scene: PackedScene, min_size := Vector2i(640, 180)) -> void:
|
||||||
|
var content: CreateObject = scene.instantiate()
|
||||||
|
var dialog := ConfirmationDialog.new()
|
||||||
|
|
||||||
|
content.content_changed.connect(
|
||||||
|
func () -> void:
|
||||||
|
content.custom_minimum_size = content.get_child(0).size
|
||||||
|
content.size = content.get_child(0).size
|
||||||
|
|
||||||
|
dialog.reset_size()
|
||||||
|
dialog.move_to_center()
|
||||||
|
)
|
||||||
|
dialog.confirmed.connect(content.create)
|
||||||
|
dialog.canceled.connect(dialog.queue_free)
|
||||||
|
dialog.about_to_popup.connect(content.on_about_to_popup)
|
||||||
|
dialog.add_child(content)
|
||||||
|
await show_dialog(dialog, min_size)
|
||||||
|
|
||||||
|
dialog.register_text_enter(content.input)
|
||||||
|
|
||||||
|
|
||||||
|
static func show_setup(is_welcome := false) -> void:
|
||||||
|
var dialog := ConfirmationDialog.new()
|
||||||
|
var content := SETUP_SCENE.instantiate()
|
||||||
|
|
||||||
|
dialog.title = "Setup"
|
||||||
|
dialog.confirmed.connect(content.on_close)
|
||||||
|
dialog.close_requested.connect(content.on_close)
|
||||||
|
dialog.about_to_popup.connect(content.on_about_to_popup)
|
||||||
|
|
||||||
|
dialog.add_child(content)
|
||||||
|
dock.add_child.call_deferred(dialog)
|
||||||
|
await dialog.ready
|
||||||
|
|
||||||
|
content.define_content(is_welcome)
|
||||||
|
content.size_calculated.connect(
|
||||||
|
func () -> void:
|
||||||
|
dialog.reset_size()
|
||||||
|
dialog.move_to_center()
|
||||||
|
)
|
||||||
|
|
||||||
|
await show_dialog(dialog, content.custom_minimum_size)
|
||||||
|
|
||||||
|
|
||||||
|
static func show_migrations(
|
||||||
|
content: MigrationsPanel, min_size := Vector2i(640, 640)
|
||||||
|
) -> AcceptDialog:
|
||||||
|
var dialog := AcceptDialog.new()
|
||||||
|
dialog.title = "Migration Tool"
|
||||||
|
content.anchors_preset = Control.PRESET_FULL_RECT
|
||||||
|
dialog.add_child(content)
|
||||||
|
await show_dialog(dialog, min_size)
|
||||||
|
|
||||||
|
return dialog
|
||||||
|
|
||||||
|
|
||||||
|
static func show_dialog(dialog: Window, min_size := Vector2i.ZERO) -> void:
|
||||||
|
if not dialog.is_inside_tree():
|
||||||
|
dock.add_child.call_deferred(dialog)
|
||||||
|
await dialog.ready
|
||||||
|
|
||||||
|
dialog.popup_centered(min_size * EditorInterface.get_editor_scale())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Type-checking functions
|
||||||
|
static func is_popochiu_clickable(node: Node) -> bool:
|
||||||
|
return node is PopochiuCharacter \
|
||||||
|
or node is PopochiuProp \
|
||||||
|
or node is PopochiuHotspot
|
||||||
|
|
||||||
|
|
||||||
|
static func is_popochiu_object(node: Node) -> bool:
|
||||||
|
return node is PopochiuRoom \
|
||||||
|
or is_popochiu_room_object(node)
|
||||||
|
|
||||||
|
|
||||||
|
static func is_popochiu_room_object(node: Node) -> bool:
|
||||||
|
return node is PopochiuCharacter \
|
||||||
|
or node is PopochiuProp \
|
||||||
|
or node is PopochiuHotspot \
|
||||||
|
or node is PopochiuWalkableArea \
|
||||||
|
or node is PopochiuRegion
|
||||||
|
|
||||||
|
|
||||||
|
static func is_room(node: Node) -> bool:
|
||||||
|
return node is PopochiuRoom
|
||||||
|
|
||||||
|
|
||||||
|
static func is_character(node: Node) -> bool:
|
||||||
|
return node is PopochiuCharacter
|
||||||
|
|
||||||
|
|
||||||
|
static func is_prop(node: Node) -> bool:
|
||||||
|
return node is PopochiuProp
|
||||||
|
|
||||||
|
|
||||||
|
static func is_hotspot(node: Node) -> bool:
|
||||||
|
return node is PopochiuHotspot
|
||||||
|
|
||||||
|
|
||||||
|
static func is_walkable_area(node: Node) -> bool:
|
||||||
|
return node is PopochiuWalkableArea
|
||||||
|
|
||||||
|
|
||||||
|
static func is_region(node: Node) -> bool:
|
||||||
|
return node is PopochiuRegion
|
||||||
|
|
||||||
|
|
||||||
|
static func is_marker(node: Node) -> bool:
|
||||||
|
return node is Marker2D
|
||||||
|
|
||||||
|
|
||||||
|
static func is_popochiu_obj_polygon(node: Node):
|
||||||
|
return node.is_in_group(POPOCHIU_OBJECT_POLYGON_GROUP)
|
||||||
|
|
||||||
|
|
||||||
|
# Context-checking functions
|
||||||
|
static func is_editing_room() -> bool:
|
||||||
|
# If the open scene in the editor is a PopochiuRoom, return true
|
||||||
|
return is_room(ei.get_edited_scene_root())
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-access functions
|
||||||
|
static func get_first_child_by_group(node: Node, group: StringName) -> Node:
|
||||||
|
if (node == null):
|
||||||
|
return null
|
||||||
|
for n in node.get_children():
|
||||||
|
if n.is_in_group(group):
|
||||||
|
return n
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
static func get_all_children(node, children := []) -> Array:
|
||||||
|
if node == null:
|
||||||
|
return []
|
||||||
|
children.push_back(node)
|
||||||
|
for child in node.get_children():
|
||||||
|
children = get_all_children(child, children)
|
||||||
|
return children
|
||||||
|
|
||||||
|
|
||||||
|
## Overrides the font [param font_name] in [param node] by the theme [Font] identified by
|
||||||
|
## [param editor_font_name].
|
||||||
|
static func override_font(node: Control, font_name: String, editor_font_name: String) -> void:
|
||||||
|
node.add_theme_font_override(font_name, node.get_theme_font(editor_font_name, "EditorFonts"))
|
||||||
|
|
||||||
|
|
||||||
|
static func frame_processed() -> void:
|
||||||
|
await EditorInterface.get_base_control().get_tree().process_frame
|
||||||
|
|
||||||
|
|
||||||
|
static func secs_passed(secs := 1.0) -> void:
|
||||||
|
await EditorInterface.get_base_control().get_tree().create_timer(secs).timeout
|
||||||
|
|
||||||
|
|
||||||
|
static func filesystem_scanned() -> void:
|
||||||
|
EditorInterface.get_resource_filesystem().scan.call_deferred()
|
||||||
|
await EditorInterface.get_resource_filesystem().filesystem_changed
|
||||||
|
|
||||||
|
|
||||||
|
static func pack_scene(node: Node, path := "") -> int:
|
||||||
|
var packed_scene := PackedScene.new()
|
||||||
|
packed_scene.pack(node)
|
||||||
|
|
||||||
|
if path.is_empty():
|
||||||
|
path = node.scene_file_path
|
||||||
|
|
||||||
|
return ResourceSaver.save(packed_scene, path)
|
||||||
|
|
||||||
|
|
||||||
|
## Helper function to recursively remove all folders and files inside [param folder_path].
|
||||||
|
static func remove_recursive(folder_path: String) -> bool:
|
||||||
|
if DirAccess.dir_exists_absolute(folder_path):
|
||||||
|
# Delete subfolders and their contents recursively in folder_path
|
||||||
|
for subfolder_path: String in get_absolute_directory_paths_at(folder_path):
|
||||||
|
remove_recursive(subfolder_path)
|
||||||
|
|
||||||
|
# Delete all files in folder_path
|
||||||
|
for file_path: String in get_absolute_file_paths_at(folder_path):
|
||||||
|
if DirAccess.remove_absolute(file_path) != OK:
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Once all files are deleted in folder_path, remove folder_path
|
||||||
|
if DirAccess.remove_absolute(folder_path) != OK:
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
## Helper function to get the absolute directory paths for all folders under [param folder_path].
|
||||||
|
static func get_absolute_directory_paths_at(folder_path: String) -> Array:
|
||||||
|
var dir_array : PackedStringArray = []
|
||||||
|
|
||||||
|
if DirAccess.dir_exists_absolute(folder_path):
|
||||||
|
for folder in DirAccess.get_directories_at(folder_path):
|
||||||
|
dir_array.append(folder_path.path_join(folder))
|
||||||
|
|
||||||
|
return Array(dir_array)
|
||||||
|
|
||||||
|
|
||||||
|
## Helper function to get the absolute file paths for all files under [param folder_path].
|
||||||
|
static func get_absolute_file_paths_at(folder_path: String) -> PackedStringArray:
|
||||||
|
var file_array : PackedStringArray = []
|
||||||
|
|
||||||
|
if DirAccess.dir_exists_absolute(folder_path):
|
||||||
|
for file in DirAccess.get_files_at(folder_path):
|
||||||
|
file_array.append(folder_path.path_join(file))
|
||||||
|
|
||||||
|
return file_array
|
||||||
|
|
||||||
|
|
||||||
|
## Returns an array of [PopochiuRoom] (instances) for all the rooms in the project.
|
||||||
|
static func get_rooms() -> Array[PopochiuRoom]:
|
||||||
|
var rooms: Array[PopochiuRoom] = []
|
||||||
|
rooms.assign(PopochiuResources.get_section_keys("rooms").map(
|
||||||
|
func (room_name: String) -> PopochiuRoom:
|
||||||
|
var scene_path := _room_scene_path_template.replace("%s", room_name.to_snake_case())
|
||||||
|
return (load(scene_path) as PackedScene).instantiate(PackedScene.GEN_EDIT_STATE_INSTANCE)
|
||||||
|
))
|
||||||
|
return rooms
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://bgogdq51wvqyn
|
359
addons/popochiu/editor/helpers/popochiu_gui_templates_helper.gd
Normal file
359
addons/popochiu/editor/helpers/popochiu_gui_templates_helper.gd
Normal file
|
@ -0,0 +1,359 @@
|
||||||
|
@tool
|
||||||
|
extends Resource
|
||||||
|
## Helper class for operations related to the GUI templates
|
||||||
|
|
||||||
|
static var _template_id := ""
|
||||||
|
static var _template_theme_path := ""
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
## Creates a copy of the selected template, including its components. Also generate the necessary
|
||||||
|
## scripts to define custom logic for the graphical interface and its commands.
|
||||||
|
static func copy_gui_template(
|
||||||
|
template_name: String, on_progress: Callable, on_complete: Callable
|
||||||
|
) -> void:
|
||||||
|
if (
|
||||||
|
DirAccess.dir_exists_absolute(PopochiuResources.GUI_GAME_FOLDER)
|
||||||
|
and template_name == PopochiuResources.get_data_value("ui", "template", "")
|
||||||
|
):
|
||||||
|
PopochiuUtils.print_normal("No changes in GUI template.")
|
||||||
|
|
||||||
|
on_complete.call()
|
||||||
|
return
|
||||||
|
|
||||||
|
on_progress.call(0, "Starting GUI template application")
|
||||||
|
|
||||||
|
_template_theme_path = ""
|
||||||
|
|
||||||
|
var scene_path := PopochiuResources.GUI_CUSTOM_SCENE
|
||||||
|
var commands_template_path := PopochiuResources.GUI_CUSTOM_TEMPLATE
|
||||||
|
|
||||||
|
_template_id = template_name.to_snake_case()
|
||||||
|
|
||||||
|
if _template_id != PopochiuResources.GUI_CUSTOM:
|
||||||
|
scene_path = PopochiuResources.GUI_TEMPLATES_FOLDER.path_join(
|
||||||
|
"%s/%s_gui.tscn" % [_template_id, _template_id]
|
||||||
|
)
|
||||||
|
commands_template_path = PopochiuResources.GUI_SCRIPT_TEMPLATES_FOLDER.path_join(
|
||||||
|
"%s_commands_template.gd" % _template_id
|
||||||
|
)
|
||||||
|
|
||||||
|
var script_path := PopochiuResources.GUI_GAME_SCENE.replace(".tscn", ".gd")
|
||||||
|
|
||||||
|
await _wait()
|
||||||
|
on_progress.call(5, "Creating Graphic Interface scene")
|
||||||
|
|
||||||
|
# ---- Make a copy of the selected GUI template ------------------------------------------------
|
||||||
|
if _create_scene(scene_path) != OK:
|
||||||
|
PopochiuUtils.print_error("Couldn't create %s file" % PopochiuResources.GUI_GAME_SCENE)
|
||||||
|
return
|
||||||
|
|
||||||
|
await _wait(2.0)
|
||||||
|
on_progress.call(10, "Copying a bunch of components")
|
||||||
|
|
||||||
|
# Copy the components used by the GUI template to the res://game/gui/components
|
||||||
|
# folder so devs can play with them freely -----------------------------------------------------
|
||||||
|
await copy_components(scene_path, true)
|
||||||
|
|
||||||
|
on_progress.call(60, "Creating scripts")
|
||||||
|
|
||||||
|
# Create a copy of the corresponding commands template -----------------------------------------
|
||||||
|
_copy_commands_and_gui_scripts(
|
||||||
|
commands_template_path, PopochiuResources.GUI_COMMANDS, script_path, scene_path
|
||||||
|
)
|
||||||
|
|
||||||
|
await _wait(1.5)
|
||||||
|
on_progress.call(80, "Assigning scripts")
|
||||||
|
|
||||||
|
# Update the script of the created gui.tscn so it uses the copy created above ------------------
|
||||||
|
if _update_scene_script(script_path) != OK:
|
||||||
|
PopochiuUtils.print_error("Couldn't update gui.tscn script")
|
||||||
|
return
|
||||||
|
|
||||||
|
await _wait()
|
||||||
|
on_progress.call(90, "Updating config file")
|
||||||
|
|
||||||
|
# Update the info related to the GUI template and the GUI commands script
|
||||||
|
# in the popochiu_data.cfg file ----------------------------------------------------------------
|
||||||
|
PopochiuResources.set_data_value("ui", "template", template_name)
|
||||||
|
await _wait(0.8)
|
||||||
|
|
||||||
|
on_progress.call(100, "All in place. Thanks for your patience.")
|
||||||
|
PopochiuUtils.print_normal("[wave]Selected GUI template successfully applied[/wave]")
|
||||||
|
await _wait()
|
||||||
|
await PopochiuEditorHelper.filesystem_scanned()
|
||||||
|
|
||||||
|
on_complete.call()
|
||||||
|
|
||||||
|
|
||||||
|
static func copy_component(source_scene_path: String) -> String:
|
||||||
|
var file_name := source_scene_path.get_file()
|
||||||
|
var source_folder := source_scene_path.get_base_dir()
|
||||||
|
|
||||||
|
var target_folder := source_folder.replace(PopochiuResources.GUI_ADDON_FOLDER, "")
|
||||||
|
target_folder = PopochiuResources.GUI_GAME_FOLDER + target_folder
|
||||||
|
|
||||||
|
if not DirAccess.dir_exists_absolute(target_folder):
|
||||||
|
DirAccess.make_dir_recursive_absolute(target_folder)
|
||||||
|
|
||||||
|
# Make a copy of the component and save it in the graphic interface components folder
|
||||||
|
var component_instance := (load(source_scene_path) as PackedScene).instantiate()
|
||||||
|
var target_scene_file := "%s/%s" % [target_folder, file_name]
|
||||||
|
|
||||||
|
var packed_scene := PackedScene.new()
|
||||||
|
packed_scene.pack(component_instance)
|
||||||
|
packed_scene.resource_path = target_scene_file
|
||||||
|
|
||||||
|
var err := ResourceSaver.save(packed_scene, target_scene_file)
|
||||||
|
|
||||||
|
if err != OK:
|
||||||
|
PopochiuUtils.print_error(
|
||||||
|
"Couldn't create instance of %s component. Error code: %d." % [
|
||||||
|
file_name.get_slice(".", 0).capitalize(),
|
||||||
|
err
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Move the dependencies of the source scene to the graphic interface folder
|
||||||
|
await copy_components(source_scene_path)
|
||||||
|
|
||||||
|
return target_scene_file
|
||||||
|
|
||||||
|
|
||||||
|
## Makes a copy of the components used by the original GUI template to the
|
||||||
|
## **res://game/gui/components/** folder so devs can play with those scenes without
|
||||||
|
## affecting the ones in the plugin's folder.
|
||||||
|
static func copy_components(source_scene_path: String, is_gui_game_scene := false) -> void:
|
||||||
|
var dependencies_to_update: Array[Dictionary] = []
|
||||||
|
|
||||||
|
# Make a copy of the dependencies of the graphic interface
|
||||||
|
for dep: String in ResourceLoader.get_dependencies(source_scene_path):
|
||||||
|
var source_file_path := dep.get_slice("::", 2)
|
||||||
|
|
||||||
|
if (
|
||||||
|
is_gui_game_scene and source_file_path.get_extension() == "gd"
|
||||||
|
and source_scene_path.get_base_dir() == source_file_path.get_base_dir()
|
||||||
|
):
|
||||||
|
# Ignore the script of the GUI template scene file
|
||||||
|
continue
|
||||||
|
|
||||||
|
var source_file_uid := ResourceUID.id_to_text(
|
||||||
|
ResourceLoader.get_resource_uid(source_file_path)
|
||||||
|
)
|
||||||
|
var dependency_data := {
|
||||||
|
source_path = source_file_path,
|
||||||
|
source_uid = source_file_uid,
|
||||||
|
target_path = "",
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Create the folder of the file -------------------------------------------------------
|
||||||
|
var file_name := source_file_path.get_file()
|
||||||
|
var source_folder := source_file_path.get_base_dir()
|
||||||
|
|
||||||
|
var target_folder := source_folder.replace(PopochiuResources.GUI_ADDON_FOLDER, "")
|
||||||
|
target_folder = target_folder.replace("templates/%s/" % _template_id, "")
|
||||||
|
target_folder = PopochiuResources.GUI_GAME_FOLDER + target_folder
|
||||||
|
|
||||||
|
dependency_data.target_path = "%s/%s" % [target_folder, file_name]
|
||||||
|
|
||||||
|
# Make sure all the copied components share the copied GUI theme
|
||||||
|
if source_file_path.get_extension() == "tres" and "_theme" in source_file_path:
|
||||||
|
if is_gui_game_scene and _template_theme_path.is_empty():
|
||||||
|
# Change the name of the GUI template theme so it differs from the original one
|
||||||
|
dependency_data.target_path = "%s/%s" % [
|
||||||
|
target_folder, "gui_theme.tres"
|
||||||
|
]
|
||||||
|
_template_theme_path = dependency_data.target_path
|
||||||
|
elif not is_gui_game_scene:
|
||||||
|
dependency_data.target_path = _template_theme_path
|
||||||
|
|
||||||
|
dependencies_to_update.append(dependency_data)
|
||||||
|
|
||||||
|
if FileAccess.file_exists(dependency_data.target_path):
|
||||||
|
# Ignore any file that has already been copied
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not DirAccess.dir_exists_absolute(target_folder):
|
||||||
|
DirAccess.make_dir_recursive_absolute(target_folder)
|
||||||
|
|
||||||
|
# --- Make a copy of the original file -----------------------------------------------------
|
||||||
|
if source_file_path.get_extension() == "gd":
|
||||||
|
_copy_component_script(source_file_path, dependency_data.target_path)
|
||||||
|
else:
|
||||||
|
_copy_file(source_file_path, target_folder, dependency_data.target_path)
|
||||||
|
|
||||||
|
EditorInterface.get_resource_filesystem().scan()
|
||||||
|
await EditorInterface.get_resource_filesystem().filesystem_changed
|
||||||
|
|
||||||
|
# Repeat the process for each of the dependencies of the .tscn resources
|
||||||
|
if source_file_path.get_extension() == "tscn":
|
||||||
|
await copy_components(source_file_path)
|
||||||
|
|
||||||
|
if is_gui_game_scene:
|
||||||
|
_update_dependencies(PopochiuResources.GUI_GAME_SCENE, dependencies_to_update)
|
||||||
|
else:
|
||||||
|
var game_scene_path := source_scene_path.replace(PopochiuResources.GUI_ADDON_FOLDER, "")
|
||||||
|
game_scene_path = game_scene_path.replace("templates/%s/" % _template_id, "")
|
||||||
|
game_scene_path = PopochiuResources.GUI_GAME_FOLDER + game_scene_path
|
||||||
|
|
||||||
|
_update_dependencies(game_scene_path, dependencies_to_update)
|
||||||
|
|
||||||
|
EditorInterface.get_resource_filesystem().scan()
|
||||||
|
await EditorInterface.get_resource_filesystem().filesystem_changed
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
## Create the **gui.tscn** file as a copy of the selected GUI template scene.
|
||||||
|
## If a template change is being made, all components of the previous template are removed along
|
||||||
|
## with the **.tscn** file before copying the new one.
|
||||||
|
static func _create_scene(scene_path: String) -> int:
|
||||||
|
# Create the res://game/gui/ folder
|
||||||
|
if not FileAccess.file_exists(PopochiuResources.GUI_GAME_SCENE):
|
||||||
|
DirAccess.make_dir_recursive_absolute(PopochiuResources.GUI_GAME_FOLDER)
|
||||||
|
else:
|
||||||
|
# Remove the gui.tscn file
|
||||||
|
DirAccess.remove_absolute(PopochiuResources.GUI_GAME_SCENE)
|
||||||
|
EditorInterface.get_resource_filesystem().scan()
|
||||||
|
|
||||||
|
for dir_name: String in DirAccess.get_directories_at(PopochiuResources.GUI_GAME_FOLDER):
|
||||||
|
_remove_components(PopochiuResources.GUI_GAME_FOLDER + dir_name)
|
||||||
|
|
||||||
|
# Make a copy of the selected GUI template (.tscn) and save it in
|
||||||
|
# res://game/gui/gui.tscn ------------------------------------------
|
||||||
|
var gi_scene := load(scene_path).duplicate(true)
|
||||||
|
gi_scene.resource_path = PopochiuResources.GUI_GAME_SCENE
|
||||||
|
|
||||||
|
return ResourceSaver.save(gi_scene, PopochiuResources.GUI_GAME_SCENE)
|
||||||
|
|
||||||
|
|
||||||
|
static func _remove_components(dir_path: String) -> void:
|
||||||
|
for file_name: String in DirAccess.get_files_at(dir_path):
|
||||||
|
DirAccess.remove_absolute(dir_path.path_join(file_name))
|
||||||
|
EditorInterface.get_resource_filesystem().scan()
|
||||||
|
|
||||||
|
for dir_name: String in DirAccess.get_directories_at(dir_path):
|
||||||
|
var sub_dir_path := dir_path.path_join(dir_name)
|
||||||
|
_remove_components(sub_dir_path)
|
||||||
|
|
||||||
|
# Once the directory is empty, remove it
|
||||||
|
DirAccess.remove_absolute(dir_path)
|
||||||
|
EditorInterface.get_resource_filesystem().scan()
|
||||||
|
|
||||||
|
|
||||||
|
## Makes a copy of a GUI component's script.
|
||||||
|
static func _copy_component_script(
|
||||||
|
source_file_path: String, target_file_path: String
|
||||||
|
) -> void:
|
||||||
|
# Make a copy of the original script -----------------------------------------------------------
|
||||||
|
var source_file := FileAccess.open(source_file_path, FileAccess.READ)
|
||||||
|
var source_code := source_file.get_as_text()
|
||||||
|
source_file.close()
|
||||||
|
|
||||||
|
if "class_name " in source_code:
|
||||||
|
source_code = source_code.replace("class_name Popochiu", "class_name GUI")
|
||||||
|
|
||||||
|
var file_write := FileAccess.open(target_file_path, FileAccess.WRITE)
|
||||||
|
file_write.store_string(source_code)
|
||||||
|
file_write.close()
|
||||||
|
|
||||||
|
# Create a script for devs to overwrite the functionality of the original's copy script --------
|
||||||
|
file_write = FileAccess.open(target_file_path.replace(".gd", "_custom.gd"), FileAccess.WRITE)
|
||||||
|
if "@tool" in source_code:
|
||||||
|
file_write.store_string("@tool\n")
|
||||||
|
file_write.store_string('extends "%s"' % target_file_path.get_file())
|
||||||
|
|
||||||
|
|
||||||
|
static func _copy_file(
|
||||||
|
source_file_path: String, target_folder: String, target_file_path: String
|
||||||
|
) -> void:
|
||||||
|
# ---- Create a copy of the scene file ---------------------------------------------------------
|
||||||
|
if source_file_path.get_extension() in ["tscn", "tres"]:
|
||||||
|
var file_resource := load(source_file_path).duplicate(true)
|
||||||
|
file_resource.resource_path = target_file_path
|
||||||
|
|
||||||
|
if ResourceSaver.save(file_resource, target_file_path) != OK:
|
||||||
|
DirAccess.remove_absolute(target_folder)
|
||||||
|
else:
|
||||||
|
DirAccess.copy_absolute(source_file_path, target_file_path)
|
||||||
|
|
||||||
|
|
||||||
|
## Replace the UID and paths of the components in the graphic interface scene
|
||||||
|
static func _update_dependencies(scene_path: String, dependencies_to_update: Array) -> void:
|
||||||
|
if dependencies_to_update.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
# ---- Update the UID and paths of the copied components ---------------------------------------
|
||||||
|
var file_read = FileAccess.open(scene_path, FileAccess.READ)
|
||||||
|
var text := file_read.get_as_text()
|
||||||
|
file_read.close()
|
||||||
|
|
||||||
|
for dic: Dictionary in dependencies_to_update:
|
||||||
|
var target_path: String = dic.target_path
|
||||||
|
|
||||||
|
if ".gd" in target_path:
|
||||||
|
target_path = target_path.replace(".gd", "_custom.gd")
|
||||||
|
|
||||||
|
text = text.replace(dic.source_path, target_path)
|
||||||
|
|
||||||
|
var target_uid := ResourceUID.id_to_text(ResourceLoader.get_resource_uid(target_path))
|
||||||
|
|
||||||
|
if "invalid" in target_uid: continue
|
||||||
|
|
||||||
|
text = text.replace(dic.source_uid, target_uid)
|
||||||
|
|
||||||
|
var file_write = FileAccess.open(scene_path, FileAccess.WRITE)
|
||||||
|
file_write.store_string(text)
|
||||||
|
file_write.close()
|
||||||
|
|
||||||
|
|
||||||
|
## Copy the commands and graphic interface scripts of the chosen GUI template. The new graphic
|
||||||
|
## interface scripts inherits from the one originally assigned to the .tscn file of the selected
|
||||||
|
## template.
|
||||||
|
static func _copy_commands_and_gui_scripts(
|
||||||
|
commands_template_path: String, commands_path: String, script_path: String, scene_path: String
|
||||||
|
) -> void:
|
||||||
|
DirAccess.copy_absolute(commands_template_path, commands_path)
|
||||||
|
|
||||||
|
# Create a copy of the graphic interface script template ---------------------------------------
|
||||||
|
var template_path := (
|
||||||
|
PopochiuResources.GUI_SCRIPT_TEMPLATES_FOLDER + "gui_template.gd"
|
||||||
|
)
|
||||||
|
var script_file := FileAccess.open(template_path, FileAccess.READ)
|
||||||
|
var source_code := script_file.get_as_text()
|
||||||
|
script_file.close()
|
||||||
|
source_code = source_code.replace(
|
||||||
|
"extends PopochiuGraphicInterface",
|
||||||
|
'extends "%s"' % scene_path.replace(".tscn", ".gd")
|
||||||
|
)
|
||||||
|
script_file = FileAccess.open(script_path, FileAccess.WRITE)
|
||||||
|
script_file.store_string(source_code)
|
||||||
|
script_file.close()
|
||||||
|
|
||||||
|
|
||||||
|
## Updates the script of the created [b]res://game/gui/gui.tscn[/b] file so it uses the one created
|
||||||
|
## in [method _copy_commands_and_gui_scripts].
|
||||||
|
static func _update_scene_script(script_path: String) -> int:
|
||||||
|
# Update the script of the GUI -----------------------------------------------------------------
|
||||||
|
var scene := (load(
|
||||||
|
PopochiuResources.GUI_GAME_SCENE
|
||||||
|
) as PackedScene).instantiate()
|
||||||
|
scene.set_script(load(script_path))
|
||||||
|
|
||||||
|
# Set the name of the root node
|
||||||
|
scene.name = "GUI"
|
||||||
|
|
||||||
|
var packed_scene: PackedScene = PackedScene.new()
|
||||||
|
packed_scene.pack(scene)
|
||||||
|
packed_scene.resource_path = PopochiuResources.GUI_GAME_SCENE
|
||||||
|
|
||||||
|
return ResourceSaver.save(packed_scene, PopochiuResources.GUI_GAME_SCENE)
|
||||||
|
|
||||||
|
|
||||||
|
static func _wait(max := 1.0) -> void:
|
||||||
|
await PopochiuEditorHelper.secs_passed(randf_range(0.5, max))
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://cn6emnkso14qe
|
9
addons/popochiu/editor/helpers/popochiu_signal_bus.gd
Normal file
9
addons/popochiu/editor/helpers/popochiu_signal_bus.gd
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
extends RefCounted
|
||||||
|
## Helper Editor class to emit and connect to signals across different components in the plugin
|
||||||
|
|
||||||
|
signal main_scene_changed(scene_path: String)
|
||||||
|
signal pc_changed(script_name: String)
|
||||||
|
signal audio_cues_deleted(cue_file_paths: Array)
|
||||||
|
signal main_object_added(type: int, name_to_add: String)
|
||||||
|
signal gizmo_visibility_changed(gizmo: int, visible: bool)
|
||||||
|
signal migrations_done
|
|
@ -0,0 +1 @@
|
||||||
|
uid://p35cn6n5xi6r
|
390
addons/popochiu/editor/importers/aseprite/animation_creator.gd
Normal file
390
addons/popochiu/editor/importers/aseprite/animation_creator.gd
Normal file
|
@ -0,0 +1,390 @@
|
||||||
|
@tool
|
||||||
|
# This logic has been taken almost as-is from Vinicius Gerevini's
|
||||||
|
# Aseprite Wizard plugin. Credits goes to him for the real magic.
|
||||||
|
# See: https://godotengine.org/asset-library/asset/713
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
const RESULT_CODE = preload("res://addons/popochiu/editor/config/result_codes.gd")
|
||||||
|
const _DEFAULT_AL = "" # Empty string equals default "Global" animation library
|
||||||
|
|
||||||
|
# Vars configured on initialization
|
||||||
|
var _file_system: EditorFileSystem
|
||||||
|
var _aseprite: RefCounted
|
||||||
|
|
||||||
|
# Vars configured on animations creation
|
||||||
|
var _target_node: Node
|
||||||
|
var _player: AnimationPlayer
|
||||||
|
var _options: Dictionary
|
||||||
|
|
||||||
|
# Class-logic vars
|
||||||
|
var _spritesheet_metadata = {}
|
||||||
|
var _target_sprite: Sprite2D
|
||||||
|
var _output: Dictionary
|
||||||
|
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
func init(aseprite: RefCounted, editor_file_system: EditorFileSystem = null):
|
||||||
|
_file_system = editor_file_system
|
||||||
|
_aseprite = aseprite
|
||||||
|
|
||||||
|
|
||||||
|
## Public interfaces, dedicated to specific popochiu objects
|
||||||
|
func create_character_animations(character: Node, player: AnimationPlayer, options: Dictionary):
|
||||||
|
# Chores
|
||||||
|
_target_node = character
|
||||||
|
_player = player
|
||||||
|
_options = options
|
||||||
|
|
||||||
|
# Duly check everything is valid and cleanup animations
|
||||||
|
var result = _perform_common_checks()
|
||||||
|
if result != RESULT_CODE.SUCCESS:
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Create the spritesheet
|
||||||
|
result = await _create_spritesheet_from_file()
|
||||||
|
if result != RESULT_CODE.SUCCESS:
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Load tags information
|
||||||
|
result = await _load_spritesheet_metadata()
|
||||||
|
if result != RESULT_CODE.SUCCESS:
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Set the texture in the sprite and configure
|
||||||
|
# the animations in the AnimationPlayer
|
||||||
|
_setup_texture()
|
||||||
|
result = _configure_animations()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
func create_prop_animations(prop: Node, aseprite_tag: String, options: Dictionary):
|
||||||
|
# Chores
|
||||||
|
_target_node = prop
|
||||||
|
# TODO: if the prop has no AnimationPlayer, add one!
|
||||||
|
_player = prop.get_node("AnimationPlayer")
|
||||||
|
_options = options
|
||||||
|
|
||||||
|
var prop_animation_name = aseprite_tag.to_snake_case()
|
||||||
|
|
||||||
|
# Duly check everything is valid and cleanup animations
|
||||||
|
var result = _perform_common_checks()
|
||||||
|
if result != RESULT_CODE.SUCCESS:
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Create the spritesheet
|
||||||
|
result = await _create_spritesheet_from_tag(aseprite_tag)
|
||||||
|
if result != RESULT_CODE.SUCCESS:
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Load tags information
|
||||||
|
result = await _load_spritesheet_metadata(aseprite_tag)
|
||||||
|
if result != RESULT_CODE.SUCCESS:
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Set the texture in the sprite and configure
|
||||||
|
# the animations in the AnimationPlayer
|
||||||
|
_setup_texture()
|
||||||
|
result = _configure_animations()
|
||||||
|
|
||||||
|
# Sorry, mom...
|
||||||
|
_player.autoplay = prop.name.to_snake_case()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
## This function creates a spritesheet with the whole file content
|
||||||
|
func _create_spritesheet_from_file():
|
||||||
|
## TODO: See _aseprite.export_layer() when the time comes to add layers selection
|
||||||
|
_output = _aseprite.export_file(_options.source, _options.output_folder, _options)
|
||||||
|
if _output.is_empty():
|
||||||
|
return RESULT_CODE.ERR_ASEPRITE_EXPORT_FAILED
|
||||||
|
return RESULT_CODE.SUCCESS
|
||||||
|
|
||||||
|
|
||||||
|
## This function creates a spritesheet with the frames of a specific tag
|
||||||
|
## WARNING: it's case sensitive
|
||||||
|
func _create_spritesheet_from_tag(selected_tag: String):
|
||||||
|
## TODO: See _aseprite.export_layer() when the time comes to add layers selection
|
||||||
|
_output = _aseprite.export_tag(_options.source, selected_tag, _options.output_folder, _options)
|
||||||
|
if _output.is_empty():
|
||||||
|
return RESULT_CODE.ERR_ASEPRITE_EXPORT_FAILED
|
||||||
|
return RESULT_CODE.SUCCESS
|
||||||
|
|
||||||
|
|
||||||
|
func _load_spritesheet_metadata(selected_tag: String = ""):
|
||||||
|
_spritesheet_metadata = {
|
||||||
|
tags = {},
|
||||||
|
frames = {},
|
||||||
|
meta = {},
|
||||||
|
sprite_sheet = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Refresh filesystem
|
||||||
|
await _scan_filesystem()
|
||||||
|
|
||||||
|
# Collect all needed info
|
||||||
|
var source_file = _output.data_file
|
||||||
|
var sprite_sheet = _output.sprite_sheet
|
||||||
|
|
||||||
|
# Try to access, decode and validate Aseprite JSON output
|
||||||
|
var file = FileAccess.open(source_file, FileAccess.READ)
|
||||||
|
if file == null:
|
||||||
|
return file.get_open_error()
|
||||||
|
|
||||||
|
var test_json_conv = JSON.new()
|
||||||
|
test_json_conv.parse(file.get_as_text())
|
||||||
|
var content = test_json_conv.get_data()
|
||||||
|
|
||||||
|
if not _aseprite.is_valid_spritesheet(content):
|
||||||
|
return RESULT_CODE.ERR_INVALID_ASEPRITE_SPRITESHEET
|
||||||
|
|
||||||
|
# Save image metadata from JSON data
|
||||||
|
_spritesheet_metadata.meta = content.meta
|
||||||
|
|
||||||
|
# Save frames metadata from JSON data
|
||||||
|
_spritesheet_metadata.frames = _aseprite.get_content_frames(content)
|
||||||
|
|
||||||
|
# Save tags metadata, starting from user's selection, and retrieving
|
||||||
|
# other information from JSON data
|
||||||
|
var tags = _options.get("tags").filter(func(tag): return tag.get("import"))
|
||||||
|
|
||||||
|
for t in tags:
|
||||||
|
# If a tag is specified, ignore every other ones
|
||||||
|
if not selected_tag.is_empty() and selected_tag != t.tag_name: continue
|
||||||
|
# Create a lookup table for tags
|
||||||
|
_spritesheet_metadata.tags[t.tag_name] = t
|
||||||
|
|
||||||
|
for ft in _aseprite.get_content_meta_tags(content):
|
||||||
|
if not _spritesheet_metadata.tags.has(ft.name): continue
|
||||||
|
_spritesheet_metadata.tags.get(ft.name).merge({
|
||||||
|
from = ft.from,
|
||||||
|
to = ft.to,
|
||||||
|
direction = ft.direction,
|
||||||
|
})
|
||||||
|
|
||||||
|
# If a tag is specified, the tags lookup table should contain
|
||||||
|
# a single tag information. In this case the to and from properties
|
||||||
|
# must be shifted back in the [1 - tag_length] range.
|
||||||
|
if not selected_tag.is_empty():
|
||||||
|
# Using a temp variable to make this readable
|
||||||
|
var t = _spritesheet_metadata.tags[selected_tag]
|
||||||
|
# NOTE: imagine this goes from 34 to 54, we need to shift
|
||||||
|
# the range back of a 33 amount, so it goes from 1 to (54 - 33)
|
||||||
|
t.to = t.to - t.from + 1
|
||||||
|
t.from = 0
|
||||||
|
_spritesheet_metadata.tags[selected_tag] = t
|
||||||
|
|
||||||
|
# Save spritesheet path from the command output
|
||||||
|
_spritesheet_metadata.sprite_sheet = sprite_sheet
|
||||||
|
|
||||||
|
# Remove the JSON file if config says so
|
||||||
|
if PopochiuEditorConfig.should_remove_source_files():
|
||||||
|
DirAccess.remove_absolute(_output.data_file)
|
||||||
|
await _scan_filesystem()
|
||||||
|
|
||||||
|
return RESULT_CODE.SUCCESS
|
||||||
|
|
||||||
|
|
||||||
|
func _configure_animations():
|
||||||
|
if not _player.has_animation_library(_DEFAULT_AL):
|
||||||
|
_player.add_animation_library(_DEFAULT_AL, AnimationLibrary.new())
|
||||||
|
|
||||||
|
if _spritesheet_metadata.tags.size() > 0:
|
||||||
|
var result = RESULT_CODE.SUCCESS
|
||||||
|
# RESTART_FROM_HERE: WARNING: in case of prop and inventory, the JSON file contains
|
||||||
|
# the whole set of tags, so we must take the tag.from and tag.to and remap the range
|
||||||
|
# from "1" to "tag.to +1 - tag.from + 1" (do the math an you'll see that's correct)
|
||||||
|
for tag in _spritesheet_metadata.tags.values():
|
||||||
|
var selected_frames = _spritesheet_metadata.frames.slice(tag.from, tag.to + 1) # slice is [)
|
||||||
|
result = _add_animation_frames(tag.tag_name, selected_frames, tag.direction)
|
||||||
|
if result != RESULT_CODE.SUCCESS:
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
return _add_animation_frames("default", _spritesheet_metadata.frames)
|
||||||
|
|
||||||
|
|
||||||
|
func _add_animation_frames(anim_name: String, frames: Array, direction = 'forward'):
|
||||||
|
# TODO: ATM there is no way to assign a walk/talk/grab/idle animation
|
||||||
|
# with a different name than the standard ones. The engine is searching for
|
||||||
|
# lowercase names in the AnimationPlayer, thus we are forcing snake_case
|
||||||
|
# animations name conversion.
|
||||||
|
# We have to add methods or properties to the Character to assign different
|
||||||
|
# animations (but maybe we can do with anim_prefix or other strategies).
|
||||||
|
var animation_name = anim_name.to_snake_case()
|
||||||
|
var is_loopable = _spritesheet_metadata.tags.get(anim_name).get("loops")
|
||||||
|
|
||||||
|
# Create animation library if it doesn't exist
|
||||||
|
# This is always true if the user selected to wipe old animations.
|
||||||
|
# See _remove_animations_from_player() function.
|
||||||
|
if not _player.has_animation_library(_DEFAULT_AL):
|
||||||
|
_player.add_animation_library(_DEFAULT_AL, AnimationLibrary.new())
|
||||||
|
|
||||||
|
if not _player.get_animation_library(_DEFAULT_AL).has_animation(animation_name):
|
||||||
|
_player.get_animation_library(_DEFAULT_AL).add_animation(animation_name, Animation.new())
|
||||||
|
|
||||||
|
# Here is where animations are created.
|
||||||
|
# TODO: we need to "fork" the logic so that Character has a single spritesheet
|
||||||
|
# containing all tags, while Rooms/Props and Inventory Items has a single spritesheet
|
||||||
|
# for each tag, so that you can have each prop with its own animation (PnC)
|
||||||
|
var animation = _player.get_animation(animation_name)
|
||||||
|
_create_meta_tracks(animation)
|
||||||
|
var frame_track = _get_property_track_path("frame")
|
||||||
|
var frame_track_index = _create_track(_target_sprite, animation, frame_track)
|
||||||
|
|
||||||
|
if direction == 'reverse':
|
||||||
|
frames.reverse()
|
||||||
|
|
||||||
|
var animation_length = 0
|
||||||
|
|
||||||
|
for frame in frames:
|
||||||
|
var frame_key = _get_frame_key(frame)
|
||||||
|
animation.track_insert_key(frame_track_index, animation_length, frame_key)
|
||||||
|
animation_length += frame.duration / 1000 ## NOTE: animation_length is in seconds
|
||||||
|
|
||||||
|
if direction == 'pingpong':
|
||||||
|
frames.remove_at(frames.size() - 1)
|
||||||
|
if is_loopable:
|
||||||
|
frames.remove_at(0)
|
||||||
|
frames.reverse()
|
||||||
|
|
||||||
|
for frame in frames:
|
||||||
|
var frame_key = _get_frame_key(frame)
|
||||||
|
animation.track_insert_key(frame_track_index, animation_length, frame_key)
|
||||||
|
animation_length += frame.duration / 1000 ## NOTE: animation_length is in seconds
|
||||||
|
|
||||||
|
animation.length = animation_length
|
||||||
|
animation.loop_mode = Animation.LOOP_LINEAR if is_loopable else Animation.LOOP_NONE
|
||||||
|
|
||||||
|
return RESULT_CODE.SUCCESS
|
||||||
|
|
||||||
|
|
||||||
|
## TODO: insert validate tokens in animation name
|
||||||
|
func _create_track(target_sprite: Node, animation: Animation, track: String):
|
||||||
|
var track_index = animation.find_track(track, Animation.TYPE_VALUE)
|
||||||
|
|
||||||
|
if track_index != -1:
|
||||||
|
animation.remove_track(track_index)
|
||||||
|
|
||||||
|
track_index = animation.add_track(Animation.TYPE_VALUE)
|
||||||
|
## Here we set a label for the track in the sprite_path:property_changed format
|
||||||
|
## so that _get_property_track_path can rebuild it by naming convention
|
||||||
|
animation.track_set_path(track_index, track)
|
||||||
|
animation.track_set_interpolation_loop_wrap(track_index, false)
|
||||||
|
animation.value_track_set_update_mode(track_index, Animation.UPDATE_DISCRETE)
|
||||||
|
|
||||||
|
return track_index
|
||||||
|
|
||||||
|
|
||||||
|
func _get_property_track_path(prop: String) -> String:
|
||||||
|
var node_path = _player.get_node(_player.root_node).get_path_to(_target_sprite)
|
||||||
|
return "%s:%s" % [node_path, prop]
|
||||||
|
|
||||||
|
|
||||||
|
func _scan_filesystem():
|
||||||
|
_file_system.scan()
|
||||||
|
await _file_system.filesystem_changed
|
||||||
|
|
||||||
|
|
||||||
|
func _remove_properties_from_path(path: NodePath) -> NodePath:
|
||||||
|
var string_path := path as String
|
||||||
|
if !(":" in string_path):
|
||||||
|
return string_path as NodePath
|
||||||
|
|
||||||
|
var property_path := path.get_concatenated_subnames() as String
|
||||||
|
string_path = string_path.substr(0, string_path.length() - property_path.length() - 1)
|
||||||
|
|
||||||
|
return string_path as NodePath
|
||||||
|
|
||||||
|
|
||||||
|
# ---- SPRITE NODE LOGIC ---------------------------------------------------------------------------
|
||||||
|
## What follow is logic specifically gathered for Sprite elements. TextureRect should
|
||||||
|
## be treated in a different way (see texture_rect_animation_creator.gd file in
|
||||||
|
## original Aseprite Wizard plugin by Vinicius Gerevini)
|
||||||
|
func _setup_texture():
|
||||||
|
# Load texture in target sprite (ignoring cache and forcing a refres)
|
||||||
|
var texture = ResourceLoader.load(
|
||||||
|
_spritesheet_metadata.sprite_sheet, 'Image', ResourceLoader.CACHE_MODE_IGNORE
|
||||||
|
)
|
||||||
|
texture.take_over_path(_spritesheet_metadata.sprite_sheet)
|
||||||
|
_target_sprite.texture = texture
|
||||||
|
|
||||||
|
if _spritesheet_metadata.frames.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
_target_sprite.hframes = (
|
||||||
|
_spritesheet_metadata.meta.size.w / _spritesheet_metadata.frames[0].sourceSize.w
|
||||||
|
)
|
||||||
|
_target_sprite.vframes = (
|
||||||
|
_spritesheet_metadata.meta.size.h / _spritesheet_metadata.frames[0].sourceSize.h
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _create_meta_tracks(animation: Animation):
|
||||||
|
var hframes_track = _get_property_track_path("hframes")
|
||||||
|
var hframes_track_index = _create_track(_target_sprite, animation, hframes_track)
|
||||||
|
animation.track_insert_key(hframes_track_index, 0, _target_sprite.hframes)
|
||||||
|
|
||||||
|
var vframes_track = _get_property_track_path("vframes")
|
||||||
|
var vframes_track_index = _create_track(_target_sprite, animation, vframes_track)
|
||||||
|
animation.track_insert_key(vframes_track_index, 0, _target_sprite.vframes)
|
||||||
|
|
||||||
|
var visible_track = _get_property_track_path("visible")
|
||||||
|
var visible_track_index = _create_track(_target_sprite, animation, visible_track)
|
||||||
|
animation.track_insert_key(visible_track_index, 0, true)
|
||||||
|
|
||||||
|
|
||||||
|
func _get_frame_key(frame: Dictionary):
|
||||||
|
return _calculate_frame_index(_target_sprite,frame)
|
||||||
|
|
||||||
|
|
||||||
|
func _calculate_frame_index(sprite: Node, frame: Dictionary) -> int:
|
||||||
|
var column = floor(frame.frame.x * sprite.hframes / sprite.texture.get_width())
|
||||||
|
var row = floor(frame.frame.y * sprite.vframes / sprite.texture.get_height())
|
||||||
|
return (row * sprite.hframes) + column
|
||||||
|
|
||||||
|
|
||||||
|
func _perform_common_checks():
|
||||||
|
# Checks
|
||||||
|
if not _aseprite.check_command_path():
|
||||||
|
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH
|
||||||
|
|
||||||
|
if not _aseprite.test_command():
|
||||||
|
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND
|
||||||
|
|
||||||
|
if not FileAccess.file_exists(_options.source):
|
||||||
|
return RESULT_CODE.ERR_SOURCE_FILE_NOT_FOUND
|
||||||
|
|
||||||
|
if not DirAccess.dir_exists_absolute(_options.output_folder):
|
||||||
|
return RESULT_CODE.ERR_OUTPUT_FOLDER_NOT_FOUND
|
||||||
|
|
||||||
|
_target_sprite = _find_sprite_in_target()
|
||||||
|
|
||||||
|
if _target_sprite == null:
|
||||||
|
return RESULT_CODE.ERR_NO_SPRITE_FOUND
|
||||||
|
|
||||||
|
if typeof(_options.get("tags")) != TYPE_ARRAY:
|
||||||
|
return RESULT_CODE.ERR_TAGS_OPTIONS_ARRAY_EMPTY
|
||||||
|
|
||||||
|
if (_options.wipe_old_animations):
|
||||||
|
_remove_animations_from_player(_player)
|
||||||
|
|
||||||
|
return RESULT_CODE.SUCCESS
|
||||||
|
|
||||||
|
|
||||||
|
func _find_sprite_in_target() -> Node:
|
||||||
|
if not _target_node.has_node("Sprite2D"):
|
||||||
|
return null
|
||||||
|
return _target_node.get_node("Sprite2D")
|
||||||
|
|
||||||
|
|
||||||
|
func _remove_animations_from_player(player: AnimationPlayer):
|
||||||
|
if player.has_animation_library(_DEFAULT_AL):
|
||||||
|
player.remove_animation_library(_DEFAULT_AL)
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://c44sonibms74d
|
249
addons/popochiu/editor/importers/aseprite/aseprite_controller.gd
Normal file
249
addons/popochiu/editor/importers/aseprite/aseprite_controller.gd
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
@tool
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
|
||||||
|
# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░
|
||||||
|
func export_file(file_name: String, output_folder: String, options: Dictionary) -> Dictionary:
|
||||||
|
var exception_pattern = options.get('exception_pattern', "")
|
||||||
|
var only_visible_layers = options.get('only_visible_layers', false)
|
||||||
|
var output_name = (
|
||||||
|
file_name if options.get('output_filename') == ""
|
||||||
|
else options.get('output_filename', file_name)
|
||||||
|
)
|
||||||
|
var basename = _get_file_basename(output_name)
|
||||||
|
var output_dir = output_folder.replace("res://", "./")
|
||||||
|
var data_file = "%s/%s.json" % [output_dir, basename]
|
||||||
|
var sprite_sheet = "%s/%s.png" % [output_dir, basename]
|
||||||
|
var output = []
|
||||||
|
var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet)
|
||||||
|
|
||||||
|
if not only_visible_layers:
|
||||||
|
arguments.push_front("--all-layers")
|
||||||
|
|
||||||
|
_add_sheet_type_arguments(arguments, options)
|
||||||
|
|
||||||
|
_add_ignore_layer_arguments(file_name, arguments, exception_pattern)
|
||||||
|
|
||||||
|
var exit_code = _execute(arguments, output)
|
||||||
|
if exit_code != 0:
|
||||||
|
printerr('[Popochiu] Aseprite: failed to export spritesheet')
|
||||||
|
printerr(output)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'data_file': data_file.replace("./", "res://"),
|
||||||
|
"sprite_sheet": sprite_sheet.replace("./", "res://")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func export_layers(file_name: String, output_folder: String, options: Dictionary) -> Array:
|
||||||
|
var exception_pattern = options.get('exception_pattern', "")
|
||||||
|
var only_visible_layers = options.get('only_visible_layers', false)
|
||||||
|
var basename = _get_file_basename(file_name)
|
||||||
|
var layers = list_layers(file_name, only_visible_layers)
|
||||||
|
var exception_regex = _compile_regex(exception_pattern)
|
||||||
|
|
||||||
|
var output = []
|
||||||
|
|
||||||
|
for layer in layers:
|
||||||
|
if layer != "" and (not exception_regex or exception_regex.search(layer) == null):
|
||||||
|
output.push_back(export_layer(file_name, layer, output_folder, options))
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
func export_layer(file_name: String, layer_name: String, output_folder: String, options: Dictionary) -> Dictionary:
|
||||||
|
var output_prefix = options.get('output_filename', "").strip_edges()
|
||||||
|
var output_dir = output_folder.replace("res://", "./").strip_edges()
|
||||||
|
var data_file = "%s/%s%s.json" % [output_dir, output_prefix, layer_name]
|
||||||
|
var sprite_sheet = "%s/%s%s.png" % [output_dir, output_prefix, layer_name]
|
||||||
|
var output = []
|
||||||
|
var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet)
|
||||||
|
arguments.push_front(layer_name)
|
||||||
|
arguments.push_front("--layer")
|
||||||
|
|
||||||
|
_add_sheet_type_arguments(arguments, options)
|
||||||
|
|
||||||
|
var exit_code = _execute(arguments, output)
|
||||||
|
if exit_code != 0:
|
||||||
|
printerr('[Popochiu] Aseprite: Failed to export layer spritesheet. Command output follows:')
|
||||||
|
print(output)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'data_file': data_file.replace("./", "res://"),
|
||||||
|
"sprite_sheet": sprite_sheet.replace("./", "res://")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# IMPROVE: See if we can extract JSON data limited to the single tag
|
||||||
|
# (so we don't have to reckon offset framerange)
|
||||||
|
func export_tag(file_name: String, tag_name: String, output_folder: String, options: Dictionary) -> Dictionary:
|
||||||
|
var output_prefix = options.get('output_filename', "").strip_edges()
|
||||||
|
var output_dir = output_folder.replace("res://", "./").strip_edges()
|
||||||
|
var data_file = "%s/%s%s.json" % [output_dir, output_prefix, tag_name]
|
||||||
|
var sprite_sheet = "%s/%s%s.png" % [output_dir, output_prefix, tag_name]
|
||||||
|
var output = []
|
||||||
|
var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet)
|
||||||
|
arguments.push_front(tag_name)
|
||||||
|
arguments.push_front("--tag")
|
||||||
|
|
||||||
|
_add_sheet_type_arguments(arguments, options)
|
||||||
|
|
||||||
|
var exit_code = _execute(arguments, output)
|
||||||
|
if exit_code != 0:
|
||||||
|
printerr('[Popochiu] Aseprite: Failed to export tag spritesheet. Command output follows:')
|
||||||
|
print(output)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'data_file': data_file.replace("./", "res://"),
|
||||||
|
"sprite_sheet": sprite_sheet.replace("./", "res://")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func list_layers(file_name: String, only_visible = false) -> Array:
|
||||||
|
var output = []
|
||||||
|
var arguments = ["-b", "--list-layers", file_name]
|
||||||
|
|
||||||
|
if not only_visible:
|
||||||
|
arguments.push_front("--all-layers")
|
||||||
|
|
||||||
|
var exit_code = _execute(arguments, output)
|
||||||
|
|
||||||
|
if exit_code != 0:
|
||||||
|
printerr('[Popochiu] Aseprite: failed listing layers')
|
||||||
|
printerr(output)
|
||||||
|
return []
|
||||||
|
|
||||||
|
return _sanitize_list_output(output)
|
||||||
|
|
||||||
|
|
||||||
|
func list_tags(file_name: String) -> Array:
|
||||||
|
var output = []
|
||||||
|
var arguments = ["-b", "--list-tags", file_name]
|
||||||
|
|
||||||
|
var exit_code = _execute(arguments, output)
|
||||||
|
|
||||||
|
if exit_code != 0:
|
||||||
|
printerr('[Popochiu] Aseprite: failed listing tags')
|
||||||
|
printerr(output)
|
||||||
|
return []
|
||||||
|
|
||||||
|
return _sanitize_list_output(output)
|
||||||
|
|
||||||
|
|
||||||
|
func is_valid_spritesheet(content):
|
||||||
|
return content.has("frames") and content.has("meta") and content.meta.has('image')
|
||||||
|
|
||||||
|
|
||||||
|
func get_content_frames(content):
|
||||||
|
return content.frames if typeof(content.frames) == TYPE_ARRAY else content.frames.values()
|
||||||
|
|
||||||
|
|
||||||
|
func get_content_meta_tags(content):
|
||||||
|
return content.meta.frameTags if content.meta.has("frameTags") else []
|
||||||
|
|
||||||
|
|
||||||
|
func check_command_path():
|
||||||
|
# On Linux, MacOS or other *nix platforms, nothing to do
|
||||||
|
if not OS.get_name() in ["Windows", "UWP"]:
|
||||||
|
return true
|
||||||
|
|
||||||
|
# On Windows, OS.Execute() calls trigger an uncatchable
|
||||||
|
# internal error if the invoked executable is not found.
|
||||||
|
# Since the error is unclear, we have to check that the aseprite
|
||||||
|
# command is given as a full path and return an error if it's not.
|
||||||
|
var regex = RegEx.new()
|
||||||
|
regex.compile("^[A-Z|a-z]:[\\\\|\\/].+\\.exe$")
|
||||||
|
return \
|
||||||
|
regex.search(_get_aseprite_command()) \
|
||||||
|
and \
|
||||||
|
FileAccess.file_exists(_get_aseprite_command())
|
||||||
|
|
||||||
|
|
||||||
|
func test_command():
|
||||||
|
var exit_code = OS.execute(_get_aseprite_command(), ['--version'], [], true)
|
||||||
|
return exit_code == 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░
|
||||||
|
func _add_ignore_layer_arguments(file_name: String, arguments: Array, exception_pattern: String):
|
||||||
|
var layers = _get_exception_layers(file_name, exception_pattern)
|
||||||
|
if not layers.is_empty():
|
||||||
|
for l in layers:
|
||||||
|
arguments.push_front(l)
|
||||||
|
arguments.push_front('--ignore-layer')
|
||||||
|
|
||||||
|
|
||||||
|
func _add_sheet_type_arguments(arguments: Array, options : Dictionary):
|
||||||
|
var column_count : int = options.get("column_count", 0)
|
||||||
|
if column_count > 0:
|
||||||
|
arguments.push_back("--merge-duplicates") # Yes, this is undocumented
|
||||||
|
arguments.push_back("--sheet-columns")
|
||||||
|
arguments.push_back(column_count)
|
||||||
|
else:
|
||||||
|
arguments.push_back("--sheet-pack")
|
||||||
|
|
||||||
|
|
||||||
|
func _get_exception_layers(file_name: String, exception_pattern: String) -> Array:
|
||||||
|
var layers = list_layers(file_name)
|
||||||
|
var regex = _compile_regex(exception_pattern)
|
||||||
|
if regex == null:
|
||||||
|
return []
|
||||||
|
|
||||||
|
var exception_layers = []
|
||||||
|
for layer in layers:
|
||||||
|
if regex.search(layer) != null:
|
||||||
|
exception_layers.push_back(layer)
|
||||||
|
|
||||||
|
return exception_layers
|
||||||
|
|
||||||
|
|
||||||
|
func _sanitize_list_output(output) -> Array:
|
||||||
|
if output.is_empty():
|
||||||
|
return output
|
||||||
|
|
||||||
|
var raw = output[0].split('\n')
|
||||||
|
var sanitized = []
|
||||||
|
for s in raw:
|
||||||
|
sanitized.append(s.strip_edges())
|
||||||
|
return sanitized
|
||||||
|
|
||||||
|
|
||||||
|
func _export_command_common_arguments(source_name: String, data_path: String, spritesheet_path: String) -> Array:
|
||||||
|
return [
|
||||||
|
"-b",
|
||||||
|
"--list-tags",
|
||||||
|
"--data",
|
||||||
|
data_path,
|
||||||
|
"--format",
|
||||||
|
"json-array",
|
||||||
|
"--sheet",
|
||||||
|
spritesheet_path,
|
||||||
|
source_name
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
func _execute(arguments, output):
|
||||||
|
return OS.execute(_get_aseprite_command(), arguments, output, true, true)
|
||||||
|
|
||||||
|
|
||||||
|
func _get_aseprite_command() -> String:
|
||||||
|
return PopochiuEditorConfig.get_command()
|
||||||
|
|
||||||
|
|
||||||
|
func _get_file_basename(file_path: String) -> String:
|
||||||
|
return file_path.get_file().trim_suffix('.%s' % file_path.get_extension())
|
||||||
|
|
||||||
|
|
||||||
|
func _compile_regex(pattern):
|
||||||
|
if pattern == "":
|
||||||
|
return
|
||||||
|
|
||||||
|
var rgx = RegEx.new()
|
||||||
|
if rgx.compile(pattern) == OK:
|
||||||
|
return rgx
|
||||||
|
|
||||||
|
printerr('[Popochiu] exception regex error')
|
|
@ -0,0 +1 @@
|
||||||
|
uid://d1vhl7uqwadfx
|
|
@ -0,0 +1,94 @@
|
||||||
|
@tool
|
||||||
|
extends HBoxContainer
|
||||||
|
|
||||||
|
signal tag_state_changed
|
||||||
|
|
||||||
|
const RESULT_CODE = preload("res://addons/popochiu/editor/config/result_codes.gd")
|
||||||
|
|
||||||
|
var _anim_tag_state: Dictionary = {}
|
||||||
|
|
||||||
|
@onready var tag_name_label = $HBoxContainer/TagName
|
||||||
|
@onready var import_toggle = $Panel/HBoxContainer/Import
|
||||||
|
@onready var loops_toggle = $Panel/HBoxContainer/Loops
|
||||||
|
@onready var separator = $Panel/HBoxContainer/Separator
|
||||||
|
@onready var visible_toggle = $Panel/HBoxContainer/Visible
|
||||||
|
@onready var clickable_toggle = $Panel/HBoxContainer/Clickable
|
||||||
|
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _ready():
|
||||||
|
# Common toggle icons
|
||||||
|
import_toggle.icon = get_theme_icon('Load', 'EditorIcons')
|
||||||
|
loops_toggle.icon = get_theme_icon('Loop', 'EditorIcons')
|
||||||
|
# Room-related toggle icons
|
||||||
|
visible_toggle.icon = get_theme_icon('GuiVisibilityVisible', 'EditorIcons')
|
||||||
|
clickable_toggle.icon = get_theme_icon('ToolSelect', 'EditorIcons')
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
func init(tag_cfg: Dictionary):
|
||||||
|
if tag_cfg.tag_name == null or tag_cfg.tag_name == "":
|
||||||
|
printerr(RESULT_CODE.get_error_message(RESULT_CODE.ERR_UNNAMED_TAG_DETECTED))
|
||||||
|
return false
|
||||||
|
|
||||||
|
_anim_tag_state = _load_default_tag_state()
|
||||||
|
_anim_tag_state.merge(tag_cfg, true)
|
||||||
|
_setup_scene()
|
||||||
|
|
||||||
|
func show_prop_buttons():
|
||||||
|
separator.visible = true
|
||||||
|
visible_toggle.visible = true
|
||||||
|
clickable_toggle.visible = true
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SetGet #####################################################################################
|
||||||
|
func get_cfg() -> Dictionary:
|
||||||
|
return _anim_tag_state
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _setup_scene():
|
||||||
|
import_toggle.button_pressed = _anim_tag_state.import
|
||||||
|
loops_toggle.button_pressed = _anim_tag_state.loops
|
||||||
|
tag_name_label.text = _anim_tag_state.tag_name
|
||||||
|
visible_toggle.button_pressed = _anim_tag_state.prop_visible
|
||||||
|
clickable_toggle.button_pressed = _anim_tag_state.prop_clickable
|
||||||
|
emit_signal("tag_state_changed")
|
||||||
|
|
||||||
|
|
||||||
|
func _load_default_tag_state() -> Dictionary:
|
||||||
|
return {
|
||||||
|
"tag_name": "",
|
||||||
|
"import": PopochiuConfig.is_default_animation_import_enabled(),
|
||||||
|
"loops": PopochiuConfig.is_default_animation_loop_enabled(),
|
||||||
|
"prop_visible": PopochiuConfig.is_default_animation_prop_visible(),
|
||||||
|
"prop_clickable": PopochiuConfig.is_default_animation_prop_clickable(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func _on_import_toggled(button_pressed):
|
||||||
|
_anim_tag_state.import = button_pressed
|
||||||
|
emit_signal("tag_state_changed")
|
||||||
|
|
||||||
|
|
||||||
|
func _on_loops_toggled(button_pressed):
|
||||||
|
_anim_tag_state.loops = button_pressed
|
||||||
|
emit_signal("tag_state_changed")
|
||||||
|
|
||||||
|
|
||||||
|
func _on_visible_toggled(button_pressed):
|
||||||
|
_anim_tag_state.prop_visible = button_pressed
|
||||||
|
emit_signal("tag_state_changed")
|
||||||
|
|
||||||
|
func _on_clickable_toggled(button_pressed):
|
||||||
|
_anim_tag_state.prop_clickable = button_pressed
|
||||||
|
emit_signal("tag_state_changed")
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://krf8u35pkjn3
|
|
@ -0,0 +1,94 @@
|
||||||
|
[gd_scene load_steps=6 format=3 uid="uid://rphyltbm12m4"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.gd" id="1"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_77wem"]
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_vdhps"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 16,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_c80ss"]
|
||||||
|
image = SubResource("Image_vdhps")
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_sd1l8"]
|
||||||
|
|
||||||
|
[node name="AnimationTagRow" type="HBoxContainer"]
|
||||||
|
offset_right = 320.0
|
||||||
|
offset_bottom = 20.0
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="TagName" type="Label" parent="HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Tag Name"
|
||||||
|
|
||||||
|
[node name="Panel" type="Panel" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxEmpty_77wem")
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="Panel"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 1
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
offset_left = -60.0
|
||||||
|
offset_bottom = 20.0
|
||||||
|
grow_horizontal = 0
|
||||||
|
|
||||||
|
[node name="Visible" type="Button" parent="Panel/HBoxContainer"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 3
|
||||||
|
tooltip_text = "This prop will be visible"
|
||||||
|
toggle_mode = true
|
||||||
|
icon = SubResource("ImageTexture_c80ss")
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[node name="Clickable" type="Button" parent="Panel/HBoxContainer"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 3
|
||||||
|
tooltip_text = "This prop will be clickable"
|
||||||
|
toggle_mode = true
|
||||||
|
icon = SubResource("ImageTexture_c80ss")
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[node name="Separator" type="Panel" parent="Panel/HBoxContainer"]
|
||||||
|
visible = false
|
||||||
|
custom_minimum_size = Vector2(1, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_sd1l8")
|
||||||
|
|
||||||
|
[node name="Import" type="Button" parent="Panel/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 3
|
||||||
|
tooltip_text = "Import this animation"
|
||||||
|
toggle_mode = true
|
||||||
|
icon = SubResource("ImageTexture_c80ss")
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[node name="Loops" type="Button" parent="Panel/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 3
|
||||||
|
tooltip_text = "Set animation as looping"
|
||||||
|
toggle_mode = true
|
||||||
|
icon = SubResource("ImageTexture_c80ss")
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[connection signal="toggled" from="Panel/HBoxContainer/Visible" to="." method="_on_visible_toggled"]
|
||||||
|
[connection signal="toggled" from="Panel/HBoxContainer/Clickable" to="." method="_on_clickable_toggled"]
|
||||||
|
[connection signal="toggled" from="Panel/HBoxContainer/Import" to="." method="_on_import_toggled"]
|
||||||
|
[connection signal="toggled" from="Panel/HBoxContainer/Loops" to="." method="_on_loops_toggled"]
|
|
@ -0,0 +1,429 @@
|
||||||
|
@tool
|
||||||
|
extends PanelContainer
|
||||||
|
|
||||||
|
# TODO: review coding standards for those constants
|
||||||
|
const RESULT_CODE = preload("res://addons/popochiu/editor/config/result_codes.gd")
|
||||||
|
const LOCAL_OBJ_CONFIG = preload("res://addons/popochiu/editor/config/local_obj_config.gd")
|
||||||
|
# TODO: this can be specialized, even if for a two buttons... ?
|
||||||
|
const AnimationTagRow =\
|
||||||
|
preload("res://addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.gd")
|
||||||
|
|
||||||
|
var scene: Node
|
||||||
|
var target_node: Node
|
||||||
|
var file_system: EditorFileSystem
|
||||||
|
|
||||||
|
# ---- External logic
|
||||||
|
var _animation_tag_row_scene: PackedScene =\
|
||||||
|
preload("res://addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.tscn")
|
||||||
|
var _aseprite = preload("../aseprite_controller.gd").new() ## TODO: should be absolute?
|
||||||
|
# ---- References for children scripts
|
||||||
|
var _root_node: Node
|
||||||
|
var _options: Dictionary
|
||||||
|
# ---- Importer parameters variables
|
||||||
|
var _source: String = ""
|
||||||
|
var _tags_cache: Array = []
|
||||||
|
var _file_dialog_aseprite: FileDialog
|
||||||
|
var _output_folder_dialog: FileDialog
|
||||||
|
var _importing := false
|
||||||
|
var _output_folder := ""
|
||||||
|
var _out_folder_default := "[Same as scene]"
|
||||||
|
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _ready():
|
||||||
|
_set_elements_styles()
|
||||||
|
|
||||||
|
if not PopochiuEditorConfig.aseprite_importer_enabled():
|
||||||
|
_show_info()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check access to Aseprite executable
|
||||||
|
var result = _check_aseprite()
|
||||||
|
if result == RESULT_CODE.SUCCESS:
|
||||||
|
_show_importer()
|
||||||
|
else:
|
||||||
|
PopochiuUtils.print_error(RESULT_CODE.get_error_message(result))
|
||||||
|
_show_warning()
|
||||||
|
|
||||||
|
# Load inspector dock configuration from node
|
||||||
|
var cfg = LOCAL_OBJ_CONFIG.load_config(target_node)
|
||||||
|
if cfg == null:
|
||||||
|
_load_default_config()
|
||||||
|
_set_options_visible(true)
|
||||||
|
else:
|
||||||
|
_load_config(cfg)
|
||||||
|
_set_tags_visible(cfg.get("tags_exp"))
|
||||||
|
_set_options_visible(cfg.get("op_exp"))
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _check_aseprite() -> int:
|
||||||
|
if not _aseprite.check_command_path():
|
||||||
|
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH
|
||||||
|
|
||||||
|
if not _aseprite.test_command():
|
||||||
|
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND
|
||||||
|
|
||||||
|
return RESULT_CODE.SUCCESS
|
||||||
|
|
||||||
|
|
||||||
|
func _list_tags(file: String):
|
||||||
|
if not _aseprite.check_command_path():
|
||||||
|
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH
|
||||||
|
if not _aseprite.test_command():
|
||||||
|
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND
|
||||||
|
return _aseprite.list_tags(file)
|
||||||
|
|
||||||
|
|
||||||
|
## TODO: Currently unused. keeping this as reference
|
||||||
|
## to populate a checkable list of layers
|
||||||
|
func _list_layers(file: String, only_visibles = false):
|
||||||
|
if not _aseprite.check_command_path():
|
||||||
|
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH
|
||||||
|
if not _aseprite.test_command():
|
||||||
|
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND
|
||||||
|
return _aseprite.list_layers(file, only_visibles)
|
||||||
|
|
||||||
|
|
||||||
|
func _load_config(cfg):
|
||||||
|
if cfg.has("source"):
|
||||||
|
_set_source(cfg.source)
|
||||||
|
|
||||||
|
_output_folder = cfg.get("o_folder", "")
|
||||||
|
get_node("%OutFolderButton").text = (
|
||||||
|
_output_folder if _output_folder != "" else _out_folder_default
|
||||||
|
)
|
||||||
|
get_node("%OutFileName").text = cfg.get("o_name", "")
|
||||||
|
get_node("%VisibleLayersCheckButton").set_pressed_no_signal(
|
||||||
|
cfg.get("only_visible_layers", false)
|
||||||
|
)
|
||||||
|
get_node("%WipeOldAnimationsCheckButton").set_pressed_no_signal(
|
||||||
|
cfg.get("wipe_old_anims", false)
|
||||||
|
)
|
||||||
|
|
||||||
|
_set_tags_visible(cfg.get("tags_exp", false))
|
||||||
|
_set_options_visible(cfg.get("op_exp", false))
|
||||||
|
_populate_tags(cfg.get("tags", []))
|
||||||
|
|
||||||
|
|
||||||
|
func _save_config():
|
||||||
|
_update_tags_cache()
|
||||||
|
|
||||||
|
var cfg := {
|
||||||
|
"source": _source,
|
||||||
|
"tags": _tags_cache,
|
||||||
|
"tags_exp": get_node("%Tags").visible,
|
||||||
|
"op_exp": get_node("%Options").visible,
|
||||||
|
"o_folder": _output_folder,
|
||||||
|
"o_name": get_node("%OutFileName").text,
|
||||||
|
"only_visible_layers": get_node("%VisibleLayersCheckButton").is_pressed(),
|
||||||
|
"wipe_old_anims": get_node("%WipeOldAnimationsCheckButton").is_pressed(),
|
||||||
|
}
|
||||||
|
|
||||||
|
LOCAL_OBJ_CONFIG.save_config(target_node, cfg)
|
||||||
|
|
||||||
|
|
||||||
|
func _load_default_config():
|
||||||
|
# Reset variables
|
||||||
|
_source = ""
|
||||||
|
_tags_cache = []
|
||||||
|
_output_folder = ""
|
||||||
|
|
||||||
|
# Empty tags list
|
||||||
|
_empty_tags_container()
|
||||||
|
|
||||||
|
# Reset inspector fields
|
||||||
|
get_node("%SourceButton").text = "[empty]"
|
||||||
|
get_node("%SourceButton").tooltip_text = ""
|
||||||
|
get_node("%OutFolderButton").text = "[empty]"
|
||||||
|
get_node("%OutFileName").clear()
|
||||||
|
get_node("%VisibleLayersCheckButton").set_pressed_no_signal(false)
|
||||||
|
get_node("%WipeOldAnimationsCheckButton").set_pressed_no_signal(
|
||||||
|
PopochiuConfig.is_default_wipe_old_anims_enabled()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _set_source(source):
|
||||||
|
_source = source
|
||||||
|
get_node("%SourceButton").text = _source
|
||||||
|
get_node("%SourceButton").tooltip_text = _source
|
||||||
|
|
||||||
|
|
||||||
|
func _on_source_pressed():
|
||||||
|
_open_source_dialog()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_aseprite_file_selected(path):
|
||||||
|
_set_source(ProjectSettings.localize_path(path))
|
||||||
|
_populate_tags(_get_tags_from_source())
|
||||||
|
_save_config()
|
||||||
|
_file_dialog_aseprite.queue_free()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_rescan_pressed():
|
||||||
|
_populate_tags(\
|
||||||
|
_merge_with_cache(_get_tags_from_source())\
|
||||||
|
)
|
||||||
|
_save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_import_pressed():
|
||||||
|
if _importing:
|
||||||
|
return
|
||||||
|
|
||||||
|
_importing = true
|
||||||
|
_root_node = get_tree().get_edited_scene_root()
|
||||||
|
|
||||||
|
if _source == "":
|
||||||
|
_show_message("Aseprite file not selected")
|
||||||
|
_importing = false
|
||||||
|
return
|
||||||
|
|
||||||
|
_options = {
|
||||||
|
"source": ProjectSettings.globalize_path(_source),
|
||||||
|
"tags": _tags_cache,
|
||||||
|
"output_folder": (
|
||||||
|
_output_folder if _output_folder != "" else _root_node.scene_file_path.get_base_dir()
|
||||||
|
),
|
||||||
|
"output_filename": get_node("%OutFileName").text,
|
||||||
|
"only_visible_layers": get_node("%VisibleLayersCheckButton").is_pressed(),
|
||||||
|
"wipe_old_animations": get_node("%WipeOldAnimationsCheckButton").is_pressed(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_reset_pressed():
|
||||||
|
var _confirmation_dialog = _show_confirmation(\
|
||||||
|
"This will reset the importer preferences." + \
|
||||||
|
"This cannot be undone! Are you sure?", "Confirmation required!")
|
||||||
|
_confirmation_dialog.get_ok_button().connect("pressed", Callable(self, "_reset_prefs_metadata"))
|
||||||
|
|
||||||
|
|
||||||
|
func _reset_prefs_metadata():
|
||||||
|
if target_node.has_meta(LOCAL_OBJ_CONFIG.LOCAL_OBJ_CONFIG_META_NAME):
|
||||||
|
target_node.remove_meta(LOCAL_OBJ_CONFIG.LOCAL_OBJ_CONFIG_META_NAME)
|
||||||
|
_load_default_config()
|
||||||
|
notify_property_list_changed()
|
||||||
|
|
||||||
|
|
||||||
|
func _open_source_dialog():
|
||||||
|
_file_dialog_aseprite = _create_aseprite_file_selection()
|
||||||
|
get_parent().add_child(_file_dialog_aseprite)
|
||||||
|
if _source != "":
|
||||||
|
_file_dialog_aseprite.set_current_dir(
|
||||||
|
ProjectSettings.globalize_path(
|
||||||
|
_source.get_base_dir()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_file_dialog_aseprite.popup_centered_ratio()
|
||||||
|
|
||||||
|
|
||||||
|
func _create_aseprite_file_selection():
|
||||||
|
var file_dialog = FileDialog.new()
|
||||||
|
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||||
|
file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||||
|
file_dialog.title = "Select Aseprite animation file"
|
||||||
|
file_dialog.connect("file_selected", Callable(self, "_on_aseprite_file_selected"))
|
||||||
|
file_dialog.set_filters(PackedStringArray(["*.ase","*.aseprite"]))
|
||||||
|
return file_dialog
|
||||||
|
|
||||||
|
|
||||||
|
func _populate_tags(tags: Array):
|
||||||
|
## reset tags container
|
||||||
|
_empty_tags_container()
|
||||||
|
|
||||||
|
# Add each tag found
|
||||||
|
for t in tags:
|
||||||
|
if t.tag_name == "":
|
||||||
|
continue
|
||||||
|
|
||||||
|
var tag_row: AnimationTagRow = _animation_tag_row_scene.instantiate()
|
||||||
|
get_node("%Tags").add_child(tag_row)
|
||||||
|
tag_row.init(t)
|
||||||
|
tag_row.connect("tag_state_changed", Callable(self, "_save_config"))
|
||||||
|
_customize_tag_ui(tag_row)
|
||||||
|
# Invoke customization hook implementable in child classes
|
||||||
|
_update_tags_cache()
|
||||||
|
|
||||||
|
|
||||||
|
func _customize_tag_ui(tagrow: AnimationTagRow):
|
||||||
|
## This can be implemented by child classes if necessary
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func _empty_tags_container():
|
||||||
|
# Clean the inspector tags container empty
|
||||||
|
for tl in get_node("%Tags").get_children():
|
||||||
|
get_node("%Tags").remove_child(tl)
|
||||||
|
tl.queue_free()
|
||||||
|
|
||||||
|
|
||||||
|
func _update_tags_cache():
|
||||||
|
_tags_cache = _get_tags_from_ui()
|
||||||
|
|
||||||
|
|
||||||
|
func _merge_with_cache(tags: Array) -> Array:
|
||||||
|
var tags_cache_index = {}
|
||||||
|
var result = []
|
||||||
|
for t in _tags_cache:
|
||||||
|
tags_cache_index[t.tag_name] = t
|
||||||
|
|
||||||
|
for i in tags.size():
|
||||||
|
result.push_back(
|
||||||
|
tags_cache_index[tags[i].tag_name]
|
||||||
|
if tags_cache_index.has(tags[i].tag_name)
|
||||||
|
else tags[i]
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
func _get_tags_from_ui() -> Array:
|
||||||
|
var tags_list = []
|
||||||
|
for tag_row in get_node("%Tags").get_children():
|
||||||
|
var tag_row_cfg = tag_row.get_cfg()
|
||||||
|
if tag_row_cfg.tag_name == "":
|
||||||
|
continue
|
||||||
|
tags_list.push_back(tag_row_cfg)
|
||||||
|
return tags_list
|
||||||
|
|
||||||
|
|
||||||
|
func _get_tags_from_source() -> Array:
|
||||||
|
var tags_found = _list_tags(ProjectSettings.globalize_path(_source))
|
||||||
|
if typeof(tags_found) == TYPE_INT:
|
||||||
|
PopochiuUtils.print_error(RESULT_CODE.get_error_message(tags_found))
|
||||||
|
return []
|
||||||
|
var tags_list = []
|
||||||
|
for t in tags_found:
|
||||||
|
if t == "":
|
||||||
|
continue
|
||||||
|
tags_list.push_back({
|
||||||
|
tag_name = t
|
||||||
|
})
|
||||||
|
return tags_list
|
||||||
|
|
||||||
|
|
||||||
|
func _show_message(
|
||||||
|
message: String, title: String = "", object: Object = null, method := ""
|
||||||
|
):
|
||||||
|
var warning_dialog = AcceptDialog.new()
|
||||||
|
|
||||||
|
if title != "":
|
||||||
|
warning_dialog.title = title
|
||||||
|
|
||||||
|
warning_dialog.dialog_text = message
|
||||||
|
warning_dialog.popup_window = true
|
||||||
|
|
||||||
|
var callback := Callable(warning_dialog, "queue_free")
|
||||||
|
|
||||||
|
if is_instance_valid(object) and not method.is_empty():
|
||||||
|
callback = func():
|
||||||
|
object.call(method)
|
||||||
|
|
||||||
|
warning_dialog.confirmed.connect(callback)
|
||||||
|
warning_dialog.close_requested.connect(callback)
|
||||||
|
|
||||||
|
PopochiuEditorHelper.show_dialog(warning_dialog)
|
||||||
|
|
||||||
|
|
||||||
|
func _show_confirmation(message: String, title: String = ""):
|
||||||
|
var _confirmation_dialog = ConfirmationDialog.new()
|
||||||
|
get_parent().add_child(_confirmation_dialog)
|
||||||
|
if title != "":
|
||||||
|
_confirmation_dialog.title = title
|
||||||
|
_confirmation_dialog.dialog_text = message
|
||||||
|
_confirmation_dialog.popup_centered()
|
||||||
|
_confirmation_dialog.connect("close_requested", Callable(_confirmation_dialog, "queue_free"))
|
||||||
|
return _confirmation_dialog
|
||||||
|
|
||||||
|
|
||||||
|
func _on_options_title_toggled(button_pressed):
|
||||||
|
_set_options_visible(button_pressed)
|
||||||
|
_save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func _set_options_visible(is_visible):
|
||||||
|
get_node("%Options").visible = is_visible
|
||||||
|
get_node("%OptionsTitle").icon = (
|
||||||
|
PopochiuEditorConfig.get_icon(PopochiuEditorConfig.Icons.EXPANDED) if is_visible
|
||||||
|
else PopochiuEditorConfig.get_icon(PopochiuEditorConfig.Icons.COLLAPSED)
|
||||||
|
)
|
||||||
|
|
||||||
|
func _on_tags_title_toggled(button_pressed: bool) -> void:
|
||||||
|
_set_tags_visible(button_pressed)
|
||||||
|
_save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func _set_tags_visible(is_visible: bool) -> void:
|
||||||
|
get_node("%Tags").visible = is_visible
|
||||||
|
get_node("%TagsTitle").icon = (
|
||||||
|
PopochiuEditorConfig.get_icon(PopochiuEditorConfig.Icons.EXPANDED) if is_visible
|
||||||
|
else PopochiuEditorConfig.get_icon(PopochiuEditorConfig.Icons.COLLAPSED)
|
||||||
|
)
|
||||||
|
|
||||||
|
func _on_out_folder_pressed():
|
||||||
|
_output_folder_dialog = _create_output_folder_selection()
|
||||||
|
get_parent().add_child(_output_folder_dialog)
|
||||||
|
if _output_folder != _out_folder_default:
|
||||||
|
_output_folder_dialog.current_dir = _output_folder
|
||||||
|
_output_folder_dialog.popup_centered_ratio()
|
||||||
|
|
||||||
|
|
||||||
|
func _create_output_folder_selection():
|
||||||
|
var file_dialog = FileDialog.new()
|
||||||
|
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_DIR
|
||||||
|
file_dialog.access = FileDialog.ACCESS_RESOURCES
|
||||||
|
file_dialog.title = "Select destination folder"
|
||||||
|
file_dialog.connect("dir_selected", Callable(self, "_on_output_folder_selected"))
|
||||||
|
return file_dialog
|
||||||
|
|
||||||
|
|
||||||
|
func _on_output_folder_selected(path):
|
||||||
|
_output_folder = path
|
||||||
|
get_node("%OutFolderButton").text = (
|
||||||
|
_output_folder if _output_folder != "" else _out_folder_default
|
||||||
|
)
|
||||||
|
_output_folder_dialog.queue_free()
|
||||||
|
_save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func _set_elements_styles():
|
||||||
|
# Set sections title colors according to current theme
|
||||||
|
var section_color = get_theme_color("prop_section", "Editor")
|
||||||
|
var section_style = StyleBoxFlat.new()
|
||||||
|
section_style.set_bg_color(section_color)
|
||||||
|
get_node("%TagsTitleBar").set("theme_override_styles/panel", section_style)
|
||||||
|
get_node("%OptionsTitleBar").set("theme_override_styles/panel", section_style)
|
||||||
|
|
||||||
|
# Set style of warning panel
|
||||||
|
get_node("%WarningPanel").add_theme_stylebox_override(
|
||||||
|
"panel",
|
||||||
|
get_node("%WarningPanel").get_theme_stylebox("sub_inspector_bg11", "Editor")
|
||||||
|
)
|
||||||
|
get_node("%WarningLabel").add_theme_color_override("font_color", Color("c46c71"))
|
||||||
|
|
||||||
|
|
||||||
|
func _show_info():
|
||||||
|
get_node("%Info").visible = true
|
||||||
|
get_node("%Warning").visible = false
|
||||||
|
get_node("%Importer").visible = false
|
||||||
|
|
||||||
|
|
||||||
|
func _show_warning():
|
||||||
|
get_node("%Info").visible = false
|
||||||
|
get_node("%Warning").visible = true
|
||||||
|
get_node("%Importer").visible = false
|
||||||
|
|
||||||
|
|
||||||
|
func _show_importer():
|
||||||
|
get_node("%Info").visible = false
|
||||||
|
get_node("%Warning").visible = false
|
||||||
|
get_node("%Importer").visible = true
|
||||||
|
|
||||||
|
# TODO: Introduce layer selection list, more or less as tags
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://c5o55inhq2abl
|
|
@ -0,0 +1,225 @@
|
||||||
|
[gd_scene load_steps=4 format=3 uid="uid://bcanby6n3eahm"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxEmpty" id="1"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wwoxk"]
|
||||||
|
bg_color = Color(0, 0, 0, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ctsm1"]
|
||||||
|
content_margin_left = 4.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 4.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
bg_color = Color(1, 0.364706, 0.364706, 1)
|
||||||
|
draw_center = false
|
||||||
|
corner_detail = 1
|
||||||
|
|
||||||
|
[node name="AsepriteImporterInspectorDock" type="PanelContainer"]
|
||||||
|
offset_right = 14.0
|
||||||
|
offset_bottom = 14.0
|
||||||
|
theme_override_styles/panel = SubResource("1")
|
||||||
|
|
||||||
|
[node name="Margin" type="MarginContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/margin_top = 2
|
||||||
|
theme_override_constants/margin_bottom = 2
|
||||||
|
|
||||||
|
[node name="Importer" type="VBoxContainer" parent="Margin"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Source" type="HBoxContainer" parent="Margin/Importer"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Location of the Aseprite (*.ase, *.aseprite) source file."
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Margin/Importer/Source"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_stretch_ratio = 2.0
|
||||||
|
text = "Aseprite File"
|
||||||
|
|
||||||
|
[node name="SourceButton" type="Button" parent="Margin/Importer/Source"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_stretch_ratio = 2.0
|
||||||
|
text = "[empty]"
|
||||||
|
clip_text = true
|
||||||
|
|
||||||
|
[node name="RescanButton" type="Button" parent="Margin/Importer/Source"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Rescan"
|
||||||
|
|
||||||
|
[node name="TagsTitleBar" type="PanelContainer" parent="Margin/Importer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_wwoxk")
|
||||||
|
|
||||||
|
[node name="TagsTitle" type="Button" parent="Margin/Importer/TagsTitleBar"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_pressed_color = Color(0.8, 0.807843, 0.827451, 1)
|
||||||
|
toggle_mode = true
|
||||||
|
text = "Animation tags"
|
||||||
|
|
||||||
|
[node name="Tags" type="VBoxContainer" parent="Margin/Importer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="OptionsTitleBar" type="PanelContainer" parent="Margin/Importer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_wwoxk")
|
||||||
|
|
||||||
|
[node name="OptionsTitle" type="Button" parent="Margin/Importer/OptionsTitleBar"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_pressed_color = Color(0.8, 0.807843, 0.827451, 1)
|
||||||
|
toggle_mode = true
|
||||||
|
text = "Options"
|
||||||
|
|
||||||
|
[node name="Options" type="VBoxContainer" parent="Margin/Importer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="OutFolder" type="HBoxContainer" parent="Margin/Importer/Options"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Location where the spritesheet file should be saved."
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Margin/Importer/Options/OutFolder"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_stretch_ratio = 2.0
|
||||||
|
text = "Output folder"
|
||||||
|
|
||||||
|
[node name="OutFolderButton" type="Button" parent="Margin/Importer/Options/OutFolder"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_stretch_ratio = 2.0
|
||||||
|
text = "[empty]"
|
||||||
|
clip_text = true
|
||||||
|
|
||||||
|
[node name="OutFile" type="HBoxContainer" parent="Margin/Importer/Options"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Base filename for spritesheet. In case the layer option is used, this works as a prefix to the layer name."
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Margin/Importer/Options/OutFile"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_stretch_ratio = 2.0
|
||||||
|
text = "Output file name"
|
||||||
|
|
||||||
|
[node name="OutFileName" type="LineEdit" parent="Margin/Importer/Options/OutFile"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_stretch_ratio = 2.0
|
||||||
|
|
||||||
|
[node name="VisibleLayers" type="HBoxContainer" parent="Margin/Importer/Options"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "If active, layers not visible in the source file won't be included in the final image."
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Margin/Importer/Options/VisibleLayers"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_stretch_ratio = 2.0
|
||||||
|
text = "Only visible layers"
|
||||||
|
|
||||||
|
[node name="VisibleLayersCheckButton" type="CheckButton" parent="Margin/Importer/Options/VisibleLayers"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_stretch_ratio = 2.0
|
||||||
|
|
||||||
|
[node name="WipeOldAnimations" type="HBoxContainer" parent="Margin/Importer/Options"]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "If active, layers not visible in the source file won't be included in the final image."
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Margin/Importer/Options/WipeOldAnimations"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_stretch_ratio = 2.0
|
||||||
|
tooltip_text = "Set this to OFF if you want to add new animations on top of old ones. Anims with same name will be updated."
|
||||||
|
mouse_filter = 0
|
||||||
|
text = "Wipe old animations"
|
||||||
|
|
||||||
|
[node name="WipeOldAnimationsCheckButton" type="CheckButton" parent="Margin/Importer/Options/WipeOldAnimations"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_stretch_ratio = 2.0
|
||||||
|
|
||||||
|
[node name="Import" type="Button" parent="Margin/Importer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Import"
|
||||||
|
|
||||||
|
[node name="Reset" type="Button" parent="Margin/Importer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Reset Preferences"
|
||||||
|
|
||||||
|
[node name="Warning" type="VBoxContainer" parent="Margin"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="Margin/Warning"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="WarningPanel" type="Panel" parent="Margin/Warning/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(222, 50)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_ctsm1")
|
||||||
|
|
||||||
|
[node name="WarningLabel" type="Label" parent="Margin/Warning/HBoxContainer/WarningPanel"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(0, 42)
|
||||||
|
layout_mode = 0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 6
|
||||||
|
theme_override_colors/font_color = Color(0.768627, 0.423529, 0.443137, 1)
|
||||||
|
text = "Error loading Aseprite Importer!
|
||||||
|
Check Output panel for details."
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Info" type="VBoxContainer" parent="Margin"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="Margin/Info"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="InfoPanel" type="Panel" parent="Margin/Info/HBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(222, 50)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_ctsm1")
|
||||||
|
|
||||||
|
[node name="InfoLabel" type="Label" parent="Margin/Info/HBoxContainer/InfoPanel"]
|
||||||
|
custom_minimum_size = Vector2(0, 42)
|
||||||
|
layout_mode = 0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 6
|
||||||
|
text = "Aseprite Importer disabled.
|
||||||
|
Can be enabled in Editor Settings."
|
||||||
|
|
||||||
|
[connection signal="pressed" from="Margin/Importer/Source/SourceButton" to="." method="_on_source_pressed"]
|
||||||
|
[connection signal="pressed" from="Margin/Importer/Source/RescanButton" to="." method="_on_rescan_pressed"]
|
||||||
|
[connection signal="toggled" from="Margin/Importer/TagsTitleBar/TagsTitle" to="." method="_on_tags_title_toggled"]
|
||||||
|
[connection signal="toggled" from="Margin/Importer/OptionsTitleBar/OptionsTitle" to="." method="_on_options_title_toggled"]
|
||||||
|
[connection signal="pressed" from="Margin/Importer/Options/OutFolder/OutFolderButton" to="." method="_on_out_folder_pressed"]
|
||||||
|
[connection signal="focus_exited" from="Margin/Importer/Options/OutFile/OutFileName" to="." method="_save_config"]
|
||||||
|
[connection signal="pressed" from="Margin/Importer/Options/VisibleLayers/VisibleLayersCheckButton" to="." method="_save_config"]
|
||||||
|
[connection signal="pressed" from="Margin/Importer/Options/WipeOldAnimations/WipeOldAnimationsCheckButton" to="." method="_save_config"]
|
||||||
|
[connection signal="pressed" from="Margin/Importer/Import" to="." method="_on_import_pressed"]
|
||||||
|
[connection signal="pressed" from="Margin/Importer/Reset" to="." method="_on_reset_pressed"]
|
|
@ -0,0 +1,55 @@
|
||||||
|
@tool
|
||||||
|
extends "res://addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.gd"
|
||||||
|
|
||||||
|
var _animation_player_path: String
|
||||||
|
var _animation_creator = preload(
|
||||||
|
"res://addons/popochiu/editor/importers/aseprite/animation_creator.gd"
|
||||||
|
).new()
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _ready():
|
||||||
|
if not target_node.has_node("AnimationPlayer"):
|
||||||
|
PopochiuUtils.print_error(
|
||||||
|
RESULT_CODE.get_error_message(RESULT_CODE.ERR_NO_ANIMATION_PLAYER_FOUND)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
_animation_player_path = target_node.get_node("AnimationPlayer").get_path()
|
||||||
|
|
||||||
|
# Instantiate animation creator
|
||||||
|
_animation_creator.init(_aseprite, file_system)
|
||||||
|
|
||||||
|
super()
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _on_import_pressed():
|
||||||
|
# Set everything up
|
||||||
|
# This will populate _root_node and _options class variables
|
||||||
|
super()
|
||||||
|
|
||||||
|
if _animation_player_path == "" or not _root_node.has_node(_animation_player_path):
|
||||||
|
_show_message("AnimationPlayer not found")
|
||||||
|
_importing = false
|
||||||
|
return
|
||||||
|
|
||||||
|
var result = await _animation_creator.create_character_animations(
|
||||||
|
target_node, _root_node.get_node(_animation_player_path), _options
|
||||||
|
)
|
||||||
|
_importing = false
|
||||||
|
|
||||||
|
if typeof(result) == TYPE_INT and result != RESULT_CODE.SUCCESS:
|
||||||
|
PopochiuUtils.print_error(RESULT_CODE.get_error_message(result))
|
||||||
|
_show_message("Some errors occurred. Please check output panel.", "Warning!")
|
||||||
|
else:
|
||||||
|
_show_message("%d animation tags processed." % [_tags_cache.size()], "Done!")
|
||||||
|
|
||||||
|
|
||||||
|
func _customize_tag_ui(tag_row: AnimationTagRow):
|
||||||
|
# Nothing special has to be done for Character tags
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://cgc5psgxjoc17
|
|
@ -0,0 +1,117 @@
|
||||||
|
@tool
|
||||||
|
extends "res://addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.gd"
|
||||||
|
|
||||||
|
var _animation_creator = preload(\
|
||||||
|
"res://addons/popochiu/editor/importers/aseprite/animation_creator.gd").new()
|
||||||
|
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _ready():
|
||||||
|
# Instantiate animation creator
|
||||||
|
_animation_creator.init(_aseprite, file_system)
|
||||||
|
|
||||||
|
super()
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _on_import_pressed():
|
||||||
|
# Set everything up
|
||||||
|
# This will populate _root_node and _options class variables
|
||||||
|
super()
|
||||||
|
|
||||||
|
var props_container = _root_node.get_node("Props")
|
||||||
|
var result: int = RESULT_CODE.SUCCESS
|
||||||
|
|
||||||
|
# Create a prop for each tag that must be imported
|
||||||
|
# and populate it with the right sprite
|
||||||
|
for tag in _options.get("tags"):
|
||||||
|
# Ignore unwanted tags
|
||||||
|
if not tag.import: continue
|
||||||
|
|
||||||
|
# Always convert to PascalCase as a standard
|
||||||
|
# TODO: check Godot 4 standards, I can't find info
|
||||||
|
var prop_name: String = tag.tag_name.to_pascal_case()
|
||||||
|
|
||||||
|
# In case the prop is there, use the one we already have
|
||||||
|
var prop = props_container.get_node_or_null(prop_name)
|
||||||
|
if prop == null:
|
||||||
|
# Create a new prop if necessary, specifying the
|
||||||
|
# interaction flags.
|
||||||
|
prop = _create_prop(prop_name, tag.prop_clickable, tag.prop_visible)
|
||||||
|
else:
|
||||||
|
# Force flags (a bit redundant but they may have been changed
|
||||||
|
# in the Importer interface, for already imported props)
|
||||||
|
prop.clickable = tag.prop_clickable
|
||||||
|
prop.visible = tag.prop_visible
|
||||||
|
|
||||||
|
prop.set_meta("ANIM_NAME", tag.tag_name)
|
||||||
|
|
||||||
|
for prop in props_container.get_children():
|
||||||
|
if not prop.has_meta("ANIM_NAME"): continue
|
||||||
|
# TODO: check if animation player exists in prop, if not add it
|
||||||
|
# same for Sprite2D even if it should be there...
|
||||||
|
|
||||||
|
# Make the output folder match the prop's folder
|
||||||
|
_options.output_folder = prop.scene_file_path.get_base_dir()
|
||||||
|
|
||||||
|
# Import a single tag animation
|
||||||
|
result = await _animation_creator.create_prop_animations(
|
||||||
|
prop,
|
||||||
|
prop.get_meta("ANIM_NAME"),
|
||||||
|
_options
|
||||||
|
)
|
||||||
|
|
||||||
|
for prop in props_container.get_children():
|
||||||
|
if not prop.has_meta("ANIM_NAME"): continue
|
||||||
|
# Save the prop
|
||||||
|
result = await _save_prop(prop)
|
||||||
|
|
||||||
|
# TODO: maybe check if this is better done with signals
|
||||||
|
_importing = false
|
||||||
|
|
||||||
|
if typeof(result) == TYPE_INT and result != RESULT_CODE.SUCCESS:
|
||||||
|
PopochiuUtils.print_error(RESULT_CODE.get_error_message(result))
|
||||||
|
_show_message("Some errors occurred. Please check output panel.", "Warning!")
|
||||||
|
else:
|
||||||
|
await get_tree().create_timer(0.1).timeout
|
||||||
|
|
||||||
|
# Once the popup is closed, call _clean_props()
|
||||||
|
_show_message(
|
||||||
|
"%d animation tags processed." % [_tags_cache.size()],
|
||||||
|
"Done!"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _customize_tag_ui(tag_row: AnimationTagRow):
|
||||||
|
# Show props-related buttons if we are in a room
|
||||||
|
tag_row.show_prop_buttons()
|
||||||
|
|
||||||
|
|
||||||
|
func _create_prop(name: String, is_clickable: bool = true, is_visible: bool = true):
|
||||||
|
var factory = PopochiuPropFactory.new()
|
||||||
|
var param := PopochiuPropFactory.PopochiuPropFactoryParam.new()
|
||||||
|
param.obj_name = name
|
||||||
|
param.room = _root_node
|
||||||
|
param.is_interactive = is_clickable
|
||||||
|
param.is_visible = is_visible
|
||||||
|
|
||||||
|
if factory.create(param) != ResultCodes.SUCCESS:
|
||||||
|
return
|
||||||
|
|
||||||
|
return factory.get_obj_scene()
|
||||||
|
|
||||||
|
func _save_prop(prop: PopochiuProp):
|
||||||
|
var packed_scene: PackedScene = PackedScene.new()
|
||||||
|
packed_scene.pack(prop)
|
||||||
|
if ResourceSaver.save(packed_scene, prop.scene_file_path) != OK:
|
||||||
|
PopochiuUtils.print_error(
|
||||||
|
"Couldn't save animations for prop %s at %s" %
|
||||||
|
[prop.name, prop.scene_file_path]
|
||||||
|
)
|
||||||
|
return ResultCodes.ERR_CANT_SAVE_OBJ_SCENE
|
||||||
|
return ResultCodes.SUCCESS
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://cv4mdldfurgpc
|
|
@ -0,0 +1,46 @@
|
||||||
|
extends EditorInspectorPlugin ## TODO: create a base class with pointer variables
|
||||||
|
|
||||||
|
const DOCKS_PATH := "res://addons/popochiu/editor/importers/aseprite/docks/"
|
||||||
|
const INSPECTOR_DOCK = preload(DOCKS_PATH + "aseprite_importer_inspector_dock.tscn")
|
||||||
|
const CONFIG_SCRIPT = preload("res://addons/popochiu/editor/config/config.gd")
|
||||||
|
const INSPECTOR_DOCK_CHARACTER := DOCKS_PATH + "aseprite_importer_inspector_dock_character.gd"
|
||||||
|
const INSPECTOR_DOCK_ROOM := DOCKS_PATH + "aseprite_importer_inspector_dock_room.gd"
|
||||||
|
|
||||||
|
var _target_node: Node
|
||||||
|
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _can_handle(object):
|
||||||
|
if object.has_method("get_parent") and object.get_parent() is Node2D:
|
||||||
|
return false
|
||||||
|
|
||||||
|
return object is PopochiuCharacter || object is PopochiuRoom #|| object is PopochiuInventoryItem
|
||||||
|
|
||||||
|
|
||||||
|
func _parse_begin(object: Object):
|
||||||
|
# Fix showing error messages in Output when inspecting nodes in the Debugger
|
||||||
|
if not object is Node: return
|
||||||
|
|
||||||
|
_target_node = object
|
||||||
|
|
||||||
|
|
||||||
|
func _parse_property(object, type, name, hint_type, hint_string, usage_flags, wide) -> bool:
|
||||||
|
if object.get_class() == "EditorDebuggerRemoteObject":
|
||||||
|
return false
|
||||||
|
if name != 'popochiu_placeholder':
|
||||||
|
return false
|
||||||
|
# Instantiate and configure the dock
|
||||||
|
var dock = INSPECTOR_DOCK.instantiate()
|
||||||
|
# Load the specific script in the dock
|
||||||
|
if object is PopochiuCharacter:
|
||||||
|
dock.set_script(load(INSPECTOR_DOCK_CHARACTER))
|
||||||
|
if object is PopochiuRoom:
|
||||||
|
dock.set_script(load(INSPECTOR_DOCK_ROOM))
|
||||||
|
dock.target_node = object
|
||||||
|
dock.file_system = EditorInterface.get_resource_filesystem()
|
||||||
|
# Add the dock to the inspector
|
||||||
|
add_custom_control(dock)
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dpianc4xo6kcl
|
|
@ -0,0 +1,52 @@
|
||||||
|
extends EditorInspectorPlugin
|
||||||
|
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _can_handle(object: Object) -> bool:
|
||||||
|
return object is PopochiuAudioCue
|
||||||
|
|
||||||
|
|
||||||
|
func _parse_property(
|
||||||
|
object: Object,
|
||||||
|
type,
|
||||||
|
path: String,
|
||||||
|
hint,
|
||||||
|
hint_text: String,
|
||||||
|
usage,
|
||||||
|
wide: bool
|
||||||
|
) -> bool:
|
||||||
|
if not object is PopochiuAudioCue or path != "bus":
|
||||||
|
return false
|
||||||
|
|
||||||
|
var ep := EditorProperty.new()
|
||||||
|
var ob := OptionButton.new()
|
||||||
|
|
||||||
|
_update_buses_list(ob, object)
|
||||||
|
|
||||||
|
ob.item_selected.connect(_update_audio_cue_bus.bind(object))
|
||||||
|
ob.pressed.connect(_update_buses_list.bind(ob, object))
|
||||||
|
|
||||||
|
ep.add_child(ob)
|
||||||
|
add_property_editor(path, ep)
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _update_audio_cue_bus(idx: int, audio_cue: PopochiuAudioCue) -> void:
|
||||||
|
audio_cue.bus = AudioServer.get_bus_name(idx)
|
||||||
|
ResourceSaver.save(audio_cue, audio_cue.resource_path)
|
||||||
|
|
||||||
|
|
||||||
|
func _update_buses_list(ob: OptionButton, pac: PopochiuAudioCue) -> void:
|
||||||
|
ob.clear()
|
||||||
|
|
||||||
|
for idx in AudioServer.bus_count:
|
||||||
|
ob.add_item(AudioServer.get_bus_name(idx), idx)
|
||||||
|
|
||||||
|
ob.selected = AudioServer.get_bus_index(pac.bus)
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://d0emjgqvommw4
|
|
@ -0,0 +1,83 @@
|
||||||
|
extends EditorInspectorPlugin
|
||||||
|
|
||||||
|
|
||||||
|
#region Virtual ####################################################################################
|
||||||
|
func _can_handle(object: Object) -> bool:
|
||||||
|
if object is PopochiuCharacter:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func _parse_begin(object: Object) -> void:
|
||||||
|
if object.get_class() == "EditorDebuggerRemoteObject":
|
||||||
|
return
|
||||||
|
|
||||||
|
if not object.get_parent() is Node2D: return
|
||||||
|
|
||||||
|
var panel := PanelContainer.new()
|
||||||
|
var hbox := HBoxContainer.new()
|
||||||
|
var button := Button.new()
|
||||||
|
|
||||||
|
hbox.custom_minimum_size.y = 42.0
|
||||||
|
button.text = "* Open Node' scene to edit its properties"
|
||||||
|
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
button.alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||||
|
|
||||||
|
panel.add_theme_stylebox_override(
|
||||||
|
"panel",
|
||||||
|
panel.get_theme_stylebox("sub_inspector_bg11", "Editor")
|
||||||
|
)
|
||||||
|
button.add_theme_color_override("font_color", Color("c46c71"))
|
||||||
|
button.add_theme_color_override("font_color_hover", Color("c46c71"))
|
||||||
|
button.add_theme_color_override("font_color_pressed", Color("c46c71"))
|
||||||
|
|
||||||
|
button.pressed.connect(
|
||||||
|
_open_scene.bind((object as PopochiuCharacter).scene_file_path),
|
||||||
|
CONNECT_DEFERRED
|
||||||
|
)
|
||||||
|
|
||||||
|
hbox.add_child(button)
|
||||||
|
panel.add_child(hbox)
|
||||||
|
|
||||||
|
add_custom_control(panel)
|
||||||
|
|
||||||
|
|
||||||
|
func _parse_property(
|
||||||
|
object: Object,
|
||||||
|
type,
|
||||||
|
path: String,
|
||||||
|
hint,
|
||||||
|
hint_text: String,
|
||||||
|
usage,
|
||||||
|
wide: bool
|
||||||
|
) -> bool:
|
||||||
|
if object.get_class() == "EditorDebuggerRemoteObject":
|
||||||
|
return false
|
||||||
|
|
||||||
|
# NOTE: We could add this as an option of the plugin settings. So devs can add extra properties
|
||||||
|
# if needed.
|
||||||
|
if object and object.get_parent() is Node2D and not path in [
|
||||||
|
"baseline",
|
||||||
|
"walk_to_point",
|
||||||
|
"look_at_point",
|
||||||
|
"position",
|
||||||
|
"visible",
|
||||||
|
"modulate",
|
||||||
|
"self_modulate",
|
||||||
|
"light_mask",
|
||||||
|
]:
|
||||||
|
return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _open_scene(path: String) -> void:
|
||||||
|
EditorInterface.set_main_screen_editor("2D")
|
||||||
|
EditorInterface.open_scene_from_path(path)
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://m7hkqdx4jhem
|
63
addons/popochiu/editor/inspector/prop_inspector_plugin.gd
Normal file
63
addons/popochiu/editor/inspector/prop_inspector_plugin.gd
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
extends EditorInspectorPlugin
|
||||||
|
|
||||||
|
|
||||||
|
#region Virtual ####################################################################################
|
||||||
|
func _can_handle(object: Object) -> bool:
|
||||||
|
if object is PopochiuProp:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func _parse_property(
|
||||||
|
object: Object,
|
||||||
|
type,
|
||||||
|
path: String,
|
||||||
|
hint,
|
||||||
|
hint_text: String,
|
||||||
|
usage,
|
||||||
|
wide: bool
|
||||||
|
) -> bool:
|
||||||
|
if (
|
||||||
|
object.get_class() == "EditorDebuggerRemoteObject"
|
||||||
|
or object is not PopochiuProp
|
||||||
|
or path != "link_to_item"
|
||||||
|
):
|
||||||
|
return false
|
||||||
|
|
||||||
|
var ep := EditorProperty.new()
|
||||||
|
var ob := OptionButton.new()
|
||||||
|
|
||||||
|
_update_items_list(ob, object)
|
||||||
|
|
||||||
|
ob.item_selected.connect(_update_link_to_item.bind(ob, object))
|
||||||
|
ob.pressed.connect(_update_items_list.bind(ob, object))
|
||||||
|
|
||||||
|
ep.add_child(ob)
|
||||||
|
add_property_editor(path, ep)
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _update_items_list(ob: OptionButton, prop: PopochiuProp) -> void:
|
||||||
|
ob.clear()
|
||||||
|
var inventory_items := PopochiuResources.get_section_keys("inventory_items")
|
||||||
|
var keys_ids_map := {}
|
||||||
|
|
||||||
|
inventory_items.sort()
|
||||||
|
ob.add_item("")
|
||||||
|
for key: String in inventory_items:
|
||||||
|
keys_ids_map[key] = ob.item_count
|
||||||
|
ob.add_item(key)
|
||||||
|
|
||||||
|
if keys_ids_map.has(prop.link_to_item):
|
||||||
|
ob.selected = ob.get_item_index(keys_ids_map[prop.link_to_item])
|
||||||
|
|
||||||
|
|
||||||
|
func _update_link_to_item(idx: int, ob: OptionButton, prop: PopochiuProp) -> void:
|
||||||
|
prop.link_to_item = ob.get_item_text(idx)
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dsuhln67g2d8k
|
117
addons/popochiu/editor/main_dock/popochiu_dock.gd
Normal file
117
addons/popochiu/editor/main_dock/popochiu_dock.gd
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
@tool
|
||||||
|
extends Panel
|
||||||
|
|
||||||
|
signal move_folders_pressed
|
||||||
|
|
||||||
|
@onready var tab_container: TabContainer = %TabContainer
|
||||||
|
@onready var tab_main: VBoxContainer = %Main
|
||||||
|
@onready var tab_room: VBoxContainer = %Room
|
||||||
|
@onready var tab_audio: VBoxContainer = %Audio
|
||||||
|
@onready var tab_gui: VBoxContainer = %GUI
|
||||||
|
# ---- FOOTER --------------------------------------------------------------------------------------
|
||||||
|
@onready var version: Label = %Version
|
||||||
|
@onready var btn_setup: Button = %BtnSetup
|
||||||
|
@onready var btn_docs: Button = %BtnDocs
|
||||||
|
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _ready() -> void:
|
||||||
|
version.text = "v" + PopochiuResources.get_version()
|
||||||
|
btn_setup.icon = get_theme_icon("Edit", "EditorIcons")
|
||||||
|
btn_docs.icon = get_theme_icon("HelpSearch", "EditorIcons")
|
||||||
|
|
||||||
|
# Set the Main tab selected by default
|
||||||
|
tab_container.current_tab = 0
|
||||||
|
|
||||||
|
# Hide the GUI tab while we decide how it will work based on devs feedback
|
||||||
|
tab_container.set_tab_hidden(tab_gui.get_index(), true)
|
||||||
|
|
||||||
|
# Connect to children's signals
|
||||||
|
tab_container.tab_changed.connect(_on_tab_changed)
|
||||||
|
btn_setup.pressed.connect(open_setup)
|
||||||
|
btn_docs.pressed.connect(OS.shell_open.bind(PopochiuResources.DOCUMENTATION))
|
||||||
|
|
||||||
|
# Connect to parent signals
|
||||||
|
get_tree().node_added.connect(_check_node)
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
func fill_data() -> void:
|
||||||
|
tab_main.fill_data()
|
||||||
|
tab_audio.fill_data()
|
||||||
|
|
||||||
|
|
||||||
|
func scene_changed(scene_root: Node) -> void:
|
||||||
|
if not is_instance_valid(tab_room): return
|
||||||
|
tab_room.scene_changed(scene_root)
|
||||||
|
|
||||||
|
# TODO: Uncomment these lines when working on the GUI tab again
|
||||||
|
#if not is_instance_valid(tab_gui): return
|
||||||
|
#tab_gui.on_scene_changed(scene_root)
|
||||||
|
|
||||||
|
if (
|
||||||
|
not scene_root
|
||||||
|
or (
|
||||||
|
not scene_root is PopochiuRoom
|
||||||
|
# TODO: Uncomment this line when working on the GUI tab again
|
||||||
|
#and not scene_root.scene_file_path == PopochiuResources.GUI_GAME_SCENE
|
||||||
|
)
|
||||||
|
):
|
||||||
|
# Open the Popochiu Main tab if the opened scene in the Editor2D is not a PopochiuRoom nor
|
||||||
|
# the GUI scene
|
||||||
|
tab_container.current_tab = 0
|
||||||
|
|
||||||
|
|
||||||
|
func scene_closed(filepath: String) -> void:
|
||||||
|
if not is_instance_valid(tab_room): return
|
||||||
|
tab_room.scene_closed(filepath)
|
||||||
|
check_open_scenes()
|
||||||
|
|
||||||
|
|
||||||
|
func search_audio_files() -> void:
|
||||||
|
if not is_instance_valid(tab_audio): return
|
||||||
|
|
||||||
|
tab_audio.search_audio_files()
|
||||||
|
|
||||||
|
|
||||||
|
func open_setup() -> void:
|
||||||
|
PopochiuEditorHelper.show_setup()
|
||||||
|
|
||||||
|
|
||||||
|
## If there are no other opened scenes in the Editor, this function connects to
|
||||||
|
## [signal EditorSelection.selection_changed] in order to make sure the Popochiu dock behaves as
|
||||||
|
## expected when the [signal EditorPlugin.scene_changed] signal is not emitted.
|
||||||
|
func check_open_scenes() -> void:
|
||||||
|
# Fixes #273: Since Godot is not triggering the EditorPlugin.scene_changed signal when opening a
|
||||||
|
# scene when no other scenes are opened, listen to the EditorSelection.selection_changed signal
|
||||||
|
await get_tree().process_frame
|
||||||
|
if EditorInterface.get_open_scenes().is_empty():
|
||||||
|
EditorInterface.get_selection().selection_changed.connect(_on_editor_selection_changed)
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _on_tab_changed(tab: int) -> void:
|
||||||
|
if tab == tab_main.get_index():
|
||||||
|
tab_main.check_data()
|
||||||
|
|
||||||
|
if tab == tab_gui.get_index():
|
||||||
|
tab_gui.open_gui_scene()
|
||||||
|
|
||||||
|
|
||||||
|
func _check_node(node: Node) -> void:
|
||||||
|
if node is PopochiuCharacter and node.get_parent() is Node2D:
|
||||||
|
# The node is a PopochiuCharacter in a room
|
||||||
|
node.set_name.call_deferred("Character%s *" % node.script_name)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_editor_selection_changed() -> void:
|
||||||
|
if EditorInterface.get_edited_scene_root():
|
||||||
|
EditorInterface.get_selection().selection_changed.disconnect(_on_editor_selection_changed)
|
||||||
|
scene_changed(EditorInterface.get_edited_scene_root())
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
1
addons/popochiu/editor/main_dock/popochiu_dock.gd.uid
Normal file
1
addons/popochiu/editor/main_dock/popochiu_dock.gd.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://bcwtfohnkna8t
|
100
addons/popochiu/editor/main_dock/popochiu_dock.tscn
Normal file
100
addons/popochiu/editor/main_dock/popochiu_dock.tscn
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
[gd_scene load_steps=8 format=3 uid="uid://bardo4kb80rvg"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bynwdds8o3tcx" path="res://addons/popochiu/editor/main_dock/tab_main/tab_main.tscn" id="2_oxyje"]
|
||||||
|
[ext_resource type="Script" path="res://addons/popochiu/editor/main_dock/popochiu_dock.gd" id="7"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://4etgd0rwjgct" path="res://addons/popochiu/editor/main_dock/tab_gui/tab_gui.tscn" id="10_82goo"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://q1bjkxavt2ay" path="res://addons/popochiu/editor/main_dock/tab_room/tab_room.tscn" id="12"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bpj8jlet25coy" path="res://addons/popochiu/editor/main_dock/tab_audio/tab_audio.tscn" id="13"]
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_sgf6r"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 16,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_32xut"]
|
||||||
|
image = SubResource("Image_sgf6r")
|
||||||
|
|
||||||
|
[node name="Popochiu" type="Panel"]
|
||||||
|
clip_contents = true
|
||||||
|
custom_minimum_size = Vector2(340, 0)
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("7")
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||||
|
layout_mode = 0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="TabContainer" type="TabContainer" parent="MarginContainer/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
current_tab = 0
|
||||||
|
|
||||||
|
[node name="Main" parent="MarginContainer/VBoxContainer/TabContainer" instance=ExtResource("2_oxyje")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Room" parent="MarginContainer/VBoxContainer/TabContainer" instance=ExtResource("12")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
focus_mode = 2
|
||||||
|
metadata/_tab_index = 1
|
||||||
|
|
||||||
|
[node name="Audio" parent="MarginContainer/VBoxContainer/TabContainer" instance=ExtResource("13")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
focus_mode = 2
|
||||||
|
metadata/_tab_index = 2
|
||||||
|
|
||||||
|
[node name="GUI" parent="MarginContainer/VBoxContainer/TabContainer" instance=ExtResource("10_82goo")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
metadata/_tab_index = 3
|
||||||
|
|
||||||
|
[node name="FooterPanel" type="PanelContainer" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/FooterPanel"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
alignment = 2
|
||||||
|
|
||||||
|
[node name="Version" type="Label" parent="MarginContainer/VBoxContainer/FooterPanel/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "v2.0.1"
|
||||||
|
|
||||||
|
[node name="BtnSetup" type="Button" parent="MarginContainer/VBoxContainer/FooterPanel/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Opens wiki in web browser"
|
||||||
|
text = "Setup"
|
||||||
|
icon = SubResource("ImageTexture_32xut")
|
||||||
|
|
||||||
|
[node name="BtnDocs" type="Button" parent="MarginContainer/VBoxContainer/FooterPanel/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Opens wiki in web browser"
|
||||||
|
text = "Documentation"
|
||||||
|
icon = SubResource("ImageTexture_32xut")
|
51
addons/popochiu/editor/main_dock/popochiu_filter.gd
Normal file
51
addons/popochiu/editor/main_dock/popochiu_filter.gd
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
@tool
|
||||||
|
extends LineEdit
|
||||||
|
|
||||||
|
var groups := {}: set = set_groups
|
||||||
|
|
||||||
|
|
||||||
|
# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░
|
||||||
|
func _ready() -> void:
|
||||||
|
right_icon = get_theme_icon('Search', 'EditorIcons')
|
||||||
|
|
||||||
|
|
||||||
|
# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ SET & GET ░░░░
|
||||||
|
func set_groups(value: Dictionary) -> void:
|
||||||
|
groups = value
|
||||||
|
|
||||||
|
if groups:
|
||||||
|
text_changed.connect(
|
||||||
|
_filter_rows.bind(groups),
|
||||||
|
CONNECT_REFERENCE_COUNTED
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░
|
||||||
|
# `source` is one of the `_types` dictionaries in PopochiuDock, TabRoom and
|
||||||
|
# TabAudio
|
||||||
|
func _filter_rows(new_text: String, source: Dictionary) -> void:
|
||||||
|
for type_dic in source.values():
|
||||||
|
type_dic.group.show()
|
||||||
|
|
||||||
|
var title_in_filter := false
|
||||||
|
|
||||||
|
if type_dic.group.title.findn(new_text) > -1:
|
||||||
|
title_in_filter = true
|
||||||
|
|
||||||
|
var hidden_rows := 0
|
||||||
|
|
||||||
|
# type_dic.group is a PopochiuGroup
|
||||||
|
var rows: Array = type_dic.group.get_elements()
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
row.show()
|
||||||
|
|
||||||
|
if new_text.is_empty(): continue
|
||||||
|
|
||||||
|
if (row as Control).name.findn(new_text) < 0\
|
||||||
|
and not title_in_filter:
|
||||||
|
hidden_rows += 1
|
||||||
|
row.hide()
|
||||||
|
|
||||||
|
if hidden_rows == rows.size() and not new_text.is_empty():
|
||||||
|
type_dic.group.hide()
|
1
addons/popochiu/editor/main_dock/popochiu_filter.gd.uid
Normal file
1
addons/popochiu/editor/main_dock/popochiu_filter.gd.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://l0id2f7cqbkj
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
|
||||||
|
<g id="PopochiuGroup" transform="matrix(0.5,0,0,0.5,0,0)">
|
||||||
|
<rect x="0" y="0" width="32" height="32" style="fill:none;"/>
|
||||||
|
<g transform="matrix(3.25,0,0,3.25,-32.75,-39.25)">
|
||||||
|
<path d="M19,13.615C19,13.276 18.724,13 18.385,13L11.615,13C11.276,13 11,13.276 11,13.615L11,20.385C11,20.724 11.276,21 11.615,21L18.385,21C18.724,21 19,20.724 19,20.385L19,13.615Z" style="fill:none;stroke:rgb(112,109,235);stroke-width:1.23px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.673999,0,0,0.570028,13,-2.18068)">
|
||||||
|
<path d="M0,24L8.902,31.902L-0,39.789" style="fill:none;stroke:rgb(112,109,235);stroke-width:6.41px;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,37 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://uwjkfsrpfx3e"
|
||||||
|
path="res://.godot/imported/popochiu_group.svg-55d153202c16f767328f8314e84a5c37.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/popochiu/editor/main_dock/popochiu_group/images/popochiu_group.svg"
|
||||||
|
dest_files=["res://.godot/imported/popochiu_group.svg-55d153202c16f767328f8314e84a5c37.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
|
@ -0,0 +1,199 @@
|
||||||
|
@tool
|
||||||
|
@icon("res://addons/popochiu/editor/main_dock/popochiu_group/images/popochiu_group.svg")
|
||||||
|
class_name PopochiuGroup
|
||||||
|
extends PanelContainer
|
||||||
|
|
||||||
|
signal create_clicked
|
||||||
|
|
||||||
|
const PopochiuRow := preload("res://addons/popochiu/editor/main_dock/popochiu_row/popochiu_row.gd")
|
||||||
|
|
||||||
|
@export var icon: Texture2D : set = set_icon
|
||||||
|
@export var is_open := true : set = set_is_open
|
||||||
|
@export var color: Color = Color("999999") : set = set_color
|
||||||
|
@export var title := "Group" : set = set_title
|
||||||
|
@export var can_create := true
|
||||||
|
@export var create_text := ""
|
||||||
|
@export var target_list: NodePath = ""
|
||||||
|
@export var custom_title_count := false
|
||||||
|
|
||||||
|
var _external_list: VBoxContainer = null
|
||||||
|
|
||||||
|
@onready var header: PanelContainer = %Header
|
||||||
|
@onready var arrow: TextureRect = %Arrow
|
||||||
|
@onready var trt_icon: TextureRect = %Icon
|
||||||
|
@onready var lbl_title: Label = %Title
|
||||||
|
@onready var body: Container = %Body
|
||||||
|
@onready var btn_create: Button = %BtnCreate
|
||||||
|
@onready var list: VBoxContainer = %List
|
||||||
|
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _ready() -> void:
|
||||||
|
# Establecer estado inicial
|
||||||
|
add_theme_stylebox_override("panel", get_theme_stylebox("panel").duplicate())
|
||||||
|
(get_theme_stylebox("panel") as StyleBoxFlat).border_color = color
|
||||||
|
|
||||||
|
if is_instance_valid(icon):
|
||||||
|
trt_icon.texture = icon
|
||||||
|
|
||||||
|
lbl_title.text = title
|
||||||
|
btn_create.icon = get_theme_icon("Add", "EditorIcons")
|
||||||
|
btn_create.text = create_text
|
||||||
|
self.is_open = list.get_child_count() > 0
|
||||||
|
|
||||||
|
if not can_create:
|
||||||
|
btn_create.hide()
|
||||||
|
|
||||||
|
header.gui_input.connect(_on_input)
|
||||||
|
list.resized.connect(_update_child_count)
|
||||||
|
btn_create.pressed.connect(emit_signal.bind("create_clicked"))
|
||||||
|
|
||||||
|
if target_list:
|
||||||
|
_external_list = get_node(target_list) as VBoxContainer
|
||||||
|
self.is_open = _external_list.get_child_count() > 0
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
func clear_list() -> void:
|
||||||
|
for c in list.get_children():
|
||||||
|
# Fix #216: Delete the row immediately so that it does not interfere with the creation of
|
||||||
|
# other rows that may have the same name as it
|
||||||
|
c.free()
|
||||||
|
|
||||||
|
|
||||||
|
func add(node: Node, sort := false) -> void:
|
||||||
|
if sort:
|
||||||
|
node.ready.connect(_order_list.bind(node))
|
||||||
|
|
||||||
|
list.add_child(node)
|
||||||
|
|
||||||
|
btn_create.disabled = false
|
||||||
|
|
||||||
|
if not is_open:
|
||||||
|
self.is_open = true
|
||||||
|
|
||||||
|
|
||||||
|
func clear_favs() -> void:
|
||||||
|
for popochiu_row: PopochiuRow in list.get_children():
|
||||||
|
popochiu_row.clear_tag()
|
||||||
|
|
||||||
|
|
||||||
|
func disable_create() -> void:
|
||||||
|
btn_create.disabled = true
|
||||||
|
|
||||||
|
|
||||||
|
func enable_create() -> void:
|
||||||
|
btn_create.disabled = false
|
||||||
|
|
||||||
|
|
||||||
|
func get_elements() -> Array:
|
||||||
|
return list.get_children()
|
||||||
|
|
||||||
|
|
||||||
|
func remove_by_name(node_name: String) -> void:
|
||||||
|
if list.has_node(node_name):
|
||||||
|
var node: HBoxContainer = list.get_node(node_name)
|
||||||
|
|
||||||
|
list.remove_child(node)
|
||||||
|
node.free()
|
||||||
|
|
||||||
|
|
||||||
|
func add_header_button(btn: Button) -> void:
|
||||||
|
btn_create.add_sibling(btn)
|
||||||
|
|
||||||
|
|
||||||
|
func set_title_count(count: int, max_count := 0) -> void:
|
||||||
|
if max_count > 0:
|
||||||
|
lbl_title.text = "%s (%d/%d)" % [title, count, max_count]
|
||||||
|
else:
|
||||||
|
lbl_title.text = "%s (%d)" % [title, count]
|
||||||
|
|
||||||
|
|
||||||
|
func get_by_name(node_name: String) -> HBoxContainer:
|
||||||
|
if list.has_node(node_name):
|
||||||
|
return list.get_node(node_name)
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SetGet #####################################################################################
|
||||||
|
func set_icon(value: Texture2D) -> void:
|
||||||
|
icon = value
|
||||||
|
|
||||||
|
if is_instance_valid(trt_icon):
|
||||||
|
trt_icon.texture = value
|
||||||
|
|
||||||
|
|
||||||
|
func set_is_open(value: bool) -> void:
|
||||||
|
is_open = value
|
||||||
|
|
||||||
|
_toggled(value)
|
||||||
|
|
||||||
|
|
||||||
|
func set_color(value: Color) -> void:
|
||||||
|
color = value
|
||||||
|
|
||||||
|
if is_instance_valid(header):
|
||||||
|
(get_theme_stylebox("panel") as StyleBoxFlat).border_color = value
|
||||||
|
|
||||||
|
|
||||||
|
func set_title(value: String) -> void:
|
||||||
|
title = value
|
||||||
|
|
||||||
|
if is_instance_valid(lbl_title):
|
||||||
|
lbl_title.text = value
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _on_input(event: InputEvent) -> void:
|
||||||
|
var mouse_event: = event as InputEventMouseButton
|
||||||
|
if mouse_event and mouse_event.button_index == MOUSE_BUTTON_LEFT \
|
||||||
|
and mouse_event.pressed:
|
||||||
|
is_open = !is_open
|
||||||
|
_toggled(is_open)
|
||||||
|
|
||||||
|
|
||||||
|
func _toggled(button_pressed: bool) -> void:
|
||||||
|
if is_instance_valid(arrow):
|
||||||
|
arrow.texture = (
|
||||||
|
get_theme_icon("GuiTreeArrowDown", "EditorIcons") if button_pressed
|
||||||
|
else get_theme_icon("GuiTreeArrowRight", "EditorIcons")
|
||||||
|
)
|
||||||
|
|
||||||
|
if is_instance_valid(body):
|
||||||
|
if button_pressed: body.show()
|
||||||
|
else: body.hide()
|
||||||
|
|
||||||
|
if is_instance_valid(_external_list):
|
||||||
|
_external_list.visible = button_pressed
|
||||||
|
|
||||||
|
|
||||||
|
func _update_child_count() -> void:
|
||||||
|
if custom_title_count: return
|
||||||
|
|
||||||
|
if is_instance_valid(lbl_title):
|
||||||
|
var children := list.get_child_count()
|
||||||
|
lbl_title.text = title + (" (%d)" % children) if children > 1 else title
|
||||||
|
|
||||||
|
|
||||||
|
func _order_list(node: Node) -> void:
|
||||||
|
node.ready.disconnect(_order_list)
|
||||||
|
|
||||||
|
# Place the new row in its place alphabetically
|
||||||
|
var place_before: Node = null
|
||||||
|
for row in list.get_children():
|
||||||
|
if str(node.name) < str(row.name):
|
||||||
|
place_before = row
|
||||||
|
break
|
||||||
|
|
||||||
|
if not place_before: return
|
||||||
|
|
||||||
|
list.move_child(node, place_before.get_index())
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dfejejptym3da
|
|
@ -0,0 +1,91 @@
|
||||||
|
[gd_scene load_steps=6 format=3 uid="uid://b55ialbvpilxv"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/popochiu/editor/main_dock/popochiu_group/popochiu_group.gd" id="1_lumyt"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qhsn2"]
|
||||||
|
content_margin_left = 8.0
|
||||||
|
content_margin_right = 8.0
|
||||||
|
draw_center = false
|
||||||
|
border_width_left = 2
|
||||||
|
border_width_top = 2
|
||||||
|
border_width_right = 2
|
||||||
|
border_width_bottom = 2
|
||||||
|
border_color = Color(0.6, 0.6, 0.6, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_t8mu1"]
|
||||||
|
content_margin_left = 0.0
|
||||||
|
content_margin_top = 0.0
|
||||||
|
content_margin_right = 0.0
|
||||||
|
content_margin_bottom = 0.0
|
||||||
|
bg_color = Color(0.6, 0.6, 0.6, 0.211765)
|
||||||
|
draw_center = false
|
||||||
|
corner_detail = 5
|
||||||
|
expand_margin_left = 4.0
|
||||||
|
expand_margin_right = 4.0
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_e0ep0"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 16,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_uhpk4"]
|
||||||
|
image = SubResource("Image_e0ep0")
|
||||||
|
|
||||||
|
[node name="PopochiuGroup" type="PanelContainer"]
|
||||||
|
offset_right = 320.0
|
||||||
|
offset_bottom = 24.0
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_qhsn2")
|
||||||
|
script = ExtResource("1_lumyt")
|
||||||
|
is_open = false
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Header" type="PanelContainer" parent="VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_t8mu1")
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/Header"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Arrow" type="TextureRect" parent="VBoxContainer/Header/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 4
|
||||||
|
texture = SubResource("ImageTexture_uhpk4")
|
||||||
|
stretch_mode = 4
|
||||||
|
|
||||||
|
[node name="Icon" type="TextureRect" parent="VBoxContainer/Header/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
texture_filter = 1
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 4
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="VBoxContainer/Header/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "Group"
|
||||||
|
|
||||||
|
[node name="Body" type="VBoxContainer" parent="VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="BtnCreate" type="Button" parent="VBoxContainer/Body"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 8
|
||||||
|
icon = SubResource("ImageTexture_uhpk4")
|
||||||
|
|
||||||
|
[node name="List" type="VBoxContainer" parent="VBoxContainer/Body"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
|
@ -0,0 +1,237 @@
|
||||||
|
@tool
|
||||||
|
extends "res://addons/popochiu/editor/main_dock/popochiu_row/popochiu_row.gd"
|
||||||
|
|
||||||
|
signal group_selected(type: int)
|
||||||
|
signal deleted(file_path: String)
|
||||||
|
|
||||||
|
enum AudioOptions {
|
||||||
|
DELETE = MenuOptions.DELETE,
|
||||||
|
ADD_TO_MUSIC,
|
||||||
|
ADD_TO_SFX,
|
||||||
|
ADD_TO_VOICE,
|
||||||
|
ADD_TO_UI
|
||||||
|
}
|
||||||
|
|
||||||
|
const DELETE_AUDIO_CUE_MSG = "This will remove the [b]%s[/b] resource. Calls to this audio in \
|
||||||
|
scripts will not work anymore. This action cannot be reversed. Continue?"
|
||||||
|
const DELETE_AUDIO_CUE_ASK = "Delete [b]%s[/b] file too? (cannot be reversed)"
|
||||||
|
const DELETE_AUDIO_FILE_MSG = "[b]%s[/b] will be deleted in the file system. This action cannot be \
|
||||||
|
reversed. Continue?"
|
||||||
|
|
||||||
|
# Only used by rows that represent an audio file
|
||||||
|
var file_name: String
|
||||||
|
var audio_cue: AudioCue
|
||||||
|
var cue_group: String
|
||||||
|
var stream_player: AudioStreamPlayer
|
||||||
|
var audio_tab: VBoxContainer = null
|
||||||
|
var is_playing := false :
|
||||||
|
set = set_is_playing
|
||||||
|
var current_playback_position := 0.0
|
||||||
|
|
||||||
|
@onready var play_btn: Button = %Play
|
||||||
|
@onready var stop_btn: Button = %Stop
|
||||||
|
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _ready() -> void:
|
||||||
|
super()
|
||||||
|
|
||||||
|
# Assign icons
|
||||||
|
play_btn.icon = get_theme_icon("MainPlay", "EditorIcons")
|
||||||
|
stop_btn.icon = get_theme_icon("Stop", "EditorIcons")
|
||||||
|
|
||||||
|
# Connect to children's signals
|
||||||
|
play_btn.pressed.connect(play)
|
||||||
|
stop_btn.pressed.connect(stop)
|
||||||
|
|
||||||
|
# Remove group options if this is a PopochiuAudioCue
|
||||||
|
if is_instance_valid(audio_cue):
|
||||||
|
menu_popup.remove_item(menu_popup.get_item_index(AudioOptions.ADD_TO_MUSIC))
|
||||||
|
menu_popup.remove_item(menu_popup.get_item_index(AudioOptions.ADD_TO_SFX))
|
||||||
|
menu_popup.remove_item(menu_popup.get_item_index(AudioOptions.ADD_TO_VOICE))
|
||||||
|
menu_popup.remove_item(menu_popup.get_item_index(AudioOptions.ADD_TO_UI))
|
||||||
|
else:
|
||||||
|
label.text = file_name
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Virtual ####################################################################################
|
||||||
|
func _remove_object() -> void:
|
||||||
|
_delete_dialog = PopochiuEditorHelper.DELETE_CONFIRMATION_SCENE.instantiate()
|
||||||
|
|
||||||
|
if is_instance_valid(audio_cue):
|
||||||
|
_delete_dialog.title = "Remove %s cue" % audio_cue.resource_name
|
||||||
|
_delete_dialog.message = DELETE_AUDIO_CUE_MSG % audio_cue.resource_name
|
||||||
|
_delete_dialog.ask = DELETE_AUDIO_CUE_ASK % audio_cue.audio.resource_path
|
||||||
|
_delete_dialog.on_confirmed = _remove_from_popochiu
|
||||||
|
else:
|
||||||
|
_delete_dialog.title = "Delete %s" % file_name
|
||||||
|
_delete_dialog.message = DELETE_AUDIO_FILE_MSG % path
|
||||||
|
_delete_dialog.on_confirmed = _delete_from_file_system
|
||||||
|
|
||||||
|
PopochiuEditorHelper.show_delete_confirmation(_delete_dialog)
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
func select() -> void:
|
||||||
|
EditorInterface.edit_resource(audio_cue)
|
||||||
|
super()
|
||||||
|
|
||||||
|
|
||||||
|
func play() -> void:
|
||||||
|
if is_playing:
|
||||||
|
# Pause the audio stream
|
||||||
|
is_playing = false
|
||||||
|
return
|
||||||
|
|
||||||
|
if is_instance_valid(audio_tab.last_played):
|
||||||
|
# Stop the currently playing row (which is different from this one)
|
||||||
|
audio_tab.last_played.stop()
|
||||||
|
|
||||||
|
if not is_instance_valid(audio_cue):
|
||||||
|
# If the row does not have a [PopochiuAudioCue] assigned, then it is the row of an audio
|
||||||
|
# file. Therefore, the [AudioStream] to play will be its own [path]
|
||||||
|
var stream: AudioStream = load(path)
|
||||||
|
stream.loop = false
|
||||||
|
stream_player.stream = stream
|
||||||
|
else:
|
||||||
|
# Otherwise, the [AudioStream] to play will be that of the audio file associated with this
|
||||||
|
# [PopochiuAudioCue.audio]
|
||||||
|
stream_player.stream = audio_cue.audio
|
||||||
|
# The values of [AudioStream.pitch_scale] and [AudioStream.volume_db] should be taken from
|
||||||
|
# the information stored in the [PopochiuAudioCue].
|
||||||
|
stream_player.pitch_scale = audio_cue.get_pitch_scale()
|
||||||
|
stream_player.volume_db = audio_cue.volume
|
||||||
|
|
||||||
|
is_playing = true
|
||||||
|
|
||||||
|
|
||||||
|
func stop() -> void:
|
||||||
|
is_playing = false
|
||||||
|
current_playback_position = 0.0
|
||||||
|
label.add_theme_color_override("font_color", dflt_font_color)
|
||||||
|
stream_player.stream = null
|
||||||
|
audio_tab.last_played = null
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SetGet #####################################################################################
|
||||||
|
func set_is_playing(value: bool) -> void:
|
||||||
|
is_playing = value
|
||||||
|
|
||||||
|
if is_playing:
|
||||||
|
if not stream_player.finished.is_connected(stop):
|
||||||
|
stream_player.finished.connect(stop)
|
||||||
|
stream_player.play(current_playback_position)
|
||||||
|
audio_tab.last_played = self
|
||||||
|
else:
|
||||||
|
current_playback_position = stream_player.get_playback_position()
|
||||||
|
|
||||||
|
if stream_player.playing:
|
||||||
|
stream_player.stop()
|
||||||
|
stream_player.finished.disconnect(stop)
|
||||||
|
|
||||||
|
play_btn.icon = play_btn.get_theme_icon("Pause" if is_playing else "MainPlay", "EditorIcons")
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _get_menu_cfg() -> Array:
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id = AudioOptions.ADD_TO_MUSIC,
|
||||||
|
icon = preload("res://addons/popochiu/icons/music.png"),
|
||||||
|
label = "Add to Music"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id = AudioOptions.ADD_TO_SFX,
|
||||||
|
icon = preload("res://addons/popochiu/icons/sfx.png"),
|
||||||
|
label = "Add to Sound Effects"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id = AudioOptions.ADD_TO_VOICE,
|
||||||
|
icon = preload("res://addons/popochiu/icons/voice.png"),
|
||||||
|
label = "Add to Voices"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id = AudioOptions.ADD_TO_UI,
|
||||||
|
icon = preload("res://addons/popochiu/icons/ui.png"),
|
||||||
|
label = "Add to Graphic Interface"
|
||||||
|
}
|
||||||
|
] + super()
|
||||||
|
|
||||||
|
|
||||||
|
func _menu_item_pressed(id: int) -> void:
|
||||||
|
match id:
|
||||||
|
AudioOptions.ADD_TO_MUSIC:
|
||||||
|
group_selected.emit(PopochiuResources.AudioTypes.MUSIC)
|
||||||
|
AudioOptions.ADD_TO_SFX:
|
||||||
|
group_selected.emit(PopochiuResources.AudioTypes.SOUND_EFFECT)
|
||||||
|
AudioOptions.ADD_TO_VOICE:
|
||||||
|
group_selected.emit(PopochiuResources.AudioTypes.VOICE)
|
||||||
|
AudioOptions.ADD_TO_UI:
|
||||||
|
group_selected.emit(PopochiuResources.AudioTypes.UI)
|
||||||
|
_:
|
||||||
|
super(id)
|
||||||
|
|
||||||
|
|
||||||
|
func _remove_from_popochiu() -> void:
|
||||||
|
# Remove the AudioCue from popochiu_data.cfg ---------------------------------------------------
|
||||||
|
var group_data: Array = PopochiuResources.get_data_value(
|
||||||
|
"audio", cue_group, []
|
||||||
|
)
|
||||||
|
if group_data:
|
||||||
|
group_data.erase(audio_cue.resource_path)
|
||||||
|
|
||||||
|
if group_data.is_empty():
|
||||||
|
PopochiuResources.erase_data_value("audio", cue_group)
|
||||||
|
else:
|
||||||
|
group_data.sort_custom(
|
||||||
|
func (a: String, b: String) -> bool:
|
||||||
|
return PopochiuUtils.sort_by_file_name(a, b)
|
||||||
|
)
|
||||||
|
PopochiuResources.set_data_value("audio", cue_group, group_data)
|
||||||
|
|
||||||
|
# Remove the AudioCue from the A singleton -----------------------------------------------------
|
||||||
|
PopochiuResources.remove_audio_autoload(cue_group, name, audio_cue.resource_path)
|
||||||
|
|
||||||
|
# Delete the file in its corresponding group in Audio tab
|
||||||
|
deleted.emit(audio_cue.audio.resource_path)
|
||||||
|
|
||||||
|
if _delete_dialog.check_box.button_pressed:
|
||||||
|
_delete_from_file_system()
|
||||||
|
else:
|
||||||
|
queue_free()
|
||||||
|
|
||||||
|
|
||||||
|
func _delete_from_file_system() -> void:
|
||||||
|
# Delete the .tres file from the file system
|
||||||
|
var err: int = DirAccess.remove_absolute(path)
|
||||||
|
|
||||||
|
if err != OK:
|
||||||
|
PopochiuUtils.print_error("Couldn't delete audio cue %s (err_code: %d)" % [path, err])
|
||||||
|
return
|
||||||
|
|
||||||
|
# Delete the audio file linked to the cue
|
||||||
|
var audio_file_path := audio_cue.audio.resource_path
|
||||||
|
err = DirAccess.remove_absolute(audio_file_path)
|
||||||
|
|
||||||
|
if err != OK:
|
||||||
|
PopochiuUtils.print_error(
|
||||||
|
"Couldn't delete audio file %s (err_code: %d)" % [audio_file_path, err]
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Do this so Godot removes the .import file of the audio file
|
||||||
|
EditorInterface.get_resource_filesystem().update_file(audio_file_path)
|
||||||
|
EditorInterface.get_resource_filesystem().scan()
|
||||||
|
EditorInterface.get_resource_filesystem().scan_sources()
|
||||||
|
queue_free()
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://lhhhuwe3ye1f
|
|
@ -0,0 +1,62 @@
|
||||||
|
[gd_scene load_steps=9 format=3 uid="uid://ds6ojs55q50ud"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dwtwuqw2hpdpe" path="res://addons/popochiu/editor/main_dock/popochiu_row/popochiu_row.tscn" id="1_i2mx0"]
|
||||||
|
[ext_resource type="Script" path="res://addons/popochiu/editor/main_dock/popochiu_row/audio_row/popochiu_audio_row.gd" id="2_24kri"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://d1dnmfkhscb7r" path="res://addons/popochiu/icons/music.png" id="3_hi2e1"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://cfh1uxtaff0ks" path="res://addons/popochiu/icons/sfx.png" id="4_1iw68"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://6ewpl4v0td2h" path="res://addons/popochiu/icons/voice.png" id="5_ray7p"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://528j2rksws2c" path="res://addons/popochiu/icons/ui.png" id="6_1bl3m"]
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_dygia"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 16,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_v80gh"]
|
||||||
|
image = SubResource("Image_dygia")
|
||||||
|
|
||||||
|
[node name="PopochiuAudioRow" instance=ExtResource("1_i2mx0")]
|
||||||
|
script = ExtResource("2_24kri")
|
||||||
|
|
||||||
|
[node name="Label" parent="HBoxContainer" index="0"]
|
||||||
|
text = ""
|
||||||
|
|
||||||
|
[node name="Play" type="Button" parent="Panel/ButtonsContainer" index="0"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 1
|
||||||
|
icon = SubResource("ImageTexture_v80gh")
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[node name="Stop" type="Button" parent="Panel/ButtonsContainer" index="1"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 1
|
||||||
|
icon = SubResource("ImageTexture_v80gh")
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[node name="BtnMenu" parent="Panel/ButtonsContainer" index="2"]
|
||||||
|
icon = SubResource("ImageTexture_v80gh")
|
||||||
|
item_count = 6
|
||||||
|
popup/item_0/text = "Add to Music"
|
||||||
|
popup/item_0/icon = ExtResource("3_hi2e1")
|
||||||
|
popup/item_0/id = 1
|
||||||
|
popup/item_1/text = "Add to Sound effects"
|
||||||
|
popup/item_1/icon = ExtResource("4_1iw68")
|
||||||
|
popup/item_1/id = 2
|
||||||
|
popup/item_2/text = "Add to Voices"
|
||||||
|
popup/item_2/icon = ExtResource("5_ray7p")
|
||||||
|
popup/item_2/id = 3
|
||||||
|
popup/item_3/text = "Add to Graphic interface"
|
||||||
|
popup/item_3/icon = ExtResource("6_1bl3m")
|
||||||
|
popup/item_3/id = 4
|
||||||
|
popup/item_4/text = ""
|
||||||
|
popup/item_4/id = -1
|
||||||
|
popup/item_4/separator = true
|
||||||
|
popup/item_5/text = "Remove"
|
||||||
|
popup/item_5/icon = SubResource("ImageTexture_v80gh")
|
||||||
|
popup/item_5/id = 0
|
Binary file not shown.
After Width: | Height: | Size: 154 B |
|
@ -0,0 +1,34 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bw3ie8wfwa2i2"
|
||||||
|
path="res://.godot/imported/add_to_core.png-52def14ca6e499df1e292c93f01c4349.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/popochiu/editor/main_dock/popochiu_row/images/add_to_core.png"
|
||||||
|
dest_files=["res://.godot/imported/add_to_core.png-52def14ca6e499df1e292c93f01c4349.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
BIN
addons/popochiu/editor/main_dock/popochiu_row/images/delete.png
Normal file
BIN
addons/popochiu/editor/main_dock/popochiu_row/images/delete.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 212 B |
|
@ -0,0 +1,34 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bahipxbrrss0o"
|
||||||
|
path="res://.godot/imported/delete.png-27dd9adc116bbf3fc8b20a99d1331933.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/popochiu/editor/main_dock/popochiu_row/images/delete.png"
|
||||||
|
dest_files=["res://.godot/imported/delete.png-27dd9adc116bbf3fc8b20a99d1331933.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
BIN
addons/popochiu/editor/main_dock/popochiu_row/images/open.png
Normal file
BIN
addons/popochiu/editor/main_dock/popochiu_row/images/open.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 171 B |
|
@ -0,0 +1,34 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://wm7qycjntmfr"
|
||||||
|
path="res://.godot/imported/open.png-eb4e739212f91fcaedbead9efc5f731f.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/popochiu/editor/main_dock/popochiu_row/images/open.png"
|
||||||
|
dest_files=["res://.godot/imported/open.png-eb4e739212f91fcaedbead9efc5f731f.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
|
@ -0,0 +1,79 @@
|
||||||
|
@tool
|
||||||
|
extends "res://addons/popochiu/editor/main_dock/popochiu_row/object_row/popochiu_object_row.gd"
|
||||||
|
|
||||||
|
enum CharacterOptions {
|
||||||
|
DELETE = MenuOptions.DELETE,
|
||||||
|
ADD_TO_CORE = Options.ADD_TO_CORE,
|
||||||
|
SET_AS_PC,
|
||||||
|
}
|
||||||
|
|
||||||
|
const TAG_ICON = preload("res://addons/popochiu/icons/player_character.png")
|
||||||
|
const STATE_TEMPLATE = "res://addons/popochiu/engine/templates/character_state_template.gd"
|
||||||
|
|
||||||
|
var is_pc := false : set = set_is_pc
|
||||||
|
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _ready() -> void:
|
||||||
|
super()
|
||||||
|
|
||||||
|
# Assign icons
|
||||||
|
tag.texture = TAG_ICON
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Virtual ####################################################################################
|
||||||
|
func _get_state_template() -> Script:
|
||||||
|
return load(STATE_TEMPLATE)
|
||||||
|
|
||||||
|
|
||||||
|
func _clear_tag() -> void:
|
||||||
|
if is_pc:
|
||||||
|
is_pc = false
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SetGet #####################################################################################
|
||||||
|
func set_is_pc(value: bool) -> void:
|
||||||
|
is_pc = value
|
||||||
|
|
||||||
|
if is_pc:
|
||||||
|
PopochiuEditorHelper.signal_bus.pc_changed.emit(name)
|
||||||
|
|
||||||
|
tag.visible = value
|
||||||
|
menu_popup.set_item_disabled(menu_popup.get_item_index(CharacterOptions.SET_AS_PC), value)
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _get_menu_cfg() -> Array:
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id = CharacterOptions.SET_AS_PC,
|
||||||
|
icon = TAG_ICON,
|
||||||
|
label = "Set as Player-controlled Character (PC)",
|
||||||
|
},
|
||||||
|
] + super()
|
||||||
|
|
||||||
|
|
||||||
|
func _menu_item_pressed(id: int) -> void:
|
||||||
|
match id:
|
||||||
|
CharacterOptions.SET_AS_PC:
|
||||||
|
self.is_pc = true
|
||||||
|
_:
|
||||||
|
super(id)
|
||||||
|
|
||||||
|
|
||||||
|
func _remove_from_core() -> void:
|
||||||
|
# Delete the object from Popochiu
|
||||||
|
PopochiuResources.remove_autoload_obj(PopochiuResources.C_SNGL, name)
|
||||||
|
PopochiuResources.erase_data_value("characters", str(name))
|
||||||
|
|
||||||
|
# Continue with the deletion flow
|
||||||
|
super()
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://clcrmrvxn4gai
|
|
@ -0,0 +1,15 @@
|
||||||
|
@tool
|
||||||
|
extends "res://addons/popochiu/editor/main_dock/popochiu_row/object_row/popochiu_object_row.gd"
|
||||||
|
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _remove_from_core() -> void:
|
||||||
|
# Delete the object from Popochiu
|
||||||
|
PopochiuResources.remove_autoload_obj(PopochiuResources.D_SNGL, name)
|
||||||
|
PopochiuResources.erase_data_value("dialogs", str(name))
|
||||||
|
|
||||||
|
# Continue with the deletion flow
|
||||||
|
super()
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dxvpjllfksrgf
|
|
@ -0,0 +1,78 @@
|
||||||
|
@tool
|
||||||
|
extends "res://addons/popochiu/editor/main_dock/popochiu_row/object_row/popochiu_object_row.gd"
|
||||||
|
|
||||||
|
enum InventoryItemOptions {
|
||||||
|
DELETE = MenuOptions.DELETE,
|
||||||
|
ADD_TO_CORE = Options.ADD_TO_CORE,
|
||||||
|
START_WITH_IT,
|
||||||
|
}
|
||||||
|
|
||||||
|
const TAG_ICON = preload("res://addons/popochiu/icons/inventory_item_start.png")
|
||||||
|
const STATE_TEMPLATE = "res://addons/popochiu/engine/templates/inventory_item_state_template.gd"
|
||||||
|
|
||||||
|
var is_on_start := false : set = set_is_on_start
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _ready() -> void:
|
||||||
|
super()
|
||||||
|
|
||||||
|
# Assign icons
|
||||||
|
tag.texture = TAG_ICON
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Virtual ####################################################################################
|
||||||
|
func _get_state_template() -> Script:
|
||||||
|
return load(STATE_TEMPLATE)
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SetGet #####################################################################################
|
||||||
|
func set_is_on_start(value: bool) -> void:
|
||||||
|
is_on_start = value
|
||||||
|
tag.visible = value
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _get_menu_cfg() -> Array:
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id = InventoryItemOptions.START_WITH_IT,
|
||||||
|
icon = TAG_ICON,
|
||||||
|
label = "Start with it",
|
||||||
|
},
|
||||||
|
] + super()
|
||||||
|
|
||||||
|
|
||||||
|
func _menu_item_pressed(id: int) -> void:
|
||||||
|
match id:
|
||||||
|
InventoryItemOptions.START_WITH_IT:
|
||||||
|
var items: Array = PopochiuConfig.get_inventory_items_on_start()
|
||||||
|
var script_name := str(name)
|
||||||
|
|
||||||
|
if script_name in items:
|
||||||
|
items.erase(script_name)
|
||||||
|
else:
|
||||||
|
items.append(script_name)
|
||||||
|
|
||||||
|
PopochiuConfig.set_inventory_items_on_start(items)
|
||||||
|
|
||||||
|
self.is_on_start = script_name in items
|
||||||
|
_:
|
||||||
|
super(id)
|
||||||
|
|
||||||
|
|
||||||
|
func _remove_from_core() -> void:
|
||||||
|
# Delete the object from Popochiu
|
||||||
|
PopochiuResources.remove_autoload_obj(PopochiuResources.I_SNGL, name)
|
||||||
|
PopochiuResources.erase_data_value("inventory_items", str(name))
|
||||||
|
|
||||||
|
# Continue with the deletion flow
|
||||||
|
super()
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dkff7x3cr07ox
|
|
@ -0,0 +1,334 @@
|
||||||
|
@tool
|
||||||
|
extends "res://addons/popochiu/editor/main_dock/popochiu_row/popochiu_row.gd"
|
||||||
|
## Row for the main object types: Room, Character, Inventory item, Dialog
|
||||||
|
|
||||||
|
enum Options {
|
||||||
|
DELETE = MenuOptions.DELETE,
|
||||||
|
ADD_TO_CORE,
|
||||||
|
}
|
||||||
|
|
||||||
|
const DELETE_MESSAGE = "This will remove the [b]%s[/b] object in [b]%s[/b] scene. Uses of this \
|
||||||
|
object in scripts will not work anymore. This action cannot be undone. Continue?"
|
||||||
|
const DELETE_ASK_MESSAGE = "Do you want to delete the [b]%s[/b] folder too?%s (cannot be reversed)"
|
||||||
|
const ADD_TO_CORE_ICON = preload(
|
||||||
|
"res://addons/popochiu/editor/main_dock/popochiu_row/images/add_to_core.png"
|
||||||
|
)
|
||||||
|
const AUDIO_FILE_TYPES = ["AudioStreamOggVorbis", "AudioStreamMP3", "AudioStreamWAV"]
|
||||||
|
|
||||||
|
@onready var btn_open: Button = %BtnOpen
|
||||||
|
@onready var btn_script: Button = %BtnScript
|
||||||
|
@onready var btn_state: Button = %BtnState
|
||||||
|
@onready var btn_state_script: Button = %BtnStateScript
|
||||||
|
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _ready() -> void:
|
||||||
|
# Assign icons
|
||||||
|
btn_open.icon = get_theme_icon("InstanceOptions", "EditorIcons")
|
||||||
|
btn_script.icon = get_theme_icon("Script", "EditorIcons")
|
||||||
|
btn_state.icon = get_theme_icon("Object", "EditorIcons")
|
||||||
|
btn_state_script.icon = get_theme_icon("GDScript", "EditorIcons")
|
||||||
|
|
||||||
|
# Connect to signals and create the options for the menu
|
||||||
|
super()
|
||||||
|
|
||||||
|
# Connect to children's signals
|
||||||
|
btn_open.pressed.connect(_open)
|
||||||
|
btn_script.pressed.connect(_open_script)
|
||||||
|
btn_state.pressed.connect(_edit_state)
|
||||||
|
btn_state_script.pressed.connect(_open_state_script)
|
||||||
|
|
||||||
|
# Disable some options by default
|
||||||
|
var add_to_core_idx := menu_popup.get_item_index(Options.ADD_TO_CORE)
|
||||||
|
if add_to_core_idx >= 0:
|
||||||
|
menu_popup.set_item_disabled(add_to_core_idx, true)
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Virtual ####################################################################################
|
||||||
|
## Shows a confirmation popup to ask the developer if the Popochiu object should be removed only
|
||||||
|
## from the core, or from the file system too.
|
||||||
|
func _remove_object() -> void:
|
||||||
|
var location := _get_location()
|
||||||
|
|
||||||
|
# Look into the Object"s folder for audio files and AudioCues to show the developer that those
|
||||||
|
# files will be removed too.
|
||||||
|
var audio_files := _search_audio_files(
|
||||||
|
EditorInterface.get_resource_filesystem().get_filesystem_path(path.get_base_dir())
|
||||||
|
)
|
||||||
|
|
||||||
|
_delete_dialog = PopochiuEditorHelper.DELETE_CONFIRMATION_SCENE.instantiate()
|
||||||
|
_delete_dialog.title = "Remove %s from %s" % [name, location]
|
||||||
|
_delete_dialog.message = DELETE_MESSAGE % [name, location]
|
||||||
|
_delete_dialog.ask = DELETE_ASK_MESSAGE % [
|
||||||
|
path.get_base_dir(),
|
||||||
|
"" if audio_files.is_empty()
|
||||||
|
else " ([b]%d[/b] audio cues will be deleted)" % audio_files.size()
|
||||||
|
]
|
||||||
|
_delete_dialog.on_confirmed = _remove_from_core
|
||||||
|
|
||||||
|
PopochiuEditorHelper.show_delete_confirmation(_delete_dialog)
|
||||||
|
|
||||||
|
|
||||||
|
func _get_state_template() -> Script:
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
func _get_location() -> String:
|
||||||
|
return "Popochiu"
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public #####################################################################################
|
||||||
|
## Called to make the row appear semitransparent to indicate that the object is in the project
|
||||||
|
## (has a folder with files inside) but is not part of the [code]popochiu_data.cfg[/code] file nor
|
||||||
|
## its corresponding autoload (e.g., R, C, I, D). This can happen when one removes an object from
|
||||||
|
## the project without removing its files, or when adding objects from another project.
|
||||||
|
func show_as_not_in_core() -> void:
|
||||||
|
label.modulate.a = 0.5
|
||||||
|
menu_popup.set_item_disabled(menu_popup.get_item_index(Options.ADD_TO_CORE), false)
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _get_menu_cfg() -> Array:
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id = Options.ADD_TO_CORE,
|
||||||
|
icon = ADD_TO_CORE_ICON,
|
||||||
|
label = "Add to Popochiu",
|
||||||
|
types = PopochiuResources.MAIN_TYPES
|
||||||
|
},
|
||||||
|
] + super()
|
||||||
|
|
||||||
|
|
||||||
|
func _menu_item_pressed(id: int) -> void:
|
||||||
|
match id:
|
||||||
|
Options.ADD_TO_CORE:
|
||||||
|
_add_object_to_core()
|
||||||
|
_:
|
||||||
|
super(id)
|
||||||
|
|
||||||
|
|
||||||
|
## Add this Object (Room, Character, InventoryItem, Dialog) to popochiu_data.cfg so it can be used
|
||||||
|
## by Popochiu.
|
||||||
|
func _add_object_to_core() -> void:
|
||||||
|
var target_array := ""
|
||||||
|
var resource: Resource
|
||||||
|
|
||||||
|
if ".tscn" in path:
|
||||||
|
resource = load(path.replace(".tscn", ".tres"))
|
||||||
|
else:
|
||||||
|
resource = load(path)
|
||||||
|
|
||||||
|
match type:
|
||||||
|
PopochiuResources.Types.ROOM:
|
||||||
|
target_array = "rooms"
|
||||||
|
PopochiuResources.Types.CHARACTER:
|
||||||
|
target_array = "characters"
|
||||||
|
PopochiuResources.Types.INVENTORY_ITEM:
|
||||||
|
target_array = "inventory_items"
|
||||||
|
PopochiuResources.Types.DIALOG:
|
||||||
|
target_array = "dialogs"
|
||||||
|
|
||||||
|
if PopochiuEditorHelper.add_resource_to_popochiu(target_array, resource) != OK:
|
||||||
|
PopochiuUtils.print_error("Couldn't add Object [b]%s[/b] to Popochiu." % str(name))
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add the object to its corresponding singleton
|
||||||
|
PopochiuResources.update_autoloads(true)
|
||||||
|
|
||||||
|
label.modulate.a = 1.0
|
||||||
|
|
||||||
|
menu_popup.set_item_disabled(menu_popup.get_item_index(Options.ADD_TO_CORE), true)
|
||||||
|
|
||||||
|
|
||||||
|
## Selects the main file of the object in the FileSystem and opens it so that it can be edited.
|
||||||
|
func _open() -> void:
|
||||||
|
EditorInterface.select_file(path)
|
||||||
|
|
||||||
|
if ".tres" in path:
|
||||||
|
EditorInterface.edit_resource(load(path))
|
||||||
|
else:
|
||||||
|
EditorInterface.set_main_screen_editor("2D")
|
||||||
|
EditorInterface.open_scene_from_path(path)
|
||||||
|
|
||||||
|
select()
|
||||||
|
|
||||||
|
|
||||||
|
func _open_script() -> void:
|
||||||
|
var script_path := path
|
||||||
|
|
||||||
|
if ".tscn" in path:
|
||||||
|
# A room, character, inventory item, or prop
|
||||||
|
script_path = path.replace(".tscn", ".gd")
|
||||||
|
elif ".tres" in path:
|
||||||
|
# A dialog
|
||||||
|
script_path = path.replace(".tres", ".gd")
|
||||||
|
elif not ".gd" in path:
|
||||||
|
return
|
||||||
|
|
||||||
|
EditorInterface.select_file(script_path)
|
||||||
|
EditorInterface.set_main_screen_editor("Script")
|
||||||
|
EditorInterface.edit_script(load(script_path))
|
||||||
|
|
||||||
|
select()
|
||||||
|
|
||||||
|
|
||||||
|
func _edit_state() -> void:
|
||||||
|
EditorInterface.select_file(path.replace(".tscn", ".tres"))
|
||||||
|
EditorInterface.edit_resource(load(path.replace(".tscn", ".tres")))
|
||||||
|
|
||||||
|
select()
|
||||||
|
|
||||||
|
|
||||||
|
func _open_state_script() -> void:
|
||||||
|
var state := load(path.replace(".tscn", ".tres"))
|
||||||
|
|
||||||
|
EditorInterface.select_file(state.get_script().resource_path)
|
||||||
|
EditorInterface.set_main_screen_editor("Script")
|
||||||
|
EditorInterface.edit_resource(state.get_script())
|
||||||
|
|
||||||
|
select()
|
||||||
|
|
||||||
|
|
||||||
|
func _search_audio_files(dir: EditorFileSystemDirectory) -> Array:
|
||||||
|
var files := []
|
||||||
|
|
||||||
|
for idx in dir.get_subdir_count():
|
||||||
|
files.append_array(_search_audio_files(dir.get_subdir(idx)))
|
||||||
|
|
||||||
|
for idx in dir.get_file_count():
|
||||||
|
match dir.get_file_type(idx):
|
||||||
|
AUDIO_FILE_TYPES:
|
||||||
|
files.append(dir.get_file_path(idx))
|
||||||
|
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
|
func _remove_from_core() -> void:
|
||||||
|
# Check if the files should be deleted in the file system
|
||||||
|
if _delete_dialog.check_box.button_pressed:
|
||||||
|
_delete_from_file_system()
|
||||||
|
elif type in PopochiuResources.MAIN_TYPES:
|
||||||
|
show_as_not_in_core()
|
||||||
|
|
||||||
|
var edited_scene: Node = EditorInterface.get_edited_scene_root()
|
||||||
|
if edited_scene and edited_scene.get("script_name") and edited_scene.script_name == name:
|
||||||
|
# If the open scene matches the object being deleted, skip saving the scene
|
||||||
|
queue_free()
|
||||||
|
return
|
||||||
|
|
||||||
|
EditorInterface.save_scene()
|
||||||
|
queue_free()
|
||||||
|
|
||||||
|
|
||||||
|
## Remove this object's directory (subfolders included) from the file system.
|
||||||
|
func _delete_from_file_system() -> void:
|
||||||
|
var object_dir: EditorFileSystemDirectory = \
|
||||||
|
EditorInterface.get_resource_filesystem().get_filesystem_path(path.get_base_dir())
|
||||||
|
|
||||||
|
# Remove files, sub folders and its files.
|
||||||
|
_recursive_delete(object_dir)
|
||||||
|
|
||||||
|
|
||||||
|
## Remove the `dir` directory from the system. For Godot to be able to delete a directory, it has to
|
||||||
|
## be empty, so this method first deletes the files from from the directory and each of its
|
||||||
|
## subdirectories.
|
||||||
|
func _recursive_delete(dir: EditorFileSystemDirectory) -> void:
|
||||||
|
if dir.get_file_count() > 0:
|
||||||
|
assert(
|
||||||
|
_delete_files(dir) == OK,
|
||||||
|
"[Popochiu] Error removing files in recursive elimination of %s" % dir.get_path()
|
||||||
|
)
|
||||||
|
|
||||||
|
if dir.get_subdir_count() > 0:
|
||||||
|
for folder_idx in dir.get_subdir_count():
|
||||||
|
# Check if there are more folders inside the folder or delete the files inside it before
|
||||||
|
# deleting the folder itself
|
||||||
|
_recursive_delete(dir.get_subdir(folder_idx))
|
||||||
|
|
||||||
|
assert(
|
||||||
|
DirAccess.remove_absolute(dir.get_path()) == OK,
|
||||||
|
"[Popochiu] Error removing folder in recursive elimination of %s" % dir.get_path()
|
||||||
|
)
|
||||||
|
EditorInterface.get_resource_filesystem().scan()
|
||||||
|
|
||||||
|
|
||||||
|
## Delete files within [param dir] directory. First, get the paths to each file, then delete them
|
||||||
|
## one by one calling [method EditorFileSystem.update_file], so that in case it's an imported file,
|
||||||
|
## its [b].import[/b] is also deleted.
|
||||||
|
func _delete_files(dir: EditorFileSystemDirectory) -> int:
|
||||||
|
# Stores the paths of the files to be deleted.
|
||||||
|
var files_paths := []
|
||||||
|
# Stores the paths of the audio resources to delete
|
||||||
|
var deleted_audios := []
|
||||||
|
|
||||||
|
for file_idx: int in dir.get_file_count():
|
||||||
|
match dir.get_file_type(file_idx):
|
||||||
|
AUDIO_FILE_TYPES:
|
||||||
|
deleted_audios.append(dir.get_file_path(file_idx))
|
||||||
|
"Resource":
|
||||||
|
var resource: Resource = load(dir.get_file_path(file_idx))
|
||||||
|
if not resource is AudioCue:
|
||||||
|
# If the resource is not an AudioCue, then it should be ignored for deletion
|
||||||
|
# in the game data
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Delete the [PopochiuAudioCue] in the project data file and the A singleton
|
||||||
|
assert(
|
||||||
|
_delete_audio_cue_in_data(resource) == true,
|
||||||
|
"[Popochiu] Couldn't remove [b]%s[/b] during deletion of [b]%s[/b]." %
|
||||||
|
[resource.resource_path, dir.get_path()]
|
||||||
|
)
|
||||||
|
|
||||||
|
deleted_audios.append(resource.audio.resource_path)
|
||||||
|
|
||||||
|
files_paths.append(dir.get_file_path(file_idx))
|
||||||
|
|
||||||
|
for fp: String in files_paths:
|
||||||
|
var err: int = DirAccess.remove_absolute(fp)
|
||||||
|
if err != OK:
|
||||||
|
PopochiuUtils.print_error("Couldn't delete file %s. err_code:%d" % [err, fp])
|
||||||
|
return err
|
||||||
|
EditorInterface.get_resource_filesystem().scan()
|
||||||
|
|
||||||
|
# Delete the rows of audio files and the deleted AudioCues in the Audio tab
|
||||||
|
if not deleted_audios.is_empty():
|
||||||
|
PopochiuEditorHelper.signal_bus.audio_cues_deleted.emit(deleted_audios)
|
||||||
|
|
||||||
|
# Remove extra files (like .import)
|
||||||
|
for file_name: String in DirAccess.get_files_at(dir.get_path()):
|
||||||
|
DirAccess.remove_absolute(dir.get_path() + "/" + file_name)
|
||||||
|
EditorInterface.get_resource_filesystem().scan()
|
||||||
|
|
||||||
|
return OK
|
||||||
|
|
||||||
|
|
||||||
|
## Looks to which audio group corresponds [param audio_cue] and deletes it both from
|
||||||
|
## [code]popochiu_data.cfg[/code] and the [b]A[/b] singleton (which is the one used to allow code
|
||||||
|
## autocompletion related to [PopochiuAudioCue]s).
|
||||||
|
func _delete_audio_cue_in_data(audio_cue: AudioCue) -> bool:
|
||||||
|
# TODO: This could be improved a lot if each PopochiuAudioCue has a variable to store the group
|
||||||
|
# to which it corresponds to.
|
||||||
|
# Delete the [PopochiuAudioCue] in the popochiu_data.cfg
|
||||||
|
for cue_group in ["mx_cues", "sfx_cues", "vo_cues", "ui_cues"]:
|
||||||
|
var cues: Array = PopochiuResources.get_data_value("audio", cue_group, [])
|
||||||
|
if not cues.has(audio_cue.resource_path): continue
|
||||||
|
|
||||||
|
cues.erase(audio_cue.resource_path)
|
||||||
|
if PopochiuResources.set_data_value("audio", cue_group, cues) != OK:
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Fix #59 : remove the [PopochiuAudioCue] from the [A] singleton
|
||||||
|
PopochiuResources.remove_audio_autoload(
|
||||||
|
cue_group, audio_cue.resource_name, audio_cue.resource_path
|
||||||
|
)
|
||||||
|
break
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dj0dw4jcynb3p
|
|
@ -0,0 +1,58 @@
|
||||||
|
[gd_scene load_steps=5 format=3 uid="uid://dwbo3pl372ugo"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dwtwuqw2hpdpe" path="res://addons/popochiu/editor/main_dock/popochiu_row/popochiu_row.tscn" id="1_xi41g"]
|
||||||
|
[ext_resource type="Script" path="res://addons/popochiu/editor/main_dock/popochiu_row/object_row/popochiu_object_row.gd" id="2_g05pm"]
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_15l6n"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 16,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_12sj2"]
|
||||||
|
image = SubResource("Image_15l6n")
|
||||||
|
|
||||||
|
[node name="PopochiuMainObjectRow" instance=ExtResource("1_xi41g")]
|
||||||
|
script = ExtResource("2_g05pm")
|
||||||
|
|
||||||
|
[node name="Label" parent="HBoxContainer" index="0"]
|
||||||
|
text = "PopochiuMainObjectRow"
|
||||||
|
|
||||||
|
[node name="BtnOpen" type="Button" parent="Panel/ButtonsContainer" index="0"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 3
|
||||||
|
tooltip_text = "Open in Editor"
|
||||||
|
icon = SubResource("ImageTexture_12sj2")
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[node name="BtnScript" type="Button" parent="Panel/ButtonsContainer" index="1"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 3
|
||||||
|
tooltip_text = "Open in Script"
|
||||||
|
icon = SubResource("ImageTexture_12sj2")
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[node name="BtnState" type="Button" parent="Panel/ButtonsContainer" index="2"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 3
|
||||||
|
tooltip_text = "Open state"
|
||||||
|
icon = SubResource("ImageTexture_12sj2")
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[node name="BtnStateScript" type="Button" parent="Panel/ButtonsContainer" index="3"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 3
|
||||||
|
tooltip_text = "Open state Script"
|
||||||
|
icon = SubResource("ImageTexture_12sj2")
|
||||||
|
flat = true
|
|
@ -0,0 +1,60 @@
|
||||||
|
@tool
|
||||||
|
extends "res://addons/popochiu/editor/main_dock/popochiu_row/object_row/popochiu_object_row.gd"
|
||||||
|
|
||||||
|
const PROP_TEMPLATE = "res://addons/popochiu/engine/templates/prop_template.gd"
|
||||||
|
|
||||||
|
var node_path := ""
|
||||||
|
|
||||||
|
|
||||||
|
#region Godot ######################################################################################
|
||||||
|
func _ready() -> void:
|
||||||
|
super()
|
||||||
|
|
||||||
|
if not FileAccess.file_exists(path.replace(".tscn", ".gd")):
|
||||||
|
btn_script.hide()
|
||||||
|
|
||||||
|
btn_state.hide()
|
||||||
|
btn_state_script.hide()
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Virtual ####################################################################################
|
||||||
|
func _get_location() -> String:
|
||||||
|
# Structure of path: "res://game/rooms/room_name/props/prop_name/"
|
||||||
|
# path split: [res:, popochiu, rooms, room_name, props, prop_name]
|
||||||
|
return "Room%s" % (path.split("/", false)[3]).to_pascal_case()
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private ####################################################################################
|
||||||
|
func _remove_from_core() -> void:
|
||||||
|
var room_child_to_free: Node = null
|
||||||
|
|
||||||
|
if EditorInterface.get_edited_scene_root() is PopochiuRoom:
|
||||||
|
var opened_room: PopochiuRoom = EditorInterface.get_edited_scene_root()
|
||||||
|
match type:
|
||||||
|
PopochiuResources.Types.PROP:
|
||||||
|
room_child_to_free = opened_room.get_prop(str(name))
|
||||||
|
PopochiuResources.Types.HOTSPOT:
|
||||||
|
room_child_to_free = opened_room.get_hotspot(str(name))
|
||||||
|
PopochiuResources.Types.MARKER:
|
||||||
|
room_child_to_free = opened_room.get_marker(str(name))
|
||||||
|
PopochiuResources.Types.REGION:
|
||||||
|
room_child_to_free = opened_room.get_region(str(name))
|
||||||
|
PopochiuResources.Types.WALKABLE_AREA:
|
||||||
|
room_child_to_free = opened_room.get_walkable_area(str(name))
|
||||||
|
|
||||||
|
# Continue with the deletion flow
|
||||||
|
super()
|
||||||
|
|
||||||
|
# Fix #196: Remove the Node from the Room tree once the folder of the object has been deleted
|
||||||
|
# from the FileSystem (this applies to Props, Hotspots, Walkable areas and Regions).
|
||||||
|
if room_child_to_free:
|
||||||
|
room_child_to_free.queue_free()
|
||||||
|
|
||||||
|
EditorInterface.save_scene()
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue