mirror of
https://github.com/Lacustat/Freewood-OctoWoW-Mail.git
synced 2026-10-31 06:36:56 +00:00
0.1.152: eigenes Briefkasten-Modul
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016onJCSHLwhs9udhC9ZLzk9
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
## Interface: 11200
|
||||
## Title: Freewood |cffc89b3cMail|r
|
||||
## Notes: Own mailbox window: open all or selected mails safely, recent recipients, mailing lists with automatic sending, multi-item mails, gold surplus and cash on delivery.
|
||||
## Notes-deDE: Eigenes Briefkasten-Fenster: alles oder Ausgewähltes sicher öffnen, letzte Empfänger, Mailinglisten mit Automatik, mehrere Items, Gold-Überschuss und Nachnahme.
|
||||
## Author: Lacustat
|
||||
## Version: 0.1.152
|
||||
## Dependencies: Freewood-OctoWoW-Core
|
||||
## OptionalDeps: Freewood-OctoWoW-Creator, Freewood-OctoWoW-Brett
|
||||
|
||||
Locales.lua
|
||||
Mail.lua
|
||||
MailUI.lua
|
||||
@@ -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>.
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
-- Freewood-OctoWoW-Mail: Texte
|
||||
local ADDON, NS = ...
|
||||
|
||||
local FW = _G.Freewood
|
||||
if not FW then
|
||||
return
|
||||
end
|
||||
|
||||
FW:AddLocale("enUS", {
|
||||
MAIL_TITLE = "Mailbox",
|
||||
MAIL_DESC = "Own mailbox window: open all or selected mails safely, recent recipients, mailing lists, several items per mail, gold surplus and cash on delivery.",
|
||||
MAIL_WINDOW_TITLE = "Freewood Mailbox",
|
||||
MAIL_TAB_INBOX = "Inbox",
|
||||
MAIL_TAB_SEND = "Send",
|
||||
MAIL_TAB_LISTS = "Mailing lists",
|
||||
MAIL_CLOSE = "Close mailbox",
|
||||
|
||||
MAIL_INBOX_COUNT = "%d mails in the inbox",
|
||||
MAIL_OPEN_ALL = "Open all",
|
||||
MAIL_OPEN_SELECTED = "Open selected only (%d)",
|
||||
MAIL_STOP = "Stop",
|
||||
MAIL_SELECT_ALL = "Select all",
|
||||
MAIL_SELECT_NONE = "Select none",
|
||||
MAIL_INBOX_EMPTY = "The inbox is empty.",
|
||||
MAIL_ROW_GOLD = "Gold: %s",
|
||||
MAIL_ROW_COD = "Cash on delivery: %s (not taken)",
|
||||
MAIL_ROW_LETTER = "Letter only (not touched)",
|
||||
MAIL_ROW_DAYS = "%d d",
|
||||
MAIL_ROW_FROM = "From %s",
|
||||
MAIL_NOTE_SAFE = "Mails are never deleted or returned. Cash-on-delivery mails and plain letters are skipped. One bag slot always stays free.",
|
||||
MAIL_RUN_DONE = "Mailbox: %d gold mails and %d items taken%s.",
|
||||
MAIL_RUN_SKIP_FULL = ", %d items left because the bags are nearly full",
|
||||
MAIL_RUN_STOPPED = "Mailbox: stopped. %d gold mails and %d items taken.",
|
||||
MAIL_RUN_PROGRESS = "Taking mail... %d items, %d gold mails",
|
||||
MAIL_RUN_FULL = "Bags nearly full: items stay in the mailbox (one slot stays free).",
|
||||
|
||||
MAIL_TO = "Recipient",
|
||||
MAIL_TO_RECENT = "Last recipients",
|
||||
MAIL_TO_NONE = "No recipients yet",
|
||||
MAIL_SUBJECT = "Subject",
|
||||
MAIL_BODY = "Text",
|
||||
MAIL_ITEMS = "Items",
|
||||
MAIL_ITEMS_ADD = "Add items...",
|
||||
MAIL_ITEMS_NONE = "No items. Without items only gold (if any) is sent.",
|
||||
MAIL_ITEMS_REMOVE = "Remove",
|
||||
MAIL_GOLD = "Gold",
|
||||
MAIL_SILVER = "Silver",
|
||||
MAIL_COPPER = "Copper",
|
||||
MAIL_COD = "Cash on delivery (the recipient pays this amount for every mail)",
|
||||
MAIL_SEND = "Send",
|
||||
MAIL_SEND_COUNT = "%d mails will be sent one after another.",
|
||||
MAIL_SEND_COUNT1 = "1 mail will be sent.",
|
||||
MAIL_SEND_NOTHING = "Nothing to send: add items or gold.",
|
||||
MAIL_SEND_NO_TO = "Enter a recipient.",
|
||||
MAIL_SEND_SELF = "You cannot mail yourself.",
|
||||
MAIL_SEND_COD_NEEDS_ITEM = "Cash on delivery needs at least one item.",
|
||||
MAIL_BOARD_BUSY = "A board mission is preparing a mail. Finish it in the mission window first; this window waits.",
|
||||
MAIL_BUSY = "Another action is running. Please wait.",
|
||||
|
||||
MAIL_PICK_TITLE = "Choose items",
|
||||
MAIL_PICK_LIST_TITLE = "Items for %s",
|
||||
MAIL_PICK_DONE = "Done",
|
||||
MAIL_PICK_BAGS = "In your bags",
|
||||
MAIL_PICK_EXTRA = "Not in your bags",
|
||||
MAIL_PICK_EMPTY = "Nothing to choose in your bags.",
|
||||
MAIL_PICK_HINT_LIST = "All stacks of a chosen item are sent. Click again to remove.",
|
||||
MAIL_PICK_HINT_SEND = "Every chosen stack is one mail. Click again to remove.",
|
||||
|
||||
MAIL_LISTS_HEADER = "Mailing lists",
|
||||
MAIL_LISTS_HINT = "Create a name, choose its items, and tick the box to send automatically whenever the mailbox opens.",
|
||||
MAIL_LISTS_ADD = "Add",
|
||||
MAIL_LISTS_NAME_BOX = "Character name",
|
||||
MAIL_LISTS_EMPTY = "No mailing list yet.",
|
||||
MAIL_LISTS_ITEMS = "Items (%d)",
|
||||
MAIL_LISTS_AUTO = "Send automatically when the mailbox opens",
|
||||
MAIL_LISTS_GOLD = "Send gold above",
|
||||
MAIL_LISTS_GOLD_G = "gold (keep this much)",
|
||||
MAIL_LISTS_NOW = "Send now",
|
||||
MAIL_LISTS_REMOVE = "Remove list",
|
||||
MAIL_LISTS_SELF = "This is you; it is skipped on this character.",
|
||||
MAIL_LISTS_RESERVED = "%d items reserved for board missions",
|
||||
MAIL_LISTS_ALL_NOW = "Send all lists now",
|
||||
|
||||
MAIL_SUBJECT_STAMP = "%s",
|
||||
MAIL_SUBJECT_N = "%s - %d of %d",
|
||||
MAIL_SUBJECT_ITEM_N = "%s (%d of %d)",
|
||||
MAIL_SUBJECT_DEFAULT = "Freewood",
|
||||
|
||||
MAIL_SEND_PROGRESS = "Sending mail %d of %d to %s...",
|
||||
MAIL_SEND_DONE = "Mail: %d of %d mails sent%s.",
|
||||
MAIL_SEND_DONE_FAIL = ", %d skipped",
|
||||
MAIL_SEND_STOPPED = "Mail: sending stopped after %d of %d mails.",
|
||||
MAIL_SEND_NOTHING_LIST = "Mail: nothing to send for %s.",
|
||||
MAIL_SEND_FAIL_ATTACH = "Mail: %s could not be attached (soulbound or locked?). Skipped.",
|
||||
MAIL_SEND_FAIL_TIMEOUT = "Mail: no answer from the server. Sending stopped.",
|
||||
MAIL_SEND_FAIL_ERROR = "Mail: the server refused a mail. Sending stopped.",
|
||||
MAIL_AUTO_WAIT_BOARD = "Mail: automatic sending waits for the board mission mail.",
|
||||
|
||||
MAIL_SET_HEADER = "Mailbox",
|
||||
MAIL_SET_REPLACE = "Replace the normal mailbox window with the Freewood mailbox",
|
||||
MAIL_SET_CHAT = "Report results in chat",
|
||||
MAIL_SET_NOTE = "Off by default. With the replacement on, the normal window stays open but invisible, so board missions keep working. /fwmail off switches back at any time.",
|
||||
MAIL_CMD_ON = "Freewood mailbox window: on",
|
||||
MAIL_CMD_OFF = "Freewood mailbox window: off",
|
||||
MAIL_CMD_CLOSED = "Open a mailbox first.",
|
||||
})
|
||||
|
||||
FW:AddLocale("deDE", {
|
||||
MAIL_TITLE = "Briefkasten",
|
||||
MAIL_DESC = "Eigenes Briefkasten-Fenster: alles oder Ausgewähltes sicher öffnen, letzte Empfänger, Mailinglisten, mehrere Items pro Versand, Gold-Überschuss und Nachnahme.",
|
||||
MAIL_WINDOW_TITLE = "Freewood Briefkasten",
|
||||
MAIL_TAB_INBOX = "Posteingang",
|
||||
MAIL_TAB_SEND = "Senden",
|
||||
MAIL_TAB_LISTS = "Mailinglisten",
|
||||
MAIL_CLOSE = "Briefkasten schließen",
|
||||
|
||||
MAIL_INBOX_COUNT = "%d Briefe im Posteingang",
|
||||
MAIL_OPEN_ALL = "Alle öffnen",
|
||||
MAIL_OPEN_SELECTED = "Nur Ausgewählte öffnen (%d)",
|
||||
MAIL_STOP = "Stopp",
|
||||
MAIL_SELECT_ALL = "Alle auswählen",
|
||||
MAIL_SELECT_NONE = "Keine auswählen",
|
||||
MAIL_INBOX_EMPTY = "Der Posteingang ist leer.",
|
||||
MAIL_ROW_GOLD = "Gold: %s",
|
||||
MAIL_ROW_COD = "Nachnahme: %s (wird nicht genommen)",
|
||||
MAIL_ROW_LETTER = "Nur ein Brief (bleibt unberührt)",
|
||||
MAIL_ROW_DAYS = "%d T",
|
||||
MAIL_ROW_FROM = "Von %s",
|
||||
MAIL_NOTE_SAFE = "Briefe werden nie gelöscht oder zurückgeschickt. Nachnahme-Briefe und reine Briefe werden übersprungen. Ein Taschenplatz bleibt immer frei.",
|
||||
MAIL_RUN_DONE = "Briefkasten: %d Gold-Briefe und %d Items genommen%s.",
|
||||
MAIL_RUN_SKIP_FULL = ", %d Items blieben liegen, weil die Taschen fast voll sind",
|
||||
MAIL_RUN_STOPPED = "Briefkasten: gestoppt. %d Gold-Briefe und %d Items genommen.",
|
||||
MAIL_RUN_PROGRESS = "Nehme Post... %d Items, %d Gold-Briefe",
|
||||
MAIL_RUN_FULL = "Taschen fast voll: Items bleiben im Briefkasten (ein Platz bleibt frei).",
|
||||
|
||||
MAIL_TO = "Empfänger",
|
||||
MAIL_TO_RECENT = "Letzte Empfänger",
|
||||
MAIL_TO_NONE = "Noch keine Empfänger",
|
||||
MAIL_SUBJECT = "Betreff",
|
||||
MAIL_BODY = "Text",
|
||||
MAIL_ITEMS = "Items",
|
||||
MAIL_ITEMS_ADD = "Items hinzufügen...",
|
||||
MAIL_ITEMS_NONE = "Keine Items. Ohne Items wird nur Gold (falls angegeben) gesendet.",
|
||||
MAIL_ITEMS_REMOVE = "Entfernen",
|
||||
MAIL_GOLD = "Gold",
|
||||
MAIL_SILVER = "Silber",
|
||||
MAIL_COPPER = "Kupfer",
|
||||
MAIL_COD = "Nachnahme (der Empfänger zahlt diesen Betrag für jeden Brief)",
|
||||
MAIL_SEND = "Senden",
|
||||
MAIL_SEND_COUNT = "%d Briefe werden nacheinander gesendet.",
|
||||
MAIL_SEND_COUNT1 = "1 Brief wird gesendet.",
|
||||
MAIL_SEND_NOTHING = "Nichts zu senden: Items oder Gold angeben.",
|
||||
MAIL_SEND_NO_TO = "Empfänger eingeben.",
|
||||
MAIL_SEND_SELF = "Du kannst dir selbst nichts schicken.",
|
||||
MAIL_SEND_COD_NEEDS_ITEM = "Nachnahme braucht mindestens ein Item.",
|
||||
MAIL_BOARD_BUSY = "Ein Brett-Auftrag bereitet gerade einen Brief vor. Erst im Auftragsfenster abschließen; dieses Fenster wartet.",
|
||||
MAIL_BUSY = "Es läuft gerade eine andere Aktion. Bitte warten.",
|
||||
|
||||
MAIL_PICK_TITLE = "Items auswählen",
|
||||
MAIL_PICK_LIST_TITLE = "Items für %s",
|
||||
MAIL_PICK_DONE = "Fertig",
|
||||
MAIL_PICK_BAGS = "In deinen Taschen",
|
||||
MAIL_PICK_EXTRA = "Nicht in den Taschen",
|
||||
MAIL_PICK_EMPTY = "In deinen Taschen gibt es nichts zum Auswählen.",
|
||||
MAIL_PICK_HINT_LIST = "Alle Stapel eines gewählten Items werden gesendet. Nochmal klicken entfernt es.",
|
||||
MAIL_PICK_HINT_SEND = "Jeder gewählte Stapel ist ein Brief. Nochmal klicken entfernt ihn.",
|
||||
|
||||
MAIL_LISTS_HEADER = "Mailinglisten",
|
||||
MAIL_LISTS_HINT = "Namen anlegen, Items festlegen und mit dem Haken automatisch senden, sobald der Briefkasten aufgeht.",
|
||||
MAIL_LISTS_ADD = "Anlegen",
|
||||
MAIL_LISTS_NAME_BOX = "Charaktername",
|
||||
MAIL_LISTS_EMPTY = "Noch keine Mailingliste.",
|
||||
MAIL_LISTS_ITEMS = "Items (%d)",
|
||||
MAIL_LISTS_AUTO = "Automatisch senden, sobald der Briefkasten aufgeht",
|
||||
MAIL_LISTS_GOLD = "Gold senden über",
|
||||
MAIL_LISTS_GOLD_G = "Gold (so viel behalten)",
|
||||
MAIL_LISTS_NOW = "Jetzt senden",
|
||||
MAIL_LISTS_REMOVE = "Liste entfernen",
|
||||
MAIL_LISTS_SELF = "Das bist du; auf diesem Charakter wird sie übersprungen.",
|
||||
MAIL_LISTS_RESERVED = "%d Items sind für Brett-Aufträge reserviert",
|
||||
MAIL_LISTS_ALL_NOW = "Alle Listen jetzt senden",
|
||||
|
||||
MAIL_SUBJECT_STAMP = "%s",
|
||||
MAIL_SUBJECT_N = "%s - %d von %d",
|
||||
MAIL_SUBJECT_ITEM_N = "%s (%d von %d)",
|
||||
MAIL_SUBJECT_DEFAULT = "Freewood",
|
||||
|
||||
MAIL_SEND_PROGRESS = "Sende Brief %d von %d an %s...",
|
||||
MAIL_SEND_DONE = "Briefkasten: %d von %d Briefen gesendet%s.",
|
||||
MAIL_SEND_DONE_FAIL = ", %d übersprungen",
|
||||
MAIL_SEND_STOPPED = "Briefkasten: Senden nach %d von %d Briefen gestoppt.",
|
||||
MAIL_SEND_NOTHING_LIST = "Briefkasten: Für %s gibt es nichts zu senden.",
|
||||
MAIL_SEND_FAIL_ATTACH = "Briefkasten: %s ließ sich nicht anhängen (seelengebunden oder gesperrt?). Übersprungen.",
|
||||
MAIL_SEND_FAIL_TIMEOUT = "Briefkasten: keine Antwort vom Server. Senden gestoppt.",
|
||||
MAIL_SEND_FAIL_ERROR = "Briefkasten: Der Server hat einen Brief abgelehnt. Senden gestoppt.",
|
||||
MAIL_AUTO_WAIT_BOARD = "Briefkasten: Das automatische Senden wartet auf den Brief des Brett-Auftrags.",
|
||||
|
||||
MAIL_SET_HEADER = "Briefkasten",
|
||||
MAIL_SET_REPLACE = "Normales Briefkasten-Fenster durch das Freewood-Briefkasten ersetzen",
|
||||
MAIL_SET_CHAT = "Ergebnisse im Chat melden",
|
||||
MAIL_SET_NOTE = "Standardmäßig aus. Mit Ersetzen bleibt das normale Fenster geöffnet, aber unsichtbar, damit Brett-Aufträge weiter funktionieren. /fwmail off schaltet jederzeit zurück.",
|
||||
MAIL_CMD_ON = "Freewood-Briefkasten-Fenster: an",
|
||||
MAIL_CMD_OFF = "Freewood-Briefkasten-Fenster: aus",
|
||||
MAIL_CMD_CLOSED = "Öffne zuerst einen Briefkasten.",
|
||||
})
|
||||
@@ -0,0 +1,800 @@
|
||||
-- Freewood-OctoWoW-Mail: eigenes Briefkasten (Logik).
|
||||
--
|
||||
-- Posteingang "Alle oeffnen" / "Nur Ausgewaehlte oeffnen": nimmt Gold und Items, ein Schritt nach dem anderen.
|
||||
-- - loescht und retourniert nie einen Brief; Nachnahme-Briefe und reine Briefe bleiben unberuehrt
|
||||
-- - ein Taschenplatz bleibt immer frei (Items bleiben sonst im Briefkasten)
|
||||
-- - nach jedem Schritt wird der Posteingang neu gelesen (die Nummern verschieben sich)
|
||||
-- Senden Empfaenger mit den letzten 5, mehrere Items (je Item ein Brief, nacheinander), Gold, Nachnahme
|
||||
-- Mailinglisten Namen mit Items, optional Gold-Ueberschuss und Automatik beim Oeffnen des Briefkastens
|
||||
--
|
||||
-- Alle Sendungen laufen ueber dieselbe Warteschlange (Mail.send): ein Brief, dann auf MAIL_SEND_SUCCESS warten,
|
||||
-- dann Pause, dann der naechste. Sie laufen nie, solange ein Brett-Auftrag einen Brief vorbereitet (BoardBusy).
|
||||
--
|
||||
-- Gespeichert: Account-weit Mail.db = { settings, recipients, lists }.
|
||||
|
||||
local ADDON, NS = ...
|
||||
|
||||
local FW = _G.Freewood
|
||||
if not FW or not FW:RequireAPI(1, ADDON) then
|
||||
return
|
||||
end
|
||||
|
||||
local Util = FW.Util
|
||||
local L = FW.L
|
||||
|
||||
local MAX_RECENT = 5
|
||||
local MAX_LISTS = 30
|
||||
local MAX_ITEMS_PER_LIST = 60
|
||||
local MAX_JOBS = 120
|
||||
local STEP = 0.1 -- Takt des Treibers
|
||||
local INBOX_GAP = 0.5 -- Pause nach dem Nehmen
|
||||
local INBOX_TIMEOUT = 2.5 -- so lange auf die Antwort des Servers warten
|
||||
local SEND_GAP = 1.2 -- Pause nach jedem gesendeten Brief
|
||||
local SEND_TIMEOUT = 8
|
||||
local POSTAGE = 30 -- Kupfer je Brief (Schaetzung fuer den Gold-Ueberschuss)
|
||||
local AUTO_DELAY = 2.5
|
||||
local KEEP_FREE = 1 -- so viele Taschenplaetze bleiben beim Oeffnen immer frei
|
||||
|
||||
local function GetMeta(field)
|
||||
local fn = (C_AddOns and C_AddOns.GetAddOnMetadata) or GetAddOnMetadata
|
||||
if fn then
|
||||
local ok, value = pcall(fn, ADDON, field)
|
||||
if ok then
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local Mail = FW:NewModule("Mail", {
|
||||
version = GetMeta("Version") or "dev",
|
||||
title = L.MAIL_TITLE,
|
||||
description = L.MAIL_DESC,
|
||||
defaults = {
|
||||
settings = { replace = false, chat = true },
|
||||
recipients = {},
|
||||
lists = {},
|
||||
},
|
||||
})
|
||||
NS.Mail = Mail
|
||||
FW.Mail = Mail
|
||||
|
||||
Mail.open = false -- ein Briefkasten ist offen
|
||||
Mail.busy = nil -- nil | "inbox" | "send"
|
||||
Mail.gen = 0 -- zaehlt die Briefkasten-Oeffnungen
|
||||
|
||||
local function Now() return GetTime and GetTime() or 0 end
|
||||
local function After(t, fn)
|
||||
if C_Timer and C_Timer.After then C_Timer.After(t, fn) else pcall(fn) end
|
||||
end
|
||||
|
||||
local function Trace(msg)
|
||||
local C = FW.Creator
|
||||
if C and C.Trace then pcall(C.Trace, "Mail: " .. tostring(msg)) end
|
||||
end
|
||||
Mail.Trace = Trace
|
||||
|
||||
local function Say(msg)
|
||||
if Mail.db and Mail.db.settings.chat and FW.Print then FW:Print(msg) end
|
||||
end
|
||||
|
||||
function Mail.Money(c)
|
||||
if Util.FormatMoney then return Util.FormatMoney(c) end
|
||||
return tostring(c) .. "c"
|
||||
end
|
||||
|
||||
local function Trim(s)
|
||||
return (tostring(s or ""):gsub("^%s+", ""):gsub("%s+$", ""))
|
||||
end
|
||||
|
||||
-- Charaktername: Leerzeichen weg, erster Buchstabe gross
|
||||
function Mail.CleanName(s)
|
||||
s = Trim(s):gsub("%s+", "")
|
||||
if s == "" then return "" end
|
||||
return (s:sub(1, 1):upper() .. s:sub(2))
|
||||
end
|
||||
|
||||
local function IsSelf(name)
|
||||
local me = UnitName and UnitName("player")
|
||||
return me and name and name:lower() == me:lower()
|
||||
end
|
||||
Mail.IsSelf = IsSelf
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Taschen
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local function NumSlots(bag)
|
||||
local fn = (C_Container and C_Container.GetContainerNumSlots) or GetContainerNumSlots
|
||||
if type(fn) ~= "function" then return 0 end
|
||||
local ok, n = pcall(fn, bag)
|
||||
return (ok and tonumber(n)) or 0
|
||||
end
|
||||
|
||||
local function SlotInfo(bag, slot)
|
||||
if C_Container and C_Container.GetContainerItemInfo then
|
||||
local ok, info = pcall(C_Container.GetContainerItemInfo, bag, slot)
|
||||
if ok and type(info) == "table" then
|
||||
return tonumber(info.itemID), tonumber(info.stackCount) or 1, info.isLocked and true or false
|
||||
end
|
||||
end
|
||||
if GetContainerItemInfo then
|
||||
local ok, _, count, locked, _, _, _, link, _, _, itemID = pcall(GetContainerItemInfo, bag, slot)
|
||||
if ok then
|
||||
local id = tonumber(itemID) or (type(link) == "string" and tonumber(link:match("item:(%d+)"))) or nil
|
||||
if not id and GetContainerItemLink then
|
||||
local ok2, l2 = pcall(GetContainerItemLink, bag, slot)
|
||||
if ok2 and type(l2) == "string" then id = tonumber(l2:match("item:(%d+)")) end
|
||||
end
|
||||
return id, tonumber(count) or 1, locked and true or false
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function BagCount() return tonumber(_G.NUM_BAG_SLOTS) or 4 end
|
||||
|
||||
-- Alle Staepel in den normalen Taschen: { bag, slot, id, count, locked }
|
||||
function Mail.BagStacks()
|
||||
local out = {}
|
||||
for bag = 0, BagCount() do
|
||||
for slot = 1, NumSlots(bag) do
|
||||
local id, count, locked = SlotInfo(bag, slot)
|
||||
if id then out[#out + 1] = { bag = bag, slot = slot, id = id, count = count, locked = locked } end
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
-- freie Plaetze in Taschen ohne Spezialtyp (Koecher, Seelentasche ... zaehlen nicht)
|
||||
function Mail.FreeSlots()
|
||||
local fn = (C_Container and C_Container.GetContainerNumFreeSlots) or GetContainerNumFreeSlots
|
||||
if type(fn) ~= "function" then return 0 end
|
||||
local n = 0
|
||||
for bag = 0, BagCount() do
|
||||
local ok, free, kind = pcall(fn, bag)
|
||||
if ok and tonumber(free) and (kind == nil or kind == 0) then n = n + free end
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Zusammenspiel mit den Brett-Auftraegen (Creator)
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- Ein Brett-Auftrag bereitet gerade einen Brief vor oder fragt danach: dann fassen wir den Brief nicht an.
|
||||
function Mail.BoardBusy()
|
||||
local C = FW.Creator
|
||||
if not C then return false end
|
||||
if C.mailChoice or C.asking then return true end
|
||||
if C.askQueue and #C.askQueue > 0 then return true end
|
||||
if C.GuardState then
|
||||
local ok, st = pcall(C.GuardState)
|
||||
if ok and st and st ~= "off" then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Items, die ein offener Brett-Auftrag noch verschicken muss: set[itemID] = true
|
||||
function Mail.Reserved()
|
||||
local set, n = {}, 0
|
||||
local C = FW.Creator
|
||||
if C and C.ActiveList and C.db and C.ObjProgress then
|
||||
local ok = pcall(function()
|
||||
for _, id in ipairs(C.ActiveList()) do
|
||||
local q = C.db.quests[id]
|
||||
for i, o in ipairs(q and q.objs or {}) do
|
||||
if o.type == "mail" and o.item then
|
||||
local have, need = C.ObjProgress(id, i)
|
||||
if have < need and not set[o.item] then
|
||||
set[o.item] = true
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
if not ok then Trace("Reservierung konnte nicht gelesen werden") end
|
||||
end
|
||||
return set, n
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Empfaenger (die letzten 5)
|
||||
------------------------------------------------------------------------
|
||||
|
||||
function Mail:AddRecipient(name)
|
||||
name = Mail.CleanName(name)
|
||||
if name == "" or IsSelf(name) then return end
|
||||
local list = self.db.recipients
|
||||
for i = #list, 1, -1 do
|
||||
if list[i]:lower() == name:lower() then table.remove(list, i) end
|
||||
end
|
||||
table.insert(list, 1, name)
|
||||
while #list > MAX_RECENT do table.remove(list) end
|
||||
FW:Fire("MAIL_RECIPIENTS_CHANGED")
|
||||
end
|
||||
|
||||
function Mail:Recipients()
|
||||
return self.db.recipients
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Posteingang
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- Liste der Briefe: { i, sender, subject, money, cod, days, hasItem, itemName, itemTex, itemCount, sig, key, take, why }
|
||||
function Mail.ReadInbox()
|
||||
local list = {}
|
||||
if not GetInboxNumItems then return list end
|
||||
local ok, n = pcall(GetInboxNumItems)
|
||||
n = (ok and tonumber(n)) or 0
|
||||
local seen = {}
|
||||
for i = 1, n do
|
||||
local ok1, _, _, sender, subject, money, cod, days, hasItem, wasRead = pcall(GetInboxHeaderInfo, i)
|
||||
if ok1 then
|
||||
local m = {
|
||||
i = i, sender = sender or "?", subject = subject or "",
|
||||
money = tonumber(money) or 0, cod = tonumber(cod) or 0, days = tonumber(days),
|
||||
hasItem = (hasItem and hasItem ~= 0) and true or false, read = wasRead and true or false,
|
||||
}
|
||||
if m.hasItem and GetInboxItem then
|
||||
local ok2, name, tex, count = pcall(GetInboxItem, i)
|
||||
if ok2 then m.itemName, m.itemTex, m.itemCount = name, tex, tonumber(count) or 1 end
|
||||
end
|
||||
m.sig = table.concat({ m.sender, m.subject, m.money, m.cod, tostring(m.itemName), tostring(m.itemCount) }, "|")
|
||||
seen[m.sig] = (seen[m.sig] or 0) + 1
|
||||
m.key = m.sig .. "#" .. seen[m.sig]
|
||||
if m.cod > 0 then
|
||||
m.take, m.why = false, "cod"
|
||||
elseif m.money > 0 or m.hasItem then
|
||||
m.take = true
|
||||
else
|
||||
m.take, m.why = false, "letter"
|
||||
end
|
||||
list[#list + 1] = m
|
||||
end
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
Mail.selected = {} -- key -> true
|
||||
|
||||
function Mail:SelectedCount()
|
||||
local n = 0
|
||||
for _ in pairs(self.selected) do n = n + 1 end
|
||||
return n
|
||||
end
|
||||
|
||||
function Mail:ToggleSelect(key, on)
|
||||
if on == nil then on = not self.selected[key] end
|
||||
self.selected[key] = on and true or nil
|
||||
FW:Fire("MAIL_SELECTION_CHANGED")
|
||||
end
|
||||
|
||||
function Mail:SelectAll(on)
|
||||
self.selected = {}
|
||||
if on then
|
||||
for _, m in ipairs(Mail.ReadInbox()) do
|
||||
if m.take then self.selected[m.key] = true end
|
||||
end
|
||||
end
|
||||
FW:Fire("MAIL_SELECTION_CHANGED")
|
||||
end
|
||||
|
||||
-- onlySelected: nur angehakte Briefe
|
||||
function Mail:StartOpen(onlySelected)
|
||||
if self.busy or not self.open then return false end
|
||||
local only
|
||||
if onlySelected then
|
||||
only = {}
|
||||
for k in pairs(self.selected) do only[k] = true end
|
||||
if next(only) == nil then return false end
|
||||
end
|
||||
self.run = { only = only, tries = {}, money = 0, items = 0, pending = nil }
|
||||
self.busy = "inbox"
|
||||
self.driver:Show()
|
||||
Trace("Posteingang: Start" .. (only and " (nur Ausgewaehlte)" or " (alle)"))
|
||||
FW:Fire("MAIL_STATE")
|
||||
return true
|
||||
end
|
||||
|
||||
local function FinishOpen(self, reason)
|
||||
local run = self.run
|
||||
self.run, self.busy = nil, nil
|
||||
if not run then return end
|
||||
local skipped = 0
|
||||
if run.full and reason == "done" then
|
||||
for _, m in ipairs(Mail.ReadInbox()) do
|
||||
if m.take and m.hasItem and (not run.only or run.only[m.key]) then skipped = skipped + 1 end
|
||||
end
|
||||
end
|
||||
Trace(string.format("Posteingang: Ende (%s), %d Gold-Briefe, %d Items, %d Items liegen geblieben", reason, run.money, run.items, skipped))
|
||||
if reason == "stopped" or reason == "closed" then
|
||||
Say(string.format(FW.L.MAIL_RUN_STOPPED, run.money, run.items))
|
||||
else
|
||||
Say(string.format(FW.L.MAIL_RUN_DONE, run.money, run.items, skipped > 0 and string.format(FW.L.MAIL_RUN_SKIP_FULL, skipped) or ""))
|
||||
end
|
||||
self.lastRun = { money = run.money, items = run.items, skipped = skipped }
|
||||
self.selected = {}
|
||||
FW:Fire("MAIL_STATE")
|
||||
FW:Fire("MAIL_SELECTION_CHANGED")
|
||||
FW:Fire("MAIL_INBOX_CHANGED")
|
||||
end
|
||||
|
||||
function Mail:Stop()
|
||||
if self.busy == "inbox" and self.run then FinishOpen(self, "stopped") end
|
||||
if self.busy == "send" and self.send then self:FinishSend("stopped") end
|
||||
end
|
||||
|
||||
function Mail:StepOpen()
|
||||
local run = self.run
|
||||
local now = Now()
|
||||
if not self.open then return FinishOpen(self, "closed") end
|
||||
if run.pending then
|
||||
-- auf die Antwort des Servers warten (oder den Ablauf der Zeit)
|
||||
if not ((run.updated and now >= run.pending + INBOX_GAP) or now >= run.pending + INBOX_TIMEOUT) then return end
|
||||
run.pending = nil
|
||||
end
|
||||
local list = Mail.ReadInbox()
|
||||
local free = Mail.FreeSlots()
|
||||
for idx = #list, 1, -1 do
|
||||
local m = list[idx]
|
||||
if m.take and (not run.only or run.only[m.key]) and (run.tries[m.sig] or 0) < 3 then
|
||||
if m.money > 0 then
|
||||
run.tries[m.sig] = (run.tries[m.sig] or 0) + 1
|
||||
run.money = run.money + 1
|
||||
run.pending, run.updated = now, false
|
||||
pcall(TakeInboxMoney, m.i)
|
||||
Trace(string.format("Brief %d von %s: Gold %d genommen", m.i, m.sender, m.money))
|
||||
FW:Fire("MAIL_STATE")
|
||||
return
|
||||
elseif m.hasItem then
|
||||
if free <= KEEP_FREE then
|
||||
run.full = true
|
||||
else
|
||||
run.tries[m.sig] = (run.tries[m.sig] or 0) + 1
|
||||
run.items = run.items + 1
|
||||
run.pending, run.updated = now, false
|
||||
pcall(TakeInboxItem, m.i)
|
||||
Trace(string.format("Brief %d von %s: Item %s genommen (frei vorher %d)", m.i, m.sender, tostring(m.itemName), free))
|
||||
FW:Fire("MAIL_STATE")
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
FinishOpen(self, "done")
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Senden (gemeinsame Warteschlange)
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local function AttachedName()
|
||||
if not GetSendMailItem then return nil end
|
||||
local ok, name = pcall(GetSendMailItem, 1)
|
||||
if ok and name and name ~= "" then return name end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Anhang-Platz leeren (zurueck in die Tasche)
|
||||
local function ClearAttachment()
|
||||
if AttachedName() and ClickSendMailItemButton then
|
||||
pcall(ClickSendMailItemButton)
|
||||
if CursorHasItem and CursorHasItem() then pcall(ClearCursor) end
|
||||
end
|
||||
end
|
||||
|
||||
local function FindStack(item)
|
||||
local fallback
|
||||
for _, s in ipairs(Mail.BagStacks()) do
|
||||
if s.id == item.id and not s.locked then
|
||||
if s.count == item.count then return s end
|
||||
fallback = fallback or s
|
||||
end
|
||||
end
|
||||
return fallback
|
||||
end
|
||||
|
||||
-- Bereitet einen Brief vor. Rueckgabe: true oder false, Grund
|
||||
local function Prepare(job)
|
||||
if CursorHasItem and CursorHasItem() then return false, "cursor" end
|
||||
ClearAttachment()
|
||||
if job.item then
|
||||
local s = FindStack(job.item)
|
||||
if not s then return false, "gone" end
|
||||
pcall(ClearCursor)
|
||||
pcall(PickupContainerItem, s.bag, s.slot)
|
||||
if not (CursorHasItem and CursorHasItem()) then return false, "pickup" end
|
||||
if not ClickSendMailItemButton then pcall(ClearCursor) return false, "noapi" end
|
||||
pcall(ClickSendMailItemButton)
|
||||
if not AttachedName() then
|
||||
pcall(ClearCursor)
|
||||
return false, "attach"
|
||||
end
|
||||
end
|
||||
if SetSendMailMoney then pcall(SetSendMailMoney, (job.cod or 0) > 0 and 0 or (job.money or 0)) end
|
||||
if SetSendMailCOD then pcall(SetSendMailCOD, job.cod or 0) end
|
||||
return true
|
||||
end
|
||||
|
||||
local function JobLabel(job)
|
||||
if job.item then
|
||||
local info = Util.GetItem and Util.GetItem(job.item.id)
|
||||
return (info and info.name) or ("#" .. tostring(job.item.id))
|
||||
end
|
||||
return Mail.Money(job.money or 0)
|
||||
end
|
||||
|
||||
-- jobs: { to, subject, body, item = {id,count}, money, cod }
|
||||
function Mail:StartSend(jobs)
|
||||
if self.busy then return false, "busy" end
|
||||
if not self.open then return false, "closed" end
|
||||
if Mail.BoardBusy() then return false, "board" end
|
||||
if not jobs or #jobs == 0 then return false, "empty" end
|
||||
self.send = { jobs = jobs, i = 0, sent = 0, failed = 0, stage = "next", nextAt = 0 }
|
||||
self.busy = "send"
|
||||
self.driver:Show()
|
||||
Trace(string.format("Senden: Start mit %d Briefen", #jobs))
|
||||
FW:Fire("MAIL_STATE")
|
||||
return true
|
||||
end
|
||||
|
||||
function Mail:FinishSend(reason)
|
||||
local s = self.send
|
||||
self.send, self.busy = nil, nil
|
||||
if not s then return end
|
||||
pcall(ClearCursor)
|
||||
if reason ~= "done" then ClearAttachment() end
|
||||
Trace(string.format("Senden: Ende (%s), %d von %d gesendet, %d uebersprungen", reason, s.sent, #s.jobs, s.failed))
|
||||
if reason == "done" then
|
||||
Say(string.format(FW.L.MAIL_SEND_DONE, s.sent, #s.jobs, s.failed > 0 and string.format(FW.L.MAIL_SEND_DONE_FAIL, s.failed) or ""))
|
||||
elseif reason == "timeout" then
|
||||
Say(FW.L.MAIL_SEND_FAIL_TIMEOUT)
|
||||
elseif reason == "error" then
|
||||
Say(FW.L.MAIL_SEND_FAIL_ERROR)
|
||||
else
|
||||
Say(string.format(FW.L.MAIL_SEND_STOPPED, s.sent, #s.jobs))
|
||||
end
|
||||
FW:Fire("MAIL_STATE")
|
||||
FW:Fire("MAIL_SENT_DONE", s.sent, #s.jobs)
|
||||
end
|
||||
|
||||
function Mail:StepSend()
|
||||
local s = self.send
|
||||
local now = Now()
|
||||
if not self.open then return self:FinishSend("closed") end
|
||||
if Mail.BoardBusy() then return self:FinishSend("board") end
|
||||
if s.stage == "wait" then
|
||||
if s.ok then
|
||||
s.stage, s.nextAt = "next", now + SEND_GAP
|
||||
elseif s.err then
|
||||
return self:FinishSend("error")
|
||||
elseif now > s.deadline then
|
||||
return self:FinishSend("timeout")
|
||||
end
|
||||
return
|
||||
end
|
||||
if now < s.nextAt then return end
|
||||
if CursorHasItem and CursorHasItem() then return end
|
||||
s.i = s.i + 1
|
||||
local job = s.jobs[s.i]
|
||||
if not job then return self:FinishSend("done") end
|
||||
s.current = job
|
||||
local ok, why = Prepare(job)
|
||||
if not ok then
|
||||
s.failed = s.failed + 1
|
||||
s.nextAt = now + 0.2
|
||||
Trace(string.format("Brief %d (%s) nicht vorbereitet: %s", s.i, JobLabel(job), tostring(why)))
|
||||
if why == "attach" or why == "gone" or why == "pickup" then
|
||||
Say(string.format(FW.L.MAIL_SEND_FAIL_ATTACH, JobLabel(job)))
|
||||
end
|
||||
FW:Fire("MAIL_STATE")
|
||||
return
|
||||
end
|
||||
s.ok, s.err, s.deadline, s.stage = false, false, now + SEND_TIMEOUT, "wait"
|
||||
Trace(string.format("Brief %d von %d an %s: %s (Betreff '%s', Nachnahme %d)", s.i, #s.jobs, job.to, JobLabel(job), tostring(job.subject), job.cod or 0))
|
||||
FW:Fire("MAIL_STATE")
|
||||
local okc = pcall(SendMail, job.to, job.subject or "", job.body or "")
|
||||
if not okc then s.err = true end
|
||||
end
|
||||
|
||||
-- Fortschritt fuer die Anzeige: i, n, an wen
|
||||
function Mail:SendProgress()
|
||||
local s = self.send
|
||||
if not s then return nil end
|
||||
local job = s.current or s.jobs[1]
|
||||
return math.min(s.i, #s.jobs), #s.jobs, job and job.to or "?"
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Briefe zusammenstellen
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local function Stamp() return date and date("%d.%m. %H:%M") or "" end
|
||||
|
||||
-- Eingabe des Senden-Reiters: form = { to, subject, body, picks = { {id,count}... }, money, cod = bool }
|
||||
function Mail:BuildCompose(form)
|
||||
local to = Mail.CleanName(form.to)
|
||||
if to == "" then return nil, "to" end
|
||||
if IsSelf(to) then return nil, "self" end
|
||||
local picks = form.picks or {}
|
||||
local money = math.floor(tonumber(form.money) or 0)
|
||||
if #picks == 0 and money <= 0 then return nil, "nothing" end
|
||||
if form.cod and #picks == 0 then return nil, "codItem" end
|
||||
if form.cod and money <= 0 then return nil, "codMoney" end
|
||||
local base = Trim(form.subject)
|
||||
local jobs = {}
|
||||
local n = math.max(#picks, 1)
|
||||
for i = 1, n do
|
||||
local pick = picks[i]
|
||||
local subject = base
|
||||
if subject == "" then
|
||||
local info = pick and Util.GetItem and Util.GetItem(pick.id)
|
||||
subject = (info and info.name) or FW.L.MAIL_SUBJECT_DEFAULT
|
||||
end
|
||||
if n > 1 then subject = string.format(FW.L.MAIL_SUBJECT_ITEM_N, subject, i, n) end
|
||||
local job = { to = to, subject = subject, body = form.body or "", item = pick }
|
||||
if form.cod then
|
||||
job.cod = money
|
||||
elseif i == 1 and money > 0 then
|
||||
job.money = money
|
||||
end
|
||||
jobs[#jobs + 1] = job
|
||||
end
|
||||
if #jobs > MAX_JOBS then return nil, "many" end
|
||||
return jobs
|
||||
end
|
||||
|
||||
-- Eintrag der Mailingliste -> Briefe. reserved: Items fuer Brett-Auftraege (werden nicht gesendet)
|
||||
function Mail:BuildList(entry, reserved)
|
||||
local jobs = {}
|
||||
if not entry or IsSelf(entry.name) then return jobs end
|
||||
reserved = reserved or Mail.Reserved()
|
||||
local stacks = Mail.BagStacks()
|
||||
local picks = {}
|
||||
for _, id in ipairs(entry.ids or {}) do
|
||||
if not reserved[id] then
|
||||
for _, s in ipairs(stacks) do
|
||||
if s.id == id and not s.locked then picks[#picks + 1] = { id = id, count = s.count } end
|
||||
end
|
||||
end
|
||||
end
|
||||
local surplus = 0
|
||||
if entry.gold then
|
||||
local have = (GetMoney and GetMoney()) or 0
|
||||
surplus = have - (entry.keep or 0) - (#picks + 1) * POSTAGE
|
||||
if surplus < 100 then surplus = 0 end
|
||||
end
|
||||
for _, p in ipairs(picks) do jobs[#jobs + 1] = { to = entry.name, item = p } end
|
||||
if surplus > 0 then jobs[#jobs + 1] = { to = entry.name, money = surplus } end
|
||||
local n, stamp = #jobs, Stamp()
|
||||
for i, job in ipairs(jobs) do
|
||||
job.subject = n > 1 and string.format(FW.L.MAIL_SUBJECT_N, stamp, i, n) or string.format(FW.L.MAIL_SUBJECT_STAMP, stamp)
|
||||
job.body = ""
|
||||
end
|
||||
return jobs
|
||||
end
|
||||
|
||||
function Mail:ListFor(name)
|
||||
for _, e in ipairs(self.db.lists) do
|
||||
if e.name:lower() == (name or ""):lower() then return e end
|
||||
end
|
||||
end
|
||||
|
||||
function Mail:AddList(name)
|
||||
name = Mail.CleanName(name)
|
||||
if name == "" or name:find("[^%w\128-\255%-]") then return false, "invalid" end
|
||||
if #self.db.lists >= MAX_LISTS then return false, "full" end
|
||||
if self:ListFor(name) then return false, "exists" end
|
||||
self.db.lists[#self.db.lists + 1] = { name = name, ids = {}, auto = false, gold = false, keep = 0 }
|
||||
FW:Fire("MAIL_LISTS_CHANGED")
|
||||
return true
|
||||
end
|
||||
|
||||
function Mail:RemoveList(entry)
|
||||
for i, e in ipairs(self.db.lists) do
|
||||
if e == entry then table.remove(self.db.lists, i) break end
|
||||
end
|
||||
FW:Fire("MAIL_LISTS_CHANGED")
|
||||
end
|
||||
|
||||
function Mail:ToggleListItem(entry, id)
|
||||
for i, v in ipairs(entry.ids) do
|
||||
if v == id then table.remove(entry.ids, i) FW:Fire("MAIL_LISTS_CHANGED") return false end
|
||||
end
|
||||
if #entry.ids >= MAX_ITEMS_PER_LIST then return nil end
|
||||
entry.ids[#entry.ids + 1] = id
|
||||
FW:Fire("MAIL_LISTS_CHANGED")
|
||||
return true
|
||||
end
|
||||
|
||||
function Mail:HasListItem(entry, id)
|
||||
for _, v in ipairs(entry.ids) do
|
||||
if v == id then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Eine Liste (entry) oder alle (entry = nil) jetzt senden. auto: nur Listen mit Haken
|
||||
function Mail:SendLists(entry, autoOnly)
|
||||
local reserved = Mail.Reserved()
|
||||
local jobs = {}
|
||||
for _, e in ipairs(self.db.lists) do
|
||||
if (entry == nil or entry == e) and (not autoOnly or e.auto) then
|
||||
local part = self:BuildList(e, reserved)
|
||||
if #part == 0 and entry == e then Say(string.format(FW.L.MAIL_SEND_NOTHING_LIST, e.name)) end
|
||||
for _, j in ipairs(part) do jobs[#jobs + 1] = j end
|
||||
end
|
||||
end
|
||||
if #jobs == 0 then return false, "empty" end
|
||||
return self:StartSend(jobs)
|
||||
end
|
||||
|
||||
-- Automatik: kurz nach dem Oeffnen, aber erst wenn Brett-Auftraege und andere Aktionen fertig sind
|
||||
function Mail:ScheduleAuto()
|
||||
local any
|
||||
for _, e in ipairs(self.db.lists) do if e.auto then any = true end end
|
||||
if not any then return end
|
||||
local gen, tries, told = self.gen, 0, false
|
||||
local function Try()
|
||||
if not Mail.open or Mail.gen ~= gen then return end
|
||||
if Mail.busy or Mail.BoardBusy() then
|
||||
tries = tries + 1
|
||||
if Mail.BoardBusy() and not told then
|
||||
told = true
|
||||
Say(FW.L.MAIL_AUTO_WAIT_BOARD)
|
||||
end
|
||||
if tries < 60 then After(1.5, Try) end
|
||||
return
|
||||
end
|
||||
Mail:SendLists(nil, true)
|
||||
end
|
||||
After(AUTO_DELAY, Try)
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Normales Fenster ausblenden (es bleibt technisch offen, damit Brett-Auftraege weiter funktionieren)
|
||||
------------------------------------------------------------------------
|
||||
|
||||
function Mail.Replacing()
|
||||
return Mail.db and Mail.db.settings.replace and true or false
|
||||
end
|
||||
|
||||
function Mail.Anchor()
|
||||
local w = Mail.window
|
||||
if Mail.Replacing() and w and w:IsShown() then return w end
|
||||
return nil
|
||||
end
|
||||
|
||||
function Mail:ApplyReplace()
|
||||
local f = _G.MailFrame
|
||||
if not f then return end
|
||||
if self.Replacing() and not self.uiFailed then
|
||||
if not self.hidden then
|
||||
self.hidden = { alpha = f:GetAlpha(), scale = f:GetScale() }
|
||||
end
|
||||
f:SetAlpha(0)
|
||||
f:SetScale(0.01)
|
||||
elseif self.hidden then
|
||||
f:SetAlpha(self.hidden.alpha or 1)
|
||||
f:SetScale(self.hidden.scale or 1)
|
||||
self.hidden = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Mail:SetReplace(on)
|
||||
self.db.settings.replace = on and true or false
|
||||
self.uiFailed = nil
|
||||
self:ApplyReplace()
|
||||
FW:Fire("MAIL_REPLACE_CHANGED")
|
||||
if self.open then
|
||||
FW:Fire(on and "MAIL_WINDOW_SHOW" or "MAIL_WINDOW_HIDE")
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Ereignisse
|
||||
------------------------------------------------------------------------
|
||||
|
||||
function Mail:OnMailShow()
|
||||
self.open = true
|
||||
self.gen = self.gen + 1
|
||||
self.selected = {}
|
||||
self:ApplyReplace()
|
||||
Trace("Briefkasten geoeffnet")
|
||||
if self.Replacing() then FW:Fire("MAIL_WINDOW_SHOW") end
|
||||
FW:Fire("MAIL_INBOX_CHANGED")
|
||||
self:ScheduleAuto()
|
||||
end
|
||||
|
||||
function Mail:OnMailClosed()
|
||||
if not self.open then return end
|
||||
self.open = false
|
||||
if self.busy == "inbox" then FinishOpen(self, "closed") end
|
||||
if self.busy == "send" then self:FinishSend("closed") end
|
||||
self.busy = nil
|
||||
FW:Fire("MAIL_WINDOW_HIDE")
|
||||
end
|
||||
|
||||
function Mail:OnInboxUpdate()
|
||||
if self.run then self.run.updated = true end
|
||||
FW:Fire("MAIL_INBOX_CHANGED")
|
||||
end
|
||||
|
||||
function Mail:OnSendSuccess()
|
||||
local s = self.send
|
||||
if self.lastTo then
|
||||
self:AddRecipient(self.lastTo)
|
||||
self.lastTo = nil
|
||||
end
|
||||
if s and s.stage == "wait" then
|
||||
s.ok = true
|
||||
s.sent = s.sent + 1
|
||||
FW:Fire("MAIL_STATE")
|
||||
end
|
||||
end
|
||||
|
||||
function Mail:OnSendFailed()
|
||||
local s = self.send
|
||||
if s and s.stage == "wait" then s.err = true end
|
||||
end
|
||||
|
||||
function Mail:OnEnable()
|
||||
self.driver = self.driver or CreateFrame("Frame")
|
||||
local acc = 0
|
||||
self.driver:SetScript("OnUpdate", function(_, elapsed)
|
||||
acc = acc + (elapsed or 0)
|
||||
if acc < STEP then return end
|
||||
acc = 0
|
||||
local ok, err = pcall(function()
|
||||
if Mail.busy == "inbox" and Mail.run then Mail:StepOpen()
|
||||
elseif Mail.busy == "send" and Mail.send then Mail:StepSend()
|
||||
else Mail.driver:Hide() end
|
||||
end)
|
||||
if not ok then
|
||||
Trace("Fehler im Takt: " .. tostring(err))
|
||||
Mail.run, Mail.send, Mail.busy = nil, nil, nil
|
||||
Mail.driver:Hide()
|
||||
FW:Fire("MAIL_STATE")
|
||||
end
|
||||
end)
|
||||
self.driver:Hide()
|
||||
FW:RegisterEvent("MAIL_SHOW", function() pcall(Mail.OnMailShow, Mail) end, self)
|
||||
FW:RegisterEvent("MAIL_CLOSED", function() pcall(Mail.OnMailClosed, Mail) end, self)
|
||||
FW:RegisterEvent("MAIL_INBOX_UPDATE", function() pcall(Mail.OnInboxUpdate, Mail) end, self)
|
||||
FW:RegisterEvent("MAIL_SEND_SUCCESS", function() pcall(Mail.OnSendSuccess, Mail) end, self)
|
||||
FW:RegisterEvent("MAIL_FAILED", function() pcall(Mail.OnSendFailed, Mail) end, self)
|
||||
if not self.hooked then
|
||||
self.hooked = true
|
||||
-- Empfaenger merken, egal wer sendet (auch das normale Fenster und Brett-Auftraege)
|
||||
if hooksecurefunc and _G.SendMail then
|
||||
pcall(hooksecurefunc, "SendMail", function(to)
|
||||
if Mail.db then Mail.lastTo = to end
|
||||
end)
|
||||
end
|
||||
if _G.MailFrame and MailFrame.HookScript then
|
||||
pcall(MailFrame.HookScript, MailFrame, "OnShow", function() pcall(Mail.ApplyReplace, Mail) end)
|
||||
-- das Ereignis MAIL_CLOSED kommt nicht immer; das Verschwinden des Fensters ist sicher
|
||||
pcall(MailFrame.HookScript, MailFrame, "OnHide", function() pcall(Mail.OnMailClosed, Mail) end)
|
||||
end
|
||||
end
|
||||
-- Briefkasten schon offen (z. B. nach /reload)?
|
||||
if _G.MailFrame and MailFrame.IsShown and MailFrame:IsShown() then
|
||||
After(0.5, function() pcall(Mail.OnMailShow, Mail) end)
|
||||
end
|
||||
end
|
||||
|
||||
function Mail:OnDisable()
|
||||
for _, ev in ipairs({ "MAIL_SHOW", "MAIL_CLOSED", "MAIL_INBOX_UPDATE", "MAIL_SEND_SUCCESS", "MAIL_FAILED" }) do
|
||||
FW:UnregisterEvent(ev, self)
|
||||
end
|
||||
if self.driver then self.driver:Hide() end
|
||||
self.run, self.send, self.busy = nil, nil, nil
|
||||
if self.hidden then
|
||||
local f = _G.MailFrame
|
||||
if f then f:SetAlpha(self.hidden.alpha or 1) f:SetScale(self.hidden.scale or 1) end
|
||||
self.hidden = nil
|
||||
end
|
||||
end
|
||||
+845
@@ -0,0 +1,845 @@
|
||||
-- Freewood-OctoWoW-Mail: eigenes Briefkasten-Fenster (Freewood-Design, verschiebbar).
|
||||
-- Es geht nur auf, wenn im Spiel ein Briefkasten geoeffnet wird, und nur, wenn es in den Einstellungen so gewaehlt ist.
|
||||
-- Reiter: Posteingang | Senden | Mailinglisten.
|
||||
|
||||
local ADDON, NS = ...
|
||||
|
||||
local FW = _G.Freewood
|
||||
local Mail = NS.Mail
|
||||
if not FW or not Mail then
|
||||
return
|
||||
end
|
||||
|
||||
local UI = FW.UI
|
||||
local Util = FW.Util
|
||||
|
||||
local W, H = 660, 560
|
||||
local ROW_H, ROW_GAP = 40, 4
|
||||
local OWNER = {}
|
||||
|
||||
local window, content, footer, tabs
|
||||
local current = "inbox"
|
||||
local panes = {}
|
||||
local picks = {} -- Senden-Reiter: gewaehlte Staepel { id, count, key }
|
||||
local picker
|
||||
|
||||
local function T(key) return FW.L[key] or key end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Bausteine
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local function ColoredName(name, quality, id)
|
||||
return "|cff" .. UI.RarityHex(quality) .. (name or ("#" .. tostring(id))) .. "|r"
|
||||
end
|
||||
|
||||
local function MakeEdit(parent, width, hintText, maxLetters, numeric)
|
||||
local box = UI.CreatePanel(parent, "bgDeep", "bronze")
|
||||
box:SetSize(width, 26)
|
||||
local edit = CreateFrame("EditBox", nil, box)
|
||||
edit:SetAutoFocus(false)
|
||||
edit:SetPoint("TOPLEFT", box, "TOPLEFT", 8, -2)
|
||||
edit:SetPoint("BOTTOMRIGHT", box, "BOTTOMRIGHT", -8, 2)
|
||||
edit:SetMaxLetters(maxLetters or 100)
|
||||
if numeric and edit.SetNumeric then pcall(edit.SetNumeric, edit, true) end
|
||||
if not edit:SetFont(UI.GetFontPath("body"), 12, "") then
|
||||
edit:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
|
||||
end
|
||||
edit:SetTextColor(UI.Color("text"))
|
||||
edit:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
|
||||
edit:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)
|
||||
if hintText then
|
||||
box.hint = UI.CreateLabel(box, hintText, { size = 12, color = "textDim" })
|
||||
box.hint:SetPoint("LEFT", box, "LEFT", 10, 0)
|
||||
end
|
||||
edit:SetScript("OnTextChanged", function(self)
|
||||
if box.hint then
|
||||
if self:GetText() == "" then box.hint:Show() else box.hint:Hide() end
|
||||
end
|
||||
if box.onChange and not box.loading then box.onChange(self:GetText()) end
|
||||
end)
|
||||
box.edit = edit
|
||||
function box:Set(text)
|
||||
self.loading = true
|
||||
edit:SetText(text or "")
|
||||
self.loading = nil
|
||||
if self.hint then
|
||||
if (text or "") == "" then self.hint:Show() else self.hint:Hide() end
|
||||
end
|
||||
end
|
||||
return box, edit
|
||||
end
|
||||
|
||||
local function Tooltip(frame, itemID, inboxIndex)
|
||||
frame:EnableMouse(true)
|
||||
frame:SetScript("OnEnter", function(self)
|
||||
if not GameTooltip then return end
|
||||
local idx = self.inboxIndex
|
||||
local id = self.itemID
|
||||
if idx and GameTooltip.SetInboxItem then
|
||||
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
||||
pcall(GameTooltip.SetInboxItem, GameTooltip, idx)
|
||||
GameTooltip:Show()
|
||||
elseif id then
|
||||
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
||||
pcall(GameTooltip.SetItemByID, GameTooltip, id)
|
||||
GameTooltip:Show()
|
||||
end
|
||||
end)
|
||||
frame:SetScript("OnLeave", function()
|
||||
if GameTooltip then GameTooltip:Hide() end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Zeile mit Symbol und Namen
|
||||
local function ItemRow(parent, h)
|
||||
local row = UI.CreatePanel(parent, "card", "bronze")
|
||||
row:SetHeight(h or 32)
|
||||
row.icon = row:CreateTexture(nil, "ARTWORK")
|
||||
row.icon:SetSize(24, 24)
|
||||
row.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
||||
row.name = UI.CreateLabel(row, "", { size = 12 })
|
||||
row.name:SetWordWrap(false)
|
||||
Tooltip(row)
|
||||
return row
|
||||
end
|
||||
|
||||
local function FillItem(row, id, count)
|
||||
row.itemID = id
|
||||
local suffix = (count and count > 1) and (" x" .. count) or ""
|
||||
row.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
|
||||
row.name:SetText(ColoredName(nil, 1, id) .. suffix)
|
||||
Util.RequestItem(id, function(info)
|
||||
if row.itemID ~= id or not info then return end
|
||||
row.icon:SetTexture(info.icon)
|
||||
row.name:SetText(ColoredName(info.name, info.quality, id) .. suffix)
|
||||
end)
|
||||
end
|
||||
|
||||
local function Heading(parent, text, x, y)
|
||||
local h = UI.CreateLabel(parent, text, { font = "heading", size = 13, color = "goldLight" })
|
||||
h:SetPoint("TOPLEFT", parent, "TOPLEFT", x or 0, y or 0)
|
||||
return h
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Auswahlfenster fuer Items
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local function BuildPicker()
|
||||
local p = UI.CreatePanel(window, "bgDeep", "gold")
|
||||
p:SetSize(460, 440)
|
||||
p:SetPoint("CENTER", window, "CENTER", 0, 0)
|
||||
p:SetFrameStrata("DIALOG")
|
||||
p:EnableMouse(true)
|
||||
p.title = UI.CreateLabel(p, "", { font = "heading", size = 15, color = "goldLight" })
|
||||
p.title:SetPoint("TOPLEFT", p, "TOPLEFT", 16, -14)
|
||||
p.hint = UI.CreateLabel(p, "", { size = 11, color = "textDim", justifyV = "TOP" })
|
||||
p.hint:SetPoint("TOPLEFT", p, "TOPLEFT", 16, -38)
|
||||
p.hint:SetPoint("TOPRIGHT", p, "TOPRIGHT", -16, -38)
|
||||
p.hint:SetHeight(28)
|
||||
p.hint:SetWordWrap(true)
|
||||
p.scroll = UI.CreateScrollFrame(p)
|
||||
p.scroll:SetPoint("TOPLEFT", p, "TOPLEFT", 16, -72)
|
||||
p.scroll:SetPoint("BOTTOMRIGHT", p, "BOTTOMRIGHT", -16, 52)
|
||||
p.empty = UI.CreateLabel(p.scroll.child, T("MAIL_PICK_EMPTY"), { size = 12, color = "textDim" })
|
||||
p.empty:SetPoint("TOPLEFT", p.scroll.child, "TOPLEFT", 4, -6)
|
||||
p.done = UI.CreateButton(p, T("MAIL_PICK_DONE"), 140, 28, function() p:Hide() end, "primary")
|
||||
p.done:SetPoint("BOTTOM", p, "BOTTOM", 0, 14)
|
||||
p.rows = {}
|
||||
p:Hide()
|
||||
|
||||
local function Row(i)
|
||||
local row = p.rows[i]
|
||||
if row then return row end
|
||||
row = ItemRow(p.scroll.child, 32)
|
||||
row.cb = UI.CreateCheckbox(row, "", function() return row.checked end, function(v)
|
||||
if row.onToggle then row.onToggle(v) end
|
||||
end)
|
||||
row.cb:SetPoint("LEFT", row, "LEFT", 6, 0)
|
||||
row.cb:SetWidth(22)
|
||||
row.icon:SetPoint("LEFT", row, "LEFT", 34, 0)
|
||||
row.name:SetPoint("LEFT", row.icon, "RIGHT", 8, 0)
|
||||
row.name:SetPoint("RIGHT", row, "RIGHT", -8, 0)
|
||||
p.rows[i] = row
|
||||
return row
|
||||
end
|
||||
|
||||
function p:Fill()
|
||||
local data = {}
|
||||
local stacks = Mail.BagStacks()
|
||||
if self.mode == "list" then
|
||||
local agg, order = {}, {}
|
||||
for _, s in ipairs(stacks) do
|
||||
local a = agg[s.id]
|
||||
if not a then
|
||||
a = { id = s.id, count = 0 }
|
||||
agg[s.id] = a
|
||||
order[#order + 1] = a
|
||||
end
|
||||
a.count = a.count + s.count
|
||||
end
|
||||
table.sort(order, function(a, b) return a.id < b.id end)
|
||||
for _, a in ipairs(order) do data[#data + 1] = a end
|
||||
local extraStart = #data + 1
|
||||
for _, id in ipairs(self.entry.ids) do
|
||||
if not agg[id] then data[#data + 1] = { id = id, count = 0, extra = true } end
|
||||
end
|
||||
self.extraStart = extraStart
|
||||
else
|
||||
local seen = {}
|
||||
for _, s in ipairs(stacks) do
|
||||
if not s.locked then
|
||||
local base = s.id .. ":" .. s.count
|
||||
seen[base] = (seen[base] or 0) + 1
|
||||
data[#data + 1] = { id = s.id, count = s.count, key = base .. ":" .. seen[base] }
|
||||
end
|
||||
end
|
||||
end
|
||||
for i, d in ipairs(data) do local d = d
|
||||
local row = Row(i)
|
||||
row:ClearAllPoints()
|
||||
row:SetPoint("TOPLEFT", p.scroll.child, "TOPLEFT", 0, -((i - 1) * 36))
|
||||
row:SetPoint("TOPRIGHT", p.scroll.child, "TOPRIGHT", 0, -((i - 1) * 36))
|
||||
FillItem(row, d.id, d.count)
|
||||
if d.extra then row.name:SetText(row.name:GetText() .. " |cff8a8f9a(" .. T("MAIL_PICK_EXTRA") .. ")|r") end
|
||||
if self.mode == "list" then
|
||||
row.checked = Mail:HasListItem(self.entry, d.id)
|
||||
row.onToggle = function()
|
||||
local r = Mail:ToggleListItem(self.entry, d.id)
|
||||
row.checked = (r == true) or (r == nil and false)
|
||||
row.cb:Refresh()
|
||||
if self.onChange then self.onChange() end
|
||||
end
|
||||
else
|
||||
local function Index()
|
||||
for k, pk in ipairs(picks) do if pk.key == d.key then return k end end
|
||||
end
|
||||
row.checked = Index() and true or false
|
||||
row.onToggle = function()
|
||||
local k = Index()
|
||||
if k then
|
||||
table.remove(picks, k)
|
||||
row.checked = false
|
||||
else
|
||||
picks[#picks + 1] = { id = d.id, count = d.count, key = d.key }
|
||||
row.checked = true
|
||||
end
|
||||
row.cb:Refresh()
|
||||
if self.onChange then self.onChange() end
|
||||
end
|
||||
end
|
||||
row.cb:Refresh()
|
||||
row:Show()
|
||||
end
|
||||
for i = #data + 1, #p.rows do p.rows[i]:Hide() end
|
||||
self.empty:SetShown(#data == 0)
|
||||
self.scroll:SetContentHeight(#data * 36 + 4)
|
||||
end
|
||||
|
||||
return p
|
||||
end
|
||||
|
||||
-- mode "list": entry = Eintrag der Mailingliste; mode "send": Auswahl des Senden-Reiters
|
||||
local function OpenPicker(mode, entry, onChange)
|
||||
picker = picker or BuildPicker()
|
||||
picker.mode, picker.entry, picker.onChange = mode, entry, onChange
|
||||
if mode == "list" then
|
||||
picker.title:SetText(string.format(T("MAIL_PICK_LIST_TITLE"), entry.name))
|
||||
picker.hint:SetText(T("MAIL_PICK_HINT_LIST"))
|
||||
else
|
||||
picker.title:SetText(T("MAIL_PICK_TITLE"))
|
||||
picker.hint:SetText(T("MAIL_PICK_HINT_SEND"))
|
||||
end
|
||||
picker:Show()
|
||||
picker:Raise()
|
||||
picker:Fill()
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Reiter: Posteingang
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local function BuildInbox(parent)
|
||||
local f = CreateFrame("Frame", nil, parent)
|
||||
local rows = {}
|
||||
|
||||
f.info = UI.CreateLabel(f, "", { size = 12, color = "textDim" })
|
||||
f.info:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -6)
|
||||
|
||||
f.none = UI.CreateButton(f, T("MAIL_SELECT_NONE"), 130, 24, function() Mail:SelectAll(false) end, "default")
|
||||
f.none:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, 0)
|
||||
f.all = UI.CreateButton(f, T("MAIL_SELECT_ALL"), 130, 24, function() Mail:SelectAll(true) end, "default")
|
||||
f.all:SetPoint("RIGHT", f.none, "LEFT", -6, 0)
|
||||
|
||||
f.open = UI.CreateButton(f, T("MAIL_OPEN_ALL"), 280, 30, function()
|
||||
Mail:StartOpen(Mail:SelectedCount() > 0)
|
||||
end, "primary")
|
||||
f.open:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -32)
|
||||
f.stop = UI.CreateButton(f, T("MAIL_STOP"), 110, 30, function() Mail:Stop() end, "danger")
|
||||
f.stop:SetPoint("LEFT", f.open, "RIGHT", 8, 0)
|
||||
|
||||
f.note = UI.CreateLabel(f, T("MAIL_NOTE_SAFE"), { size = 11, color = "textDim", justifyV = "TOP" })
|
||||
f.note:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 0, 0)
|
||||
f.note:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
|
||||
f.note:SetHeight(30)
|
||||
f.note:SetWordWrap(true)
|
||||
|
||||
f.scroll = UI.CreateScrollFrame(f)
|
||||
f.scroll:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -70)
|
||||
f.scroll:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 36)
|
||||
f.empty = UI.CreateLabel(f.scroll.child, T("MAIL_INBOX_EMPTY"), { size = 12, color = "textDim" })
|
||||
f.empty:SetPoint("TOPLEFT", f.scroll.child, "TOPLEFT", 4, -6)
|
||||
|
||||
local function Row(i)
|
||||
local row = rows[i]
|
||||
if row then return row end
|
||||
row = UI.CreatePanel(f.scroll.child, "card", "bronze")
|
||||
row:SetHeight(ROW_H)
|
||||
row.cb = UI.CreateCheckbox(row, "", function() return row.mail and Mail.selected[row.mail.key] end, function(v)
|
||||
if row.mail then Mail:ToggleSelect(row.mail.key, v) end
|
||||
end)
|
||||
row.cb:SetPoint("LEFT", row, "LEFT", 8, 0)
|
||||
row.cb:SetWidth(22)
|
||||
row.icon = row:CreateTexture(nil, "ARTWORK")
|
||||
row.icon:SetSize(28, 28)
|
||||
row.icon:SetPoint("LEFT", row, "LEFT", 38, 0)
|
||||
row.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
||||
row.l1 = UI.CreateLabel(row, "", { size = 12 })
|
||||
row.l1:SetPoint("TOPLEFT", row, "TOPLEFT", 76, -6)
|
||||
row.l1:SetPoint("RIGHT", row, "RIGHT", -60, 0)
|
||||
row.l1:SetWordWrap(false)
|
||||
row.l2 = UI.CreateLabel(row, "", { size = 11, color = "textDim" })
|
||||
row.l2:SetPoint("TOPLEFT", row, "TOPLEFT", 76, -22)
|
||||
row.l2:SetPoint("RIGHT", row, "RIGHT", -60, 0)
|
||||
row.l2:SetWordWrap(false)
|
||||
row.days = UI.CreateLabel(row, "", { size = 11, color = "textDim", justify = "RIGHT" })
|
||||
row.days:SetPoint("RIGHT", row, "RIGHT", -10, 0)
|
||||
Tooltip(row)
|
||||
rows[i] = row
|
||||
return row
|
||||
end
|
||||
|
||||
function f.Refresh()
|
||||
local list = Mail.ReadInbox()
|
||||
local busy = Mail.busy
|
||||
local takeable = 0
|
||||
for i, m in ipairs(list) do local m = m
|
||||
local row = Row(i)
|
||||
row.mail = m
|
||||
row.inboxIndex = m.hasItem and m.i or nil
|
||||
row:ClearAllPoints()
|
||||
row:SetPoint("TOPLEFT", f.scroll.child, "TOPLEFT", 0, -((i - 1) * (ROW_H + ROW_GAP)))
|
||||
row:SetPoint("TOPRIGHT", f.scroll.child, "TOPRIGHT", 0, -((i - 1) * (ROW_H + ROW_GAP)))
|
||||
if m.itemTex then
|
||||
row.icon:SetTexture(m.itemTex)
|
||||
elseif m.money > 0 then
|
||||
row.icon:SetTexture("Interface\\Icons\\INV_Misc_Coin_01")
|
||||
else
|
||||
row.icon:SetTexture("Interface\\Icons\\INV_Misc_Note_01")
|
||||
end
|
||||
row.l1:SetText(m.subject ~= "" and m.subject or m.sender)
|
||||
local parts = {}
|
||||
if m.hasItem then parts[#parts + 1] = (m.itemName or "?") .. ((m.itemCount or 1) > 1 and (" x" .. m.itemCount) or "") end
|
||||
if m.money > 0 then parts[#parts + 1] = string.format(T("MAIL_ROW_GOLD"), Mail.Money(m.money)) end
|
||||
local text
|
||||
if m.cod > 0 then
|
||||
text = "|cffff7060" .. string.format(T("MAIL_ROW_COD"), Mail.Money(m.cod)) .. "|r"
|
||||
elseif #parts == 0 then
|
||||
text = T("MAIL_ROW_LETTER")
|
||||
else
|
||||
text = table.concat(parts, " + ")
|
||||
end
|
||||
row.l2:SetText(string.format(T("MAIL_ROW_FROM"), m.sender) .. " " .. text)
|
||||
row.days:SetText(m.days and string.format(T("MAIL_ROW_DAYS"), math.floor(m.days)) or "")
|
||||
row.cb:SetShown(m.take)
|
||||
row:SetAlpha(m.take and 1 or 0.55)
|
||||
row.cb:Refresh()
|
||||
if m.take then takeable = takeable + 1 end
|
||||
row:Show()
|
||||
end
|
||||
for i = #list + 1, #rows do rows[i].mail = nil rows[i]:Hide() end
|
||||
f.empty:SetShown(#list == 0)
|
||||
f.scroll:SetContentHeight(#list * (ROW_H + ROW_GAP) + 4)
|
||||
f.info:SetText(string.format(T("MAIL_INBOX_COUNT"), #list))
|
||||
|
||||
local sel = Mail:SelectedCount()
|
||||
f.open:SetLabel(sel > 0 and string.format(T("MAIL_OPEN_SELECTED"), sel) or T("MAIL_OPEN_ALL"))
|
||||
f.open:SetButtonEnabled(Mail.open and not busy and (sel > 0 or takeable > 0))
|
||||
f.stop:SetShown(busy ~= nil)
|
||||
f.all:SetButtonEnabled(not busy and takeable > 0)
|
||||
f.none:SetButtonEnabled(not busy and sel > 0)
|
||||
end
|
||||
|
||||
return f
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Reiter: Senden
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local function BuildSend(parent)
|
||||
local f = CreateFrame("Frame", nil, parent)
|
||||
local rows = {}
|
||||
local cod = false
|
||||
|
||||
Heading(f, T("MAIL_TO"), 0, 0)
|
||||
local toBox, toEdit = MakeEdit(f, 300, nil, 24)
|
||||
toBox:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -20)
|
||||
local dd
|
||||
dd = UI.CreateDropdown(f, 170, 26, {
|
||||
allText = T("MAIL_TO_RECENT"),
|
||||
getOptions = function()
|
||||
local o = {}
|
||||
for _, n in ipairs(Mail:Recipients()) do o[#o + 1] = { value = n, text = n } end
|
||||
return o
|
||||
end,
|
||||
onChange = function(v)
|
||||
if v then toBox:Set(v) end
|
||||
dd:SetValue(nil)
|
||||
end,
|
||||
})
|
||||
dd:SetPoint("LEFT", toBox, "RIGHT", 8, 0)
|
||||
|
||||
Heading(f, T("MAIL_SUBJECT"), 0, -54)
|
||||
local subjBox = MakeEdit(f, 628, nil, 64)
|
||||
subjBox:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -74)
|
||||
|
||||
Heading(f, T("MAIL_BODY"), 0, -108)
|
||||
local body = UI.CreateTextArea(f, 628, 64)
|
||||
body:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -128)
|
||||
|
||||
-- Items
|
||||
Heading(f, T("MAIL_ITEMS"), 0, -202)
|
||||
local addBtn = UI.CreateButton(f, T("MAIL_ITEMS_ADD"), 170, 24, nil, "default")
|
||||
addBtn:SetPoint("TOPLEFT", f, "TOPLEFT", 120, -198)
|
||||
local itemScroll = UI.CreateScrollFrame(f)
|
||||
itemScroll:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -228)
|
||||
itemScroll:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -228)
|
||||
itemScroll:SetHeight(104)
|
||||
local itemsEmpty = UI.CreateLabel(itemScroll.child, T("MAIL_ITEMS_NONE"), { size = 12, color = "textDim" })
|
||||
itemsEmpty:SetPoint("TOPLEFT", itemScroll.child, "TOPLEFT", 4, -6)
|
||||
|
||||
-- Gold und Nachnahme
|
||||
local goldBox = MakeEdit(f, 90, T("MAIL_GOLD"), 7, true)
|
||||
goldBox:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -344)
|
||||
local silverBox = MakeEdit(f, 70, T("MAIL_SILVER"), 2, true)
|
||||
silverBox:SetPoint("LEFT", goldBox, "RIGHT", 8, 0)
|
||||
local copperBox = MakeEdit(f, 70, T("MAIL_COPPER"), 2, true)
|
||||
copperBox:SetPoint("LEFT", silverBox, "RIGHT", 8, 0)
|
||||
local codBox = UI.CreateCheckbox(f, T("MAIL_COD"), function() return cod end, function(v) cod = v and true or false end)
|
||||
codBox:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -376)
|
||||
codBox:SetWidth(620)
|
||||
|
||||
local status = UI.CreateLabel(f, "", { size = 12, color = "textDim", justify = "LEFT" })
|
||||
status:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 0, 6)
|
||||
status:SetPoint("RIGHT", f, "RIGHT", -160, 0)
|
||||
status:SetWordWrap(false)
|
||||
local send = UI.CreateButton(f, T("MAIL_SEND"), 150, 30, nil, "primary")
|
||||
send:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
|
||||
|
||||
local function Copper()
|
||||
return (tonumber(goldBox.edit:GetText()) or 0) * 10000 + (tonumber(silverBox.edit:GetText()) or 0) * 100 + (tonumber(copperBox.edit:GetText()) or 0)
|
||||
end
|
||||
|
||||
local function Form()
|
||||
return { to = toEdit:GetText(), subject = subjBox.edit:GetText(), body = body.edit:GetText(), picks = picks, money = Copper(), cod = cod }
|
||||
end
|
||||
|
||||
local function SetStatus(text, colorKey)
|
||||
status:SetText(text or "")
|
||||
status:SetTextColor(UI.Color(colorKey or "textDim"))
|
||||
end
|
||||
|
||||
function f.Refresh()
|
||||
for i, pk in ipairs(picks) do local pk, i = pk, i
|
||||
local row = rows[i]
|
||||
if not row then
|
||||
row = ItemRow(itemScroll.child, 30)
|
||||
row.icon:SetPoint("LEFT", row, "LEFT", 6, 0)
|
||||
row.name:SetPoint("LEFT", row.icon, "RIGHT", 8, 0)
|
||||
row.name:SetPoint("RIGHT", row, "RIGHT", -40, 0)
|
||||
row.remove = UI.CreateButton(row, "X", 26, 22, nil, "danger")
|
||||
row.remove:SetPoint("RIGHT", row, "RIGHT", -4, 0)
|
||||
rows[i] = row
|
||||
end
|
||||
row:ClearAllPoints()
|
||||
row:SetPoint("TOPLEFT", itemScroll.child, "TOPLEFT", 0, -((i - 1) * 34))
|
||||
row:SetPoint("TOPRIGHT", itemScroll.child, "TOPRIGHT", 0, -((i - 1) * 34))
|
||||
FillItem(row, pk.id, pk.count)
|
||||
row.remove:SetScript("OnClick", function()
|
||||
table.remove(picks, i)
|
||||
f.Refresh()
|
||||
end)
|
||||
row:Show()
|
||||
end
|
||||
for i = #picks + 1, #rows do rows[i]:Hide() end
|
||||
itemsEmpty:SetShown(#picks == 0)
|
||||
itemScroll:SetContentHeight(#picks * 34 + 4)
|
||||
codBox:Refresh()
|
||||
|
||||
local busy = Mail.busy
|
||||
local boardBusy = Mail.BoardBusy()
|
||||
local jobs, err = Mail:BuildCompose(Form())
|
||||
if boardBusy then
|
||||
SetStatus(T("MAIL_BOARD_BUSY"), "goldLight")
|
||||
elseif busy then
|
||||
SetStatus(T("MAIL_BUSY"), "textDim")
|
||||
elseif jobs then
|
||||
SetStatus(#jobs == 1 and T("MAIL_SEND_COUNT1") or string.format(T("MAIL_SEND_COUNT"), #jobs), "textDim")
|
||||
elseif err == "to" then SetStatus(T("MAIL_SEND_NO_TO"), "textDim")
|
||||
elseif err == "self" then SetStatus(T("MAIL_SEND_SELF"), "danger")
|
||||
elseif err == "codItem" then SetStatus(T("MAIL_SEND_COD_NEEDS_ITEM"), "danger")
|
||||
else SetStatus(T("MAIL_SEND_NOTHING"), "textDim") end
|
||||
send:SetButtonEnabled(jobs ~= nil and Mail.open and not busy and not boardBusy)
|
||||
end
|
||||
|
||||
for _, b in ipairs({ goldBox, silverBox, copperBox, subjBox }) do b.onChange = function() if f:IsShown() then f.Refresh() end end end
|
||||
toBox.onChange = function() if f:IsShown() then f.Refresh() end end
|
||||
addBtn:SetScript("OnClick", function() OpenPicker("send", nil, f.Refresh) end)
|
||||
send:SetScript("OnClick", function()
|
||||
local jobs = Mail:BuildCompose(Form())
|
||||
if not jobs then return f.Refresh() end
|
||||
local ok = Mail:StartSend(jobs)
|
||||
if ok then f.pendingClear = #jobs end
|
||||
f.Refresh()
|
||||
end)
|
||||
FW:On("MAIL_SENT_DONE", function(_, sent, total)
|
||||
if f.pendingClear and sent >= f.pendingClear then
|
||||
for i = #picks, 1, -1 do table.remove(picks, i) end
|
||||
subjBox:Set("")
|
||||
body.edit:SetText("")
|
||||
goldBox:Set("") silverBox:Set("") copperBox:Set("")
|
||||
cod = false
|
||||
end
|
||||
f.pendingClear = nil
|
||||
if f:IsShown() then f.Refresh() end
|
||||
end, OWNER)
|
||||
FW:On("MAIL_RECIPIENTS_CHANGED", function() end, OWNER)
|
||||
|
||||
-- Brett-Auftraege melden sich nicht bei uns; das Fenster fragt deshalb im Takt nach
|
||||
local acc = 0
|
||||
f:SetScript("OnUpdate", function(_, elapsed)
|
||||
acc = acc + (elapsed or 0)
|
||||
if acc > 0.5 then
|
||||
acc = 0
|
||||
f.Refresh()
|
||||
end
|
||||
end)
|
||||
|
||||
return f
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Reiter: Mailinglisten
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local LIST_ROW_H = 96
|
||||
|
||||
local function BuildLists(parent)
|
||||
local f = CreateFrame("Frame", nil, parent)
|
||||
local rows = {}
|
||||
|
||||
local nameBox, nameEdit = MakeEdit(f, 220, T("MAIL_LISTS_NAME_BOX"), 24)
|
||||
nameBox:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
|
||||
local status = UI.CreateLabel(f, "", { size = 11, color = "textDim" })
|
||||
local function Add()
|
||||
local ok, why = Mail:AddList(nameEdit:GetText())
|
||||
if ok then
|
||||
nameBox:Set("")
|
||||
nameEdit:ClearFocus()
|
||||
status:SetText("")
|
||||
else
|
||||
status:SetText(why == "exists" and "Name exists." or (why == "full" and "List is full." or "Invalid name."))
|
||||
end
|
||||
end
|
||||
local addBtn = UI.CreateButton(f, T("MAIL_LISTS_ADD"), 110, 26, Add, "primary")
|
||||
addBtn:SetPoint("LEFT", nameBox, "RIGHT", 8, 0)
|
||||
nameEdit:SetScript("OnEnterPressed", Add)
|
||||
status:SetPoint("LEFT", addBtn, "RIGHT", 10, 0)
|
||||
local allBtn = UI.CreateButton(f, T("MAIL_LISTS_ALL_NOW"), 190, 26, function() Mail:SendLists(nil, false) end, "default")
|
||||
allBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, 0)
|
||||
|
||||
local hint = UI.CreateLabel(f, T("MAIL_LISTS_HINT"), { size = 11, color = "textDim", justifyV = "TOP" })
|
||||
hint:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -34)
|
||||
hint:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -34)
|
||||
hint:SetHeight(28)
|
||||
hint:SetWordWrap(true)
|
||||
|
||||
local reserved = UI.CreateLabel(f, "", { size = 11, color = "goldLight" })
|
||||
reserved:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 0, 2)
|
||||
|
||||
local scroll = UI.CreateScrollFrame(f)
|
||||
scroll:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -66)
|
||||
scroll:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 24)
|
||||
local empty = UI.CreateLabel(scroll.child, T("MAIL_LISTS_EMPTY"), { size = 12, color = "textDim" })
|
||||
empty:SetPoint("TOPLEFT", scroll.child, "TOPLEFT", 4, -6)
|
||||
|
||||
local function Row(i)
|
||||
local row = rows[i]
|
||||
if row then return row end
|
||||
row = UI.CreatePanel(scroll.child, "card", "bronze")
|
||||
row:SetHeight(LIST_ROW_H)
|
||||
row.name = UI.CreateLabel(row, "", { font = "heading", size = 14, color = "goldLight" })
|
||||
row.name:SetPoint("TOPLEFT", row, "TOPLEFT", 12, -12)
|
||||
row.items = UI.CreateButton(row, "", 130, 24, function()
|
||||
if row.entry then OpenPicker("list", row.entry, function() f.Refresh() end) end
|
||||
end, "default")
|
||||
row.items:SetPoint("TOPLEFT", row, "TOPLEFT", 190, -8)
|
||||
row.now = UI.CreateButton(row, T("MAIL_LISTS_NOW"), 120, 24, function()
|
||||
if row.entry then Mail:SendLists(row.entry, false) end
|
||||
end, "default")
|
||||
row.now:SetPoint("LEFT", row.items, "RIGHT", 8, 0)
|
||||
row.remove = UI.CreateButton(row, "X", 26, 24, function()
|
||||
if row.entry then Mail:RemoveList(row.entry) end
|
||||
end, "danger")
|
||||
row.remove:SetPoint("TOPRIGHT", row, "TOPRIGHT", -8, -8)
|
||||
UI.AttachTooltip(row.remove, function() return T("MAIL_LISTS_REMOVE") end, "")
|
||||
row.selfNote = UI.CreateLabel(row, T("MAIL_LISTS_SELF"), { size = 11, color = "textDim" })
|
||||
row.selfNote:SetPoint("LEFT", row.items, "RIGHT", 8, 0)
|
||||
|
||||
row.auto = UI.CreateCheckbox(row, T("MAIL_LISTS_AUTO"), function() return row.entry and row.entry.auto end, function(v)
|
||||
if row.entry then row.entry.auto = v and true or false end
|
||||
end)
|
||||
row.auto:SetPoint("TOPLEFT", row, "TOPLEFT", 12, -40)
|
||||
row.auto:SetWidth(420)
|
||||
row.gold = UI.CreateCheckbox(row, T("MAIL_LISTS_GOLD"), function() return row.entry and row.entry.gold end, function(v)
|
||||
if row.entry then row.entry.gold = v and true or false end
|
||||
end)
|
||||
row.gold:SetPoint("TOPLEFT", row, "TOPLEFT", 12, -66)
|
||||
row.gold:SetWidth(170)
|
||||
row.keepBox = MakeEdit(row, 80, nil, 7, true)
|
||||
row.keepBox:SetPoint("TOPLEFT", row, "TOPLEFT", 186, -64)
|
||||
row.keepBox.onChange = function(text)
|
||||
if row.entry then row.entry.keep = math.max(0, math.floor(tonumber(text) or 0)) * 10000 end
|
||||
end
|
||||
row.keepLabel = UI.CreateLabel(row, T("MAIL_LISTS_GOLD_G"), { size = 12, color = "textDim" })
|
||||
row.keepLabel:SetPoint("LEFT", row.keepBox, "RIGHT", 8, 0)
|
||||
rows[i] = row
|
||||
return row
|
||||
end
|
||||
|
||||
function f.Refresh()
|
||||
local lists = Mail.db.lists
|
||||
for i, e in ipairs(lists) do
|
||||
local row = Row(i)
|
||||
row.entry = e
|
||||
row:ClearAllPoints()
|
||||
row:SetPoint("TOPLEFT", scroll.child, "TOPLEFT", 0, -((i - 1) * (LIST_ROW_H + ROW_GAP)))
|
||||
row:SetPoint("TOPRIGHT", scroll.child, "TOPRIGHT", 0, -((i - 1) * (LIST_ROW_H + ROW_GAP)))
|
||||
row.name:SetText(e.name)
|
||||
row.items:SetLabel(string.format(T("MAIL_LISTS_ITEMS"), #e.ids))
|
||||
local me = Mail.IsSelf(e.name)
|
||||
row.now:SetShown(not me)
|
||||
row.selfNote:SetShown(me and true or false)
|
||||
row.now:SetButtonEnabled(Mail.open and not Mail.busy and not Mail.BoardBusy())
|
||||
row.auto:Refresh()
|
||||
row.gold:Refresh()
|
||||
row.keepBox:Set(tostring(math.floor((e.keep or 0) / 10000)))
|
||||
row:Show()
|
||||
end
|
||||
for i = #lists + 1, #rows do rows[i].entry = nil rows[i]:Hide() end
|
||||
empty:SetShown(#lists == 0)
|
||||
scroll:SetContentHeight(#lists * (LIST_ROW_H + ROW_GAP) + 4)
|
||||
allBtn:SetButtonEnabled(#lists > 0 and Mail.open and not Mail.busy and not Mail.BoardBusy())
|
||||
local _, n = Mail.Reserved()
|
||||
reserved:SetText(n > 0 and string.format(T("MAIL_LISTS_RESERVED"), n) or "")
|
||||
end
|
||||
|
||||
return f
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Fenster
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local TAB_DEFS = {
|
||||
{ id = "inbox", label = "MAIL_TAB_INBOX", build = BuildInbox },
|
||||
{ id = "send", label = "MAIL_TAB_SEND", build = BuildSend },
|
||||
{ id = "lists", label = "MAIL_TAB_LISTS", build = BuildLists },
|
||||
}
|
||||
|
||||
local function SelectTab(id)
|
||||
current = id
|
||||
for _, def in ipairs(TAB_DEFS) do
|
||||
local pane = panes[def.id]
|
||||
if pane then
|
||||
if def.id == id then pane:Show() pane.Refresh() else pane:Hide() end
|
||||
end
|
||||
if tabs[def.id] then tabs[def.id]:SetActive(def.id == id) end
|
||||
end
|
||||
end
|
||||
|
||||
local function UpdateFooter()
|
||||
if not footer then return end
|
||||
local text = ""
|
||||
if Mail.busy == "inbox" and Mail.run then
|
||||
text = string.format(T("MAIL_RUN_PROGRESS"), Mail.run.items, Mail.run.money)
|
||||
elseif Mail.busy == "send" then
|
||||
local i, n, to = Mail:SendProgress()
|
||||
if i then text = string.format(T("MAIL_SEND_PROGRESS"), math.max(i, 1), n, to) end
|
||||
end
|
||||
footer:SetText(text)
|
||||
end
|
||||
|
||||
local function SavePosition()
|
||||
local left, top = window:GetLeft(), window:GetTop()
|
||||
if left and top then Mail.db.settings.pos = { x = left, y = top } end
|
||||
end
|
||||
|
||||
local function BuildWindow()
|
||||
local f = UI.CreatePanel(UIParent, "bgDeep", "gold")
|
||||
f:SetSize(W, H)
|
||||
f:SetFrameStrata("HIGH")
|
||||
f:SetClampedToScreen(true)
|
||||
f:EnableMouse(true)
|
||||
f:SetMovable(true)
|
||||
f:RegisterForDrag("LeftButton")
|
||||
f:SetScript("OnDragStart", function(self) self:StartMoving() end)
|
||||
f:SetScript("OnDragStop", function(self) self:StopMovingOrSizing() SavePosition() end)
|
||||
local pos = Mail.db.settings.pos
|
||||
f:ClearAllPoints()
|
||||
if pos and pos.x and pos.y then
|
||||
f:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", pos.x, pos.y)
|
||||
else
|
||||
f:SetPoint("CENTER", UIParent, "CENTER", -120, 40)
|
||||
end
|
||||
window = f
|
||||
Mail.window = f
|
||||
|
||||
local title = UI.CreateLabel(f, T("MAIL_WINDOW_TITLE"), { font = "heading", size = 16, color = "goldLight" })
|
||||
title:SetPoint("TOPLEFT", f, "TOPLEFT", 16, -12)
|
||||
local close = UI.CreateButton(f, "X", 26, 24, function()
|
||||
if CloseMail then pcall(CloseMail) end
|
||||
if _G.MailFrame and HideUIPanel and MailFrame:IsShown() then pcall(HideUIPanel, MailFrame) end
|
||||
f:Hide()
|
||||
end, "danger")
|
||||
close:SetPoint("TOPRIGHT", f, "TOPRIGHT", -10, -10)
|
||||
UI.AttachTooltip(close, function() return T("MAIL_CLOSE") end, "")
|
||||
local div = UI.CreateDivider(f)
|
||||
div:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -40)
|
||||
div:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -40)
|
||||
|
||||
tabs = {}
|
||||
local prev
|
||||
for _, def in ipairs(TAB_DEFS) do local def = def
|
||||
local b = UI.CreateTabButton(f, T(def.label))
|
||||
if prev then b:SetPoint("LEFT", prev, "RIGHT", 4, 0) else b:SetPoint("TOPLEFT", f, "TOPLEFT", 12, -44) end
|
||||
b:SetScript("OnClick", function() SelectTab(def.id) end)
|
||||
tabs[def.id] = b
|
||||
prev = b
|
||||
end
|
||||
|
||||
content = CreateFrame("Frame", nil, f)
|
||||
content:SetPoint("TOPLEFT", f, "TOPLEFT", 16, -86)
|
||||
content:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -16, 32)
|
||||
for _, def in ipairs(TAB_DEFS) do
|
||||
local pane = def.build(content)
|
||||
pane:SetAllPoints(content)
|
||||
pane:Hide()
|
||||
panes[def.id] = pane
|
||||
end
|
||||
|
||||
footer = UI.CreateLabel(f, "", { size = 11, color = "textDim" })
|
||||
footer:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 16, 10)
|
||||
footer:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -16, 10)
|
||||
footer:SetWordWrap(false)
|
||||
|
||||
f:Hide()
|
||||
return f
|
||||
end
|
||||
|
||||
local function RefreshCurrent()
|
||||
local pane = panes[current]
|
||||
if window and window:IsShown() and pane and pane.Refresh then pane.Refresh() end
|
||||
UpdateFooter()
|
||||
end
|
||||
|
||||
local function ShowWindow()
|
||||
local ok, err = pcall(function()
|
||||
if not window then BuildWindow() end
|
||||
window:Show()
|
||||
SelectTab(current)
|
||||
UpdateFooter()
|
||||
end)
|
||||
if not ok then
|
||||
-- Fehler: das normale Fenster zurueckgeben, damit der Briefkasten benutzbar bleibt
|
||||
Mail.uiFailed = true
|
||||
Mail:ApplyReplace()
|
||||
Mail.Trace("Fenster-Fehler: " .. tostring(err))
|
||||
if FW.Print then FW:Print("Mail: " .. tostring(err)) end
|
||||
end
|
||||
end
|
||||
|
||||
local function HideWindow()
|
||||
if window then
|
||||
if picker then picker:Hide() end
|
||||
window:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
FW:On("MAIL_WINDOW_SHOW", function() ShowWindow() end, OWNER)
|
||||
FW:On("MAIL_WINDOW_HIDE", function() HideWindow() end, OWNER)
|
||||
FW:On("MAIL_STATE", function() RefreshCurrent() end, OWNER)
|
||||
FW:On("MAIL_INBOX_CHANGED", function() RefreshCurrent() end, OWNER)
|
||||
FW:On("MAIL_SELECTION_CHANGED", function() RefreshCurrent() end, OWNER)
|
||||
FW:On("MAIL_LISTS_CHANGED", function() RefreshCurrent() if picker and picker:IsShown() then picker:Fill() end end, OWNER)
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Einstellungen
|
||||
------------------------------------------------------------------------
|
||||
|
||||
if UI.RegisterSettingsSection then
|
||||
UI:RegisterSettingsSection("mail", {
|
||||
order = 118,
|
||||
height = 190,
|
||||
build = function(parent)
|
||||
local f, stack = UI.CreateSection(parent, T("MAIL_SET_HEADER"))
|
||||
local boxes = {}
|
||||
local replace = UI.CreateCheckbox(f, T("MAIL_SET_REPLACE"), function()
|
||||
return Mail.db.settings.replace
|
||||
end, function(value)
|
||||
Mail:SetReplace(value)
|
||||
end)
|
||||
stack:Add(replace, { fit = true, gap = 4 })
|
||||
boxes[#boxes + 1] = replace
|
||||
local chat = UI.CreateCheckbox(f, T("MAIL_SET_CHAT"), function()
|
||||
return Mail.db.settings.chat
|
||||
end, function(value)
|
||||
Mail.db.settings.chat = value and true or false
|
||||
end)
|
||||
stack:Add(chat, { fit = true, gap = 4 })
|
||||
boxes[#boxes + 1] = chat
|
||||
local note = UI.CreateLabel(f, T("MAIL_SET_NOTE"), { size = 11, color = "textDim", justifyV = "TOP" })
|
||||
stack:Add(note, { fit = true, gap = 10 })
|
||||
function f.Refresh()
|
||||
for _, cb in ipairs(boxes) do cb:Refresh() end
|
||||
end
|
||||
stack:Layout()
|
||||
return f
|
||||
end,
|
||||
refresh = function(frame)
|
||||
frame.Refresh()
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Befehl
|
||||
------------------------------------------------------------------------
|
||||
|
||||
SLASH_FREEWOODMAIL1 = "/fwmail"
|
||||
SlashCmdList["FREEWOODMAIL"] = function(msg)
|
||||
msg = (msg or ""):lower():match("^%s*(.-)%s*$")
|
||||
if msg == "on" then
|
||||
Mail:SetReplace(true)
|
||||
FW:Print(T("MAIL_CMD_ON"))
|
||||
elseif msg == "off" then
|
||||
Mail:SetReplace(false)
|
||||
FW:Print(T("MAIL_CMD_OFF"))
|
||||
elseif Mail.open then
|
||||
if window and window:IsShown() then HideWindow() else ShowWindow() end
|
||||
else
|
||||
FW:Print(T("MAIL_CMD_CLOSED"))
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user