update craft guide (resolves error messages). add coins mod (obsoletes commoditymarket). remove commoditymarket for simplicity and lack of use. test remove hud text on npc-traders.
|
@ -1,7 +1,8 @@
|
|||
Sara's Simple Survival Game
|
||||
==========================
|
||||
Copyright (C) 2019-2020 FreeGamers.org
|
||||
Thank you to everyone who has contributed to this project, mods, and the FreeGamers communitys.
|
||||
Thank you to everyone who has contributed to this project, mods, and the FreeGamers community.
|
||||
Special thanks to the Minetest community and developers as well. This game was built using the Minetest game engine and upon minetest_game as a foundation.
|
||||
|
||||
Description
|
||||
-------------------------
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
How to host a dedicated server.
|
||||
server hardware
|
||||
domain name or dynamic dns
|
||||
port forwards and firewalls
|
||||
operating system.
|
||||
how to install
|
||||
administration fundamentals
|
||||
|
||||
|
||||
How to host a listen (local LAN) server.
|
|
@ -16,13 +16,6 @@ initial_stuff = default:pick_wood,default:axe_wood,default:sword_wood,default:to
|
|||
#################
|
||||
# MOD CONFIGS #
|
||||
#################
|
||||
# commoditymarket
|
||||
commoditymarket_enable_kings_market = true
|
||||
commoditymarket_enable_night_market = true
|
||||
commoditymarket_enable_caravan_market = true
|
||||
commoditymarket_enable_goblin_market = true
|
||||
commoditymarket_enable_under_market = true
|
||||
commoditymarket_coins_per_ingot = 100
|
||||
|
||||
# craftguide
|
||||
craftguide_sfinv_only = true
|
||||
|
|
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{one line to give the program's name and a brief idea of what it does.}
|
||||
Copyright (C) {year} {name of author}
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
{project} Copyright (C) {year} {fullname}
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
|
@ -0,0 +1,17 @@
|
|||
saras_simple_survival mod: coins
|
||||
==============================
|
||||
Copyright (2020) freegamers.org
|
||||
|
||||
License of source code
|
||||
------------------------------
|
||||
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.
|
||||
|
||||
https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
|
||||
License of textures
|
||||
-------------------
|
||||
commoditymarket_gold_coins.png - from https://commons.wikimedia.org/wiki/File:Farm-Fresh_coins.png, by FatCow under the CC-BY 3.0 license
|
|
@ -0,0 +1,7 @@
|
|||
-- Cook gold ingots into 100 gold coins.
|
||||
minetest.register_craft({
|
||||
type = "cooking",
|
||||
cooktime = "60",
|
||||
output = "coins:gold_coins 100",
|
||||
recipe = "default:gold_ingot",
|
||||
})
|
|
@ -0,0 +1,14 @@
|
|||
-- TODO: coin press machine. copper, silver, coins. melt coins back to ingots
|
||||
|
||||
local path = minetest.get_modpath("coins")
|
||||
dofile(path .. "/crafting.lua")
|
||||
|
||||
-- This number controls the number of coins per ingot.
|
||||
local coins_per_ingot = 100
|
||||
|
||||
-- Register the gold coin craft item.
|
||||
minetest.register_craftitem("coins:gold_coins", {
|
||||
description = "Gold Coins",
|
||||
inventory_image = "coins_gold_coins.png",
|
||||
stack_max = coins_per_ingot,
|
||||
})
|
|
@ -0,0 +1,6 @@
|
|||
name = coins
|
||||
author = FreeGamers.org
|
||||
description = Ore-based currencies.
|
||||
depends = default
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
@ -1,2 +0,0 @@
|
|||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
|
@ -1,40 +0,0 @@
|
|||
# Compiled Lua sources
|
||||
luac.out
|
||||
|
||||
# luarocks build files
|
||||
*.src.rock
|
||||
*.zip
|
||||
*.tar.gz
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.os
|
||||
*.ko
|
||||
*.elf
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
*.def
|
||||
*.exp
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 FaceDeer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,41 +0,0 @@
|
|||
if not minetest.get_modpath("doc") then
|
||||
return
|
||||
end
|
||||
|
||||
-- internationalization boilerplate
|
||||
local MP = minetest.get_modpath(minetest.get_current_modname())
|
||||
local S, NS = dofile(MP.."/intllib.lua")
|
||||
|
||||
doc.add_category("commoditymarket",
|
||||
{
|
||||
name = S("Commodity Markets"),
|
||||
description = S("Game-wide marketplaces where goods can be bought and sold at prices of your choice."),
|
||||
build_formspec = doc.entry_builders.text_and_gallery,
|
||||
})
|
||||
|
||||
doc.add_entry("commoditymarket", "ui_inventory", {
|
||||
name = S("User Interface: Inventory"),
|
||||
data = { text =
|
||||
S("Each player's account has an inventory that serves as a holding area for items that are destined to be sold or that have been bought by the player but not yet retrieved. This inventory is a bit different from the standard Minetest inventory in that it doesn't hold item \"stacks\", it just tracks the total number of that item present. Some markets allow for extremely large quantities of an item to be stored here for sale."
|
||||
.."\n\n"..
|
||||
"To add an item to your market inventory for eventual sale either shift-click on the item in your player inventory or drag the item stack to the inventory slot below the main market inventory list. Some markets may have restrictions on what items can be bought and sold, if an item is not valid for that market it won't go into the market's inventory. Some items are considered \"currency\" and will add to your account's currency balance instead of being listed in your market inventory."
|
||||
.."\n\n"..
|
||||
"Tools cannot be added to the market inventory if they have any wear on them. The market also can't handle items with attached metadata such as books that have had text added to them."
|
||||
.."\n\n"..
|
||||
"To remove an item from your market inventory, double-click in it in the market inventory list. As much of the item as can fit into your player inventory will be transferred to you, with any remainder staying behind in the market inventory. To withdraw currency from your market balance type the amount you'd like to withdraw in the field next to the \"Withdraw\" button. The currency will be converted into items and added to your player inventory, with whatever cannot be converted remaining behind in your market balance.")
|
||||
}})
|
||||
|
||||
doc.add_entry("commoditymarket", "ui_orders", {
|
||||
name = S("User Interface: Orders"),
|
||||
data = { text =
|
||||
S(
|
||||
"At the core of how a market operates are \"buy\" and \"sell\" orders. A buy order is an announcement to the world that you are interested in purchasing a certain quantity of item and are willing to pay a certain amount of currency in exchange for each unit of that item. Conversely, a sell order is an announcement to the world that you are interested in selling a certain quantity of item and will accept a certain amount of currency in exchange for each unit of that item."
|
||||
.."\n\n"..
|
||||
"The market price of an item is determined by where the existing buy and sell orders for that item intersect. When you offer to buy an item for a price that someone is offering to sell it at, the item is transferred to you and currency is transferred from your account to theirs to cover the cost. The market will keep track of the most recent price that an item was successfully sold for, but note that this information is for historical interest only - there's no guarantee that anyone is currently willing to match the historical price."
|
||||
.."\n\n"..
|
||||
"When an item is selected in the upper list, the currently existing buy and sell orders for that item will be displayed in the lower list. Sell orders are listed first in descending price, followed by buy orders in ascending price. The current market price will be somewhere in between the lowest sell order and the highest buy order. If you wish to cancel a buy or sell order that you've placed for an item, double-click on the order and the item or currency that you put into that order will be returned to your inventory."
|
||||
.."\n\n"..
|
||||
"If you place a buy order and there are already sell orders for the item that meet or are below your price, some or all of your buy order might be immediately fulfilled. Your purchases will be made at the price that the sell orders have been set to - if you were willing to pay 15 units of currency per item but someone was already offering to sell for 2 units of currency per item, you only pay 2 units for each of that offer's items. If there aren't enough compatible sell orders to fulfill your buy order, the remainder will be placed into the market and made available for future sellers to see and fulfill if they agree to your price. Your buy order will immediately deduct the currency required for it from your account's balance, but if you cancel your order you will get that currency back - it's not gone until the order is actually fulfilled."
|
||||
.."\n\n"..
|
||||
"If you place a sell order and there are already buy orders that meet or exceed your price, some or all of your sell order may be immediately fulfilled. You'll be paid the price that the buyers are offering rather than the amount you're demanding. If any of your sell offer is left unfulfilled, the sell order will be added to the market for future buyers to see. The items for this offer will be immediately taken from your market inventory but if you cancel your order you will get those items back.")
|
||||
}})
|
|
@ -1,790 +0,0 @@
|
|||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
|
||||
local truncate_item_names_to = 30
|
||||
|
||||
-- Large textures can screw with the formspecs.
|
||||
-- See https://github.com/minetest/minetest/issues/9300 for a feature request that would simplify and improve icon generation, if supported.
|
||||
-- In the meantime, here's some methods for overriding item icons to manually work around this:
|
||||
local override_item_icon = {}
|
||||
commoditymarket.override_item_icon = function(item_name, new_icon_texture)
|
||||
override_item_icon[item_name] = new_icon_texture
|
||||
end
|
||||
local override_image_icon = {}
|
||||
commoditymarket.override_image_icon = function(old_icon_texture, new_icon_texture)
|
||||
override_image_icon[old_icon_texture] = new_icon_texture
|
||||
end
|
||||
-- And a setting for disabling icons entirely:
|
||||
local global_enable_item_icons = minetest.settings:get_bool("commoditymarket_enable_item_icons", true)
|
||||
|
||||
--[inventorycube{<top>{<left>{<right>
|
||||
--Escaping does not apply here and `^` is replaced by `&` in texture names instead.
|
||||
--Example:
|
||||
-- [inventorycube{grass.png{dirt.png&grass_side.png{dirt.png&grass_side.png
|
||||
--Creates an inventorycube with `grass.png`, `dirt.png^grass_side.png` and `dirt.png^grass_side.png` textures
|
||||
local process_inventory_cube = function(texture_string)
|
||||
if not texture_string:sub(1,14) == "[inventorycube" then
|
||||
return texture_string
|
||||
end
|
||||
local split = texture_string:split("{")
|
||||
local left = split[3] -- the "front" of the cube we're seeing in the inventory list
|
||||
if left == nil then -- in case something weird happens, don't crash.
|
||||
return texture_string
|
||||
end
|
||||
left = left:gsub("&", "^")
|
||||
return left
|
||||
end
|
||||
|
||||
local get_icon = function(item)
|
||||
local def = minetest.registered_items[item]
|
||||
local returnstring = "unknown_item.png"
|
||||
if def == nil then
|
||||
return returnstring
|
||||
end
|
||||
|
||||
local override = override_item_icon[item]
|
||||
if override then
|
||||
return override
|
||||
end
|
||||
|
||||
local inventory_image = def.inventory_image
|
||||
if inventory_image and inventory_image ~= "" then
|
||||
returnstring = inventory_image
|
||||
else
|
||||
local tiles = def.tiles
|
||||
if tiles then
|
||||
local tilecount = #tiles
|
||||
-- Textures of node; +Y, -Y, +X, -X, +Z, -Z
|
||||
local selected_tile = tiles[math.min(5,tilecount)]
|
||||
if type(selected_tile) == "string" then
|
||||
returnstring = selected_tile
|
||||
else
|
||||
local tile_name = selected_tile.name
|
||||
if tile_name then
|
||||
returnstring = tile_name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
returnstring = process_inventory_cube(returnstring)
|
||||
|
||||
-- Formspec tables can't handle image compositing and modifiers
|
||||
local found_caret = returnstring:find("%^")
|
||||
if found_caret then
|
||||
returnstring = returnstring:sub(1, found_caret-1)
|
||||
end
|
||||
|
||||
override = override_image_icon[returnstring]
|
||||
if override then
|
||||
return override
|
||||
end
|
||||
|
||||
return minetest.formspec_escape(returnstring)
|
||||
end
|
||||
-- Exposed so that the purge_unknowns command can use it.
|
||||
commoditymarket.get_icon = get_icon
|
||||
|
||||
|
||||
local truncate_string = function(target, length)
|
||||
if target:len() > length then
|
||||
return target:sub(1,length-2).."..."
|
||||
end
|
||||
return target
|
||||
end
|
||||
|
||||
local get_item_description = function(item)
|
||||
local def = minetest.registered_items[item]
|
||||
if def then
|
||||
local description = def.description
|
||||
if description then
|
||||
return minetest.formspec_escape(description:gsub("\n", " "))
|
||||
end
|
||||
end
|
||||
return S("Unknown Item")
|
||||
end
|
||||
|
||||
-- Inventory formspec
|
||||
-------------------------------------------------------------------------------------
|
||||
|
||||
local inventory_item_comp = function(invitem1, invitem2) return invitem1.item < invitem2.item end
|
||||
local inventory_desc_comp = function(invitem1, invitem2) return invitem1.description < invitem2.description end
|
||||
|
||||
local get_account_formspec = function(market, account)
|
||||
local show_itemnames = account.show_itemnames == "true"
|
||||
local show_icons = global_enable_item_icons and ((account.show_icons or "true") == "true")
|
||||
local market_def = market.def
|
||||
|
||||
local inventory = {}
|
||||
local inventory_count = 0
|
||||
for item, quantity in pairs(account.inventory) do
|
||||
local icon
|
||||
if show_icons then
|
||||
icon = get_icon(item)
|
||||
end
|
||||
table.insert(inventory, {item=item, quantity=quantity, icon=icon, description=get_item_description(item)})
|
||||
inventory_count = inventory_count + quantity
|
||||
end
|
||||
if show_itemnames then
|
||||
table.sort(inventory, inventory_item_comp)
|
||||
else
|
||||
table.sort(inventory, inventory_desc_comp)
|
||||
end
|
||||
|
||||
local formspec = {
|
||||
"size[10,10]"
|
||||
.."tabheader[0,0;tabs;"..market_def.description..","..S("Your Inventory")..","..S("Market Orders")..";2;false;true]"
|
||||
}
|
||||
formspec[#formspec+1] = "tablecolumns["
|
||||
|
||||
if show_icons then
|
||||
formspec[#formspec+1] = "image"
|
||||
for i=1, #inventory, 2 do
|
||||
formspec[#formspec+1] = ","..i.."="..inventory[i].icon
|
||||
end
|
||||
formspec[#formspec+1] = ";"
|
||||
end
|
||||
if show_itemnames then
|
||||
formspec[#formspec+1] = "text;"
|
||||
end
|
||||
formspec[#formspec+1] = "text;text,align=center"
|
||||
if show_icons then
|
||||
formspec[#formspec+1] = ";image"
|
||||
for i=2, #inventory, 2 do
|
||||
formspec[#formspec+1] = ","..i.."="..inventory[i].icon
|
||||
end
|
||||
end
|
||||
if show_itemnames then
|
||||
formspec[#formspec+1] = ";text"
|
||||
end
|
||||
formspec[#formspec+1] = ";text;text,align=center]"
|
||||
.."tooltip[inventory;"..S("All the items you've transfered to the market to sell and the items you've\npurchased with buy orders. Double-click on an item to bring it back into your\npersonal inventory.").."]"
|
||||
.."table[0,0;9.75,4;inventory;"
|
||||
if show_icons then
|
||||
formspec[#formspec+1] = "0,"
|
||||
end
|
||||
if show_itemnames then
|
||||
formspec[#formspec+1] = S("Item")..","
|
||||
end
|
||||
formspec[#formspec+1] = S("Description")..","..S("Quantity")
|
||||
if show_icons then
|
||||
formspec[#formspec+1] = ",0"
|
||||
end
|
||||
if show_itemnames then
|
||||
formspec[#formspec+1] = ","..S("Item")
|
||||
end
|
||||
formspec[#formspec+1] = ","..S("Description")..","..S("Quantity")
|
||||
|
||||
for i, entry in ipairs(inventory) do
|
||||
if show_icons then
|
||||
formspec[#formspec+1] = "," .. i
|
||||
end
|
||||
if show_itemnames then
|
||||
formspec[#formspec+1] = "," .. truncate_string(entry.item, truncate_item_names_to)
|
||||
end
|
||||
-- no need to formspec_escape description here, it gets done when it's initially added to the inventory table
|
||||
formspec[#formspec+1] = "," .. entry.description .. "," .. entry.quantity
|
||||
end
|
||||
|
||||
formspec[#formspec+1] = "]container[1,4.5]list[detached:commoditymarket:" .. market.name .. ";add;0,0;1,1;]"
|
||||
.."label[1,0;"..S("Drop items here to\nadd to your account").."]"
|
||||
.."listring[current_player;main]listring[detached:commoditymarket:" .. market.name .. ";add]"
|
||||
|
||||
if market_def.inventory_limit then
|
||||
formspec[#formspec+1] = "label[3,0;"..S("Inventory limit:").."\n" .. inventory_count.."/" .. market_def.inventory_limit .. "]"
|
||||
.. "tooltip[3,0;1.5,1;"..S("You can still receive purchased items if you've exceeded your inventory limit,\nbut you won't be able to transfer items from your personal inventory into\nthe market until you've emptied it back down below the limit again.").."]"
|
||||
end
|
||||
formspec[#formspec+1] = "label[4.9,0;"..S("Balance:") .. "\n" .. market_def.currency_symbol .. account.balance .. "]"
|
||||
.."tooltip[4.9,0;3.5,1;"..S("Enter the amount of currency you'd like to withdraw then click the 'Withdraw'\nbutton to convert it into items and transfer it to your personal inventory.").."]"
|
||||
.."field[6.1,0.325;1,1;withdrawamount;;]"
|
||||
.."field_close_on_enter[withdrawamount;false]"
|
||||
.."button[6.7,0;1.2,1;withdraw;"..S("Withdraw").."]"
|
||||
.."container_end[]"
|
||||
.."container[1,5.75]list[current_player;main;0,0;8,1;]"
|
||||
.."list[current_player;main;0,1.25;8,3;8]container_end[]"
|
||||
|
||||
return table.concat(formspec)
|
||||
end
|
||||
|
||||
|
||||
-- Market formspec
|
||||
--------------------------------------------------------------------------------------------------------
|
||||
|
||||
local compare_market_item = function(mkt1, mkt2)
|
||||
return mkt1.item < mkt2.item
|
||||
end
|
||||
local compare_market_desc = function(mkt1, mkt2)
|
||||
-- TODO: see https://github.com/minetest/minetest/issues/8398 for sorting localized strings
|
||||
return get_item_description(mkt1.item) < get_item_description(mkt2.item)
|
||||
end
|
||||
local compare_buy_volume = function(mkt1, mkt2)
|
||||
return mkt1.buy_volume > mkt2.buy_volume
|
||||
end
|
||||
local compare_buy_max = function(mkt1, mkt2)
|
||||
return ((mkt1.buy_orders[#mkt1.buy_orders] or {}).price or -2^30) > ((mkt2.buy_orders[#mkt2.buy_orders] or {}).price or -2^30)
|
||||
end
|
||||
local compare_sell_volume = function(mkt1, mkt2)
|
||||
return mkt1.sell_volume > mkt2.sell_volume
|
||||
end
|
||||
local compare_sell_min = function(mkt1, mkt2)
|
||||
return ((mkt1.sell_orders[#mkt1.sell_orders] or {}).price or 2^31) < ((mkt2.sell_orders[#mkt2.sell_orders] or {}).price or 2^31)
|
||||
end
|
||||
local compare_last_price = function(mkt1, mkt2)
|
||||
return (mkt1.last_price or 2^31) < (mkt2.last_price or 2^31)
|
||||
end
|
||||
|
||||
local sort_marketlist = function(item_list, account)
|
||||
-- I think tonumber is now redundant here, leaving it in in case upgrading a world that has text recorded in this field for an existing player account
|
||||
local sort_by = tonumber(account.sort_markets_by_column)
|
||||
if sort_by == nil then return end
|
||||
local show_itemnames = account.show_itemnames == "true"
|
||||
local show_icons = global_enable_item_icons and ((account.show_icons or "true") == "true")
|
||||
|
||||
local icon_displace = 0
|
||||
if show_icons then
|
||||
icon_displace = 1
|
||||
end
|
||||
local itemname_displace = 0
|
||||
if show_itemnames then
|
||||
itemname_displace = 1
|
||||
end
|
||||
|
||||
-- "Icon,Item,Description,#00FF00,Buy Vol,Buy Max,#FF0000,Sell Vol,Sell Min,Last Price"
|
||||
if sort_by == 1 + icon_displace and show_itemnames then
|
||||
table.sort(item_list, compare_market_item)
|
||||
elseif sort_by == 1 + icon_displace + itemname_displace then
|
||||
table.sort(item_list, compare_market_desc)
|
||||
elseif sort_by == 3 + icon_displace + itemname_displace then
|
||||
table.sort(item_list, compare_buy_volume)
|
||||
elseif sort_by == 4 + icon_displace + itemname_displace then
|
||||
table.sort(item_list, compare_buy_max)
|
||||
elseif sort_by == 6 + icon_displace + itemname_displace then
|
||||
table.sort(item_list, compare_sell_volume)
|
||||
elseif sort_by == 7 + icon_displace + itemname_displace then
|
||||
table.sort(item_list, compare_sell_min)
|
||||
elseif sort_by == 8 + icon_displace + itemname_displace then
|
||||
table.sort(item_list, compare_last_price)
|
||||
elseif sort_by == 9 + icon_displace + itemname_displace then
|
||||
table.sort(item_list, function(mkt1, mkt2)
|
||||
-- Define locally so that account is available
|
||||
return (account.inventory[mkt1.item] or 0) > (account.inventory[mkt2.item] or 0)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local make_marketlist = function(market, account)
|
||||
local market_list = {}
|
||||
local search_filter = account.search or ""
|
||||
for item, row in pairs(market.orders_for_items) do
|
||||
if (search_filter == "" or string.find(item, search_filter)) then
|
||||
if account.filter_participating == "true" then
|
||||
local found = false
|
||||
for _, order in ipairs(row.buy_orders) do
|
||||
if account == order.account then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
for _, order in ipairs(row.sell_orders) do
|
||||
if account == order.account then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if found then
|
||||
table.insert(market_list, row)
|
||||
end
|
||||
else
|
||||
table.insert(market_list, row)
|
||||
end
|
||||
end
|
||||
end
|
||||
sort_marketlist(market_list, account)
|
||||
return market_list
|
||||
end
|
||||
|
||||
local get_account_name = function(target_account, this_account, anonymous)
|
||||
if anonymous and target_account ~= this_account then
|
||||
return ""
|
||||
end
|
||||
return target_account.name
|
||||
end
|
||||
|
||||
local get_market_formspec = function(market, account)
|
||||
local market_def = market.def
|
||||
local selected = account.selected
|
||||
local market_list = make_marketlist(market, account)
|
||||
local show_itemnames = account.show_itemnames == "true"
|
||||
local show_icons = global_enable_item_icons and ((account.show_icons or "true") == "true")
|
||||
local anonymous = market_def.anonymous
|
||||
|
||||
local formspec = {
|
||||
"size[10,10]"
|
||||
.."tabheader[0,0;tabs;"..market_def.description..","..S("Your Inventory")..","..S("Market Orders")..";3;false;true]"
|
||||
}
|
||||
|
||||
-- column definitions
|
||||
formspec[#formspec+1] = "tablecolumns["
|
||||
if show_icons then
|
||||
formspec[#formspec+1] = "image" -- icon
|
||||
for i, row in ipairs(market_list) do
|
||||
formspec[#formspec+1] = "," .. i .. "=" .. get_icon(row.item)
|
||||
end
|
||||
formspec[#formspec+1] = ";"
|
||||
end if show_itemnames then
|
||||
formspec[#formspec+1] = "text;" -- itemname
|
||||
end
|
||||
formspec[#formspec+1] = "text;" -- description
|
||||
.."color,span=2;"
|
||||
.."text,align=right,tooltip="..S("Number of items there's demand for in the market.")..";"
|
||||
.."text,align=right,tooltip="..S("Maximum price being offered to buy one of these.")..";"
|
||||
.."color,span=2;"
|
||||
.."text,align=right,tooltip="..S("Number of items available for sale in the market.")..";"
|
||||
.."text,align=right,tooltip="..S("Minimum price being demanded to sell one of these.")..";"
|
||||
.."text,align=right,tooltip="..S("Price paid for one of these the last time one was sold.")..";"
|
||||
.."text,align=right,tooltip="..S("Quantity of this item that you have in your inventory ready to sell.").."]"
|
||||
.."table[0,0;9.75,5;summary;"
|
||||
if show_icons then
|
||||
formspec[#formspec+1] = "0,"-- icon
|
||||
end
|
||||
|
||||
-- header row
|
||||
if show_itemnames then
|
||||
formspec[#formspec+1] = "Item," -- itemname
|
||||
end
|
||||
formspec[#formspec+1] = S("Description")..",#00FF00,"..S("Buy Vol")..","..S("Buy Max")
|
||||
..",#FF0000,"..S("Sell Vol")..","..S("Sell Min")..","..S("Last Price")..","..S("Inventory")
|
||||
|
||||
local selected_idx
|
||||
local selected_row
|
||||
|
||||
-- Show list of item market summaries
|
||||
for i, row in ipairs(market_list) do
|
||||
if show_icons then
|
||||
formspec[#formspec+1] = ","..i -- icon
|
||||
end
|
||||
|
||||
if show_itemnames then
|
||||
formspec[#formspec+1] = "," .. truncate_string(row.item, truncate_item_names_to)
|
||||
end
|
||||
|
||||
formspec[#formspec+1] = "," .. get_item_description(row.item)
|
||||
.. ",#00FF00,"
|
||||
.. row.buy_volume
|
||||
.. "," .. ((row.buy_orders[#row.buy_orders] or {}).price or "-")
|
||||
.. ",#FF0000,"
|
||||
.. row.sell_volume
|
||||
.. "," .. ((row.sell_orders[#row.sell_orders] or {}).price or "-")
|
||||
.. "," .. (row.last_price or "-")
|
||||
.. "," .. (account.inventory[row.item] or "-")
|
||||
|
||||
-- we happen to be processing the row that matches the item this player has selected. Record that.
|
||||
if selected == row.item then
|
||||
selected_row = row
|
||||
selected_idx = i + 1
|
||||
end
|
||||
|
||||
end
|
||||
-- a row that's visible is marked as the selected item, so make it selected in the formspec
|
||||
if selected_row then
|
||||
formspec[#formspec+1] = ";"..selected_idx
|
||||
end
|
||||
formspec[#formspec+1] = "]"
|
||||
|
||||
-- search field
|
||||
formspec[#formspec+1] = "container[2.5,5]field_close_on_enter[search_filter;false]"
|
||||
.."field[0,0.85;2.5,1;search_filter;;"..minetest.formspec_escape(account.search or "").."]"
|
||||
.."image_button[2.05,0.65;0.8,0.8;commoditymarket_search.png;apply_search;]"
|
||||
.."image_button[2.7,0.65;0.8,0.8;commoditymarket_clear.png;clear_search;]"
|
||||
.."checkbox[1.77,0;filter_participating;"..S("My orders")..";".. account.filter_participating .."]"
|
||||
.."tooltip[filter_participating;"..S("Select this to show only the markets where you have either a buy or a sell order pending.").."]"
|
||||
.."tooltip[search_filter;"..S("Enter substring to search item identifiers for.").."]"
|
||||
.."tooltip[apply_search;"..S("Apply search to outputs.").."]"
|
||||
.."tooltip[clear_search;"..S("Clear search.").."]"
|
||||
.."container_end[]"
|
||||
|
||||
-- if a visible item market is selected, show the orders for it in detail
|
||||
if selected_row then
|
||||
local current_time = minetest.get_gametime()
|
||||
|
||||
local desc_display
|
||||
if show_itemnames then
|
||||
desc_display = selected
|
||||
else
|
||||
local def = minetest.registered_items[selected_row.item] or {description=S("Unknown Item")}
|
||||
desc_display = minetest.formspec_escape(def.description:gsub("\n", " "))
|
||||
end
|
||||
|
||||
-- player inventory for this item and for currency
|
||||
formspec[#formspec+1] = "label[0.1,5.1;"..desc_display.."\n"..S("In inventory:").." "
|
||||
.. tostring(account.inventory[selected] or 0) .."\n"..S("Balance:").." "..market_def.currency_symbol..account.balance .."]"
|
||||
-- buy/sell controls
|
||||
.. "container[6.1,5]"
|
||||
local sell_limit = market_def.sell_limit
|
||||
if sell_limit then
|
||||
local total_sell = 0
|
||||
for item, orders in pairs(market.orders_for_items) do
|
||||
for _, order in ipairs(orders.sell_orders) do
|
||||
if order.account == account then
|
||||
total_sell = total_sell + order.quantity
|
||||
end
|
||||
end
|
||||
end
|
||||
formspec[#formspec+1] = "label[0,0;"..S("Sell limit:").." ".. total_sell .. "/" .. sell_limit .."]"
|
||||
.."tooltip[0,0;2,0.25;"..S("This market limits the total number of items a given seller can have for sale at a time.\nYou have @1 items remaining. Cancel old sell orders to free up space.", sell_limit-total_sell).."]"
|
||||
end
|
||||
-- Buy, sell, quantity and price button
|
||||
formspec[#formspec+1] = "tooltip[0,0.25;3.75,1;"..S("Use these fields to enter buy and sell orders for the selected item.").."]"
|
||||
.."button[0,0.55;1,1;buy;"..S("Buy").."]field[1.2,0.85;1,1;quantity;"..S("Quantity")..";]"
|
||||
.."field[2.1,0.85;1,1;price;"..S("Price per")..";]button[2.7,0.55;1,1;sell;Sell]"
|
||||
.."field_close_on_enter[quantity;false]field_close_on_enter[price;false]"
|
||||
.."container_end[]"
|
||||
-- table of buy and sell orders
|
||||
.."tablecolumns[color;text;"
|
||||
.."text,align=right,tooltip="..S("The price per item in this order.")..";"
|
||||
.."text,align=right,tooltip="..S("The total amount of items in this particular order.")..";"
|
||||
.."text,align=right,tooltip="..S("The total amount of items available at this price accounting for the other orders also currently being offered.")..";"
|
||||
.."text,tooltip="..S("The name of the player who placed this order.\nDouble-click your own orders to cancel them.")..";"
|
||||
.."text,align=right,tooltip="..S("How many days ago this order was placed.").."]"
|
||||
.."table[0,6.5;9.75,3.5;orders;#FFFFFF,"..S("Order")..","..S("Price")..","..S("Quantity")..","..S("Total Volume")..","..S("Player")..","..S("Days Old")
|
||||
|
||||
local sell_volume = selected_row.sell_volume
|
||||
for i, sell in ipairs(selected_row.sell_orders) do
|
||||
formspec[#formspec+1] = ",#FF0000,"..S("Sell")..","
|
||||
..sell.price..","
|
||||
..sell.quantity..","
|
||||
..sell_volume..","
|
||||
..get_account_name(sell.account, account, anonymous)..","
|
||||
..math.floor((current_time-sell.timestamp)/86400)
|
||||
sell_volume = sell_volume - sell.quantity
|
||||
end
|
||||
local buy_volume = 0
|
||||
local buy_orders = selected_row.buy_orders
|
||||
local buy_count = #buy_orders
|
||||
-- Show buy orders in reverse order
|
||||
for i = buy_count, 1, -1 do
|
||||
local buy = buy_orders[i]
|
||||
buy_volume = buy_volume + buy.quantity
|
||||
formspec[#formspec+1] = ",#00FF00,"..S("Buy")..","
|
||||
..buy.price..","
|
||||
..buy.quantity..","
|
||||
..buy_volume..","
|
||||
..get_account_name(buy.account, account, anonymous)..","
|
||||
..math.floor((current_time-buy.timestamp)/86400)
|
||||
end
|
||||
formspec[#formspec+1] = "]"
|
||||
else
|
||||
formspec[#formspec+1] = "label[0.1,5.1;"..S("Select an item to view or place orders.").."]"
|
||||
end
|
||||
|
||||
return table.concat(formspec)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------
|
||||
-- Information formspec
|
||||
|
||||
--{item=item, quantity=quantity, price=price, purchaser=purchaser, seller=seller, timestamp = minetest.get_gametime()}
|
||||
local log_to_string = function(market, log_entry, account)
|
||||
local anonymous = market.def.anonymous
|
||||
local purchaser = log_entry.purchaser
|
||||
local seller = log_entry.seller
|
||||
local purchaser_name
|
||||
if purchaser == seller then
|
||||
purchaser_name = S("yourself")
|
||||
elseif anonymous and purchaser ~= account then
|
||||
purchaser_name = S("someone")
|
||||
elseif purchaser == account then
|
||||
purchaser_name = S("you")
|
||||
else
|
||||
purchaser_name = purchaser.name
|
||||
end
|
||||
local seller_name
|
||||
if anonymous and seller ~= account then
|
||||
seller_name = S("someone")
|
||||
elseif seller == account then
|
||||
seller_name = S("you")
|
||||
else
|
||||
seller_name = seller.name
|
||||
end
|
||||
local colour
|
||||
local new
|
||||
local last_acknowledged = account.last_acknowledged or 0
|
||||
if log_entry.timestamp > last_acknowledged then
|
||||
colour = "#FFFF00"
|
||||
new = true
|
||||
else
|
||||
colour = "#FFFFFF"
|
||||
new = false
|
||||
end
|
||||
local show_itemnames = account.show_itemnames == "true"
|
||||
local itemname = log_entry.item
|
||||
if not show_itemnames then
|
||||
local item_def = minetest.registered_items[log_entry.item]
|
||||
if item_def then
|
||||
itemname = minetest.formspec_escape(item_def.description:gsub("\n", " "))
|
||||
end
|
||||
end
|
||||
|
||||
local currency_symbol = market.def.currency_symbol
|
||||
return colour .. S("On day @1 @2 sold @3 @4 to @5 at @6@7 each for a total of @8@9.",
|
||||
math.ceil(log_entry.timestamp/86400), seller_name, log_entry.quantity, itemname,
|
||||
purchaser_name, currency_symbol, log_entry.price, currency_symbol, log_entry.quantity*log_entry.price), new
|
||||
end
|
||||
|
||||
|
||||
local get_info_formspec = function(market, account)
|
||||
local formspec = {
|
||||
"size[10,10]"
|
||||
.."tabheader[0,0;tabs;"..market.def.description..","..S("Your Inventory")..","..S("Market Orders")..";1;false;true]"
|
||||
.."textarea[0.75,0.5;9.25,1.5;;"..S("Description:")..";"..market.def.long_description.."]"
|
||||
.."label[0.5,2.2;"..S("Your Recent Purchases and Sales:").."]"
|
||||
.."textlist[0.5,2.6;8.75,4;log_entries;"
|
||||
}
|
||||
if next(account.log) then
|
||||
local new = false
|
||||
for _, log_entry in ipairs(account.log) do
|
||||
local log_string, new_log = log_to_string(market, log_entry, account)
|
||||
new = new or new_log
|
||||
formspec[#formspec+1] = log_string
|
||||
formspec[#formspec+1] = ","
|
||||
end
|
||||
formspec[#formspec] = "]" -- Note: there's no +1 here deliberately, that way the "]" overwrites the last comma added by the loop above.
|
||||
if new then
|
||||
formspec[#formspec+1] = "button[7.1,6.9;2,0.5;acknowledge_log;"..S("Mark logs as read").."]" ..
|
||||
"tooltip[acknowledge_log;"..S("Log entries in yellow are new since last time you marked your log as read.").."]"
|
||||
end
|
||||
else
|
||||
formspec[#formspec+1] = "#CCCCCC"..S("No logged activities in this market yet.").."]"
|
||||
end
|
||||
local show_itemnames = account.show_itemnames or "false"
|
||||
|
||||
formspec[#formspec+1] = "]container[0.5, 7.5]label[0,0;Settings:]checkbox[0,0.25;show_itemnames;"..S("Show Itemnames")..";"
|
||||
..show_itemnames.."]"
|
||||
if global_enable_item_icons then
|
||||
local show_icons = account.show_icons or "true"
|
||||
formspec[#formspec+1] = "checkbox[2,0.25;show_icons;"..S("Show Icons")..";"..show_icons.."]"
|
||||
end
|
||||
formspec[#formspec+1] = "container_end[]"
|
||||
|
||||
return table.concat(formspec)
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------
|
||||
|
||||
commoditymarket.get_formspec = function(market, account)
|
||||
local tab = account.tab
|
||||
if tab == 1 then
|
||||
return get_info_formspec(market, account)
|
||||
elseif tab == 2 then
|
||||
return get_account_formspec(market, account)
|
||||
else
|
||||
return get_market_formspec(market, account)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
------------------------------------------------------------------------------------
|
||||
-- Handling recieve_fields
|
||||
|
||||
local add_to_player_inventory = function(name, item, amount)
|
||||
local playerinv = minetest.get_inventory({type="player", name=name})
|
||||
local not_full = true
|
||||
while amount > 0 and not_full do
|
||||
local stack = ItemStack(item .. " " .. amount)
|
||||
amount = amount - stack:get_count()
|
||||
local leftover = playerinv:add_item("main", stack)
|
||||
if leftover:get_count() > 0 then
|
||||
amount = amount + leftover:get_count()
|
||||
return amount
|
||||
end
|
||||
end
|
||||
return amount
|
||||
end
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
local formname_split = formname:split(":")
|
||||
|
||||
if formname_split[1] ~= "commoditymarket" then
|
||||
return false
|
||||
end
|
||||
local market = commoditymarket.registered_markets[formname_split[2]]
|
||||
if not market then
|
||||
return false
|
||||
end
|
||||
|
||||
local name = formname_split[3]
|
||||
if name ~= player:get_player_name() then
|
||||
return false
|
||||
end
|
||||
|
||||
local account = market:get_account(name)
|
||||
local show_icons = global_enable_item_icons and ((account.show_icons or "true") == "true")
|
||||
|
||||
local something_changed = false
|
||||
if fields.tabs then
|
||||
account.tab = tonumber(fields.tabs)
|
||||
something_changed = true
|
||||
end
|
||||
-- player clicked on an item in the market summary table
|
||||
if fields.summary then
|
||||
local summaryevent = minetest.explode_table_event(fields.summary)
|
||||
if summaryevent.type == "DCL" or summaryevent.type == "CHG" then
|
||||
if summaryevent.row == 1 then
|
||||
-- header clicked, sort by column
|
||||
local column = tonumber(summaryevent.column)
|
||||
if not (column == 1 and show_icons) then -- ignore clicks on the icon column header
|
||||
account.sort_markets_by_column = column
|
||||
end
|
||||
else
|
||||
-- item clicked, recreate the list to find out which one
|
||||
local marketlist = make_marketlist(market, account)
|
||||
local selected = marketlist[summaryevent.row-1]
|
||||
if selected then
|
||||
account.selected = selected.item
|
||||
end
|
||||
end
|
||||
elseif summaryevent.type == "INV" then
|
||||
account.selected = nil
|
||||
end
|
||||
something_changed = true
|
||||
end
|
||||
if fields.orders then
|
||||
local ordersevent = minetest.explode_table_event(fields.orders)
|
||||
if ordersevent.type == "DCL" and ordersevent.column > 0 then
|
||||
local selected_idx = ordersevent.row - 1 -- account for header
|
||||
local selected_row = market.orders_for_items[account.selected] -- sell orders come first
|
||||
local sell_orders = selected_row.sell_orders
|
||||
local sell_order_count = #sell_orders
|
||||
local selected_order
|
||||
if selected_idx <= sell_order_count then -- if the index is within the range of sell orders,
|
||||
selected_order = sell_orders[selected_idx]
|
||||
if selected_order and selected_order.account == account then -- and the order belongs to the current player,
|
||||
market:cancel_sell(account.selected, selected_order) -- cancel it
|
||||
something_changed = true
|
||||
end
|
||||
else
|
||||
-- otherwise we're in the buy group, shift the index up by sell_order_count and reverse index order
|
||||
local buy_orders = selected_row.buy_orders
|
||||
local buy_orders_count = #buy_orders
|
||||
selected_order = buy_orders[buy_orders_count - (selected_idx - sell_order_count - 1)]
|
||||
if selected_order and selected_order.account == account then
|
||||
market:cancel_buy(account.selected, selected_order)
|
||||
something_changed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if fields.buy then
|
||||
local quantity = tonumber(fields.quantity)
|
||||
local price = tonumber(fields.price)
|
||||
if price ~= nil and quantity ~= nil then
|
||||
market:buy(name, account.selected, quantity, price)
|
||||
something_changed = true
|
||||
end
|
||||
end
|
||||
if fields.sell then
|
||||
local quantity = tonumber(fields.quantity)
|
||||
local price = tonumber(fields.price)
|
||||
if price ~= nil and quantity ~= nil then
|
||||
market:sell(name, account.selected, quantity, price)
|
||||
something_changed = true
|
||||
end
|
||||
end
|
||||
|
||||
-- player clicked in their inventory table, may need to give him his stuff back
|
||||
if fields.inventory then
|
||||
local invevent = minetest.explode_table_event(fields.inventory)
|
||||
if invevent.type == "DCL" and invevent.column > 0 then
|
||||
local col_count = 8
|
||||
local show_itemnames = account.show_itemnames == "true"
|
||||
if not show_itemnames then
|
||||
col_count = col_count - 2
|
||||
end
|
||||
if not show_icons then
|
||||
col_count = col_count - 2
|
||||
end
|
||||
local index = math.floor(((invevent.row-1)*col_count + invevent.column - 1)/(col_count/2)) - 1
|
||||
local account = market:get_account(name)
|
||||
-- build a local copy of the inventory that would be displayed in the formspec so we can
|
||||
-- figure out what item the index we were given is pointing to
|
||||
local inventory = {}
|
||||
for item, quantity in pairs(account.inventory) do
|
||||
table.insert(inventory, {item=item, quantity=quantity, description=get_item_description(item)})
|
||||
end
|
||||
if show_itemnames then
|
||||
table.sort(inventory, inventory_item_comp)
|
||||
else
|
||||
table.sort(inventory, inventory_desc_comp)
|
||||
end
|
||||
if inventory[index] then
|
||||
local item = inventory[index].item
|
||||
local amount = account.inventory[item]
|
||||
local remaining = add_to_player_inventory(name, item, amount)
|
||||
if remaining == 0 then
|
||||
account.inventory[item] = nil
|
||||
else
|
||||
account.inventory[item] = remaining
|
||||
end
|
||||
if remaining ~= amount then
|
||||
something_changed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if fields.withdraw or fields.key_enter_field == "withdrawamount" then
|
||||
local withdrawvalue = tonumber(fields.withdrawamount)
|
||||
if withdrawvalue then
|
||||
local account = market:get_account(name)
|
||||
withdrawvalue = math.min(withdrawvalue, account.balance)
|
||||
for _, currency in ipairs(market.def.currency_ordered) do
|
||||
this_unit_amount = math.floor(withdrawvalue/currency.amount)
|
||||
if this_unit_amount > 0 then
|
||||
local remaining = add_to_player_inventory(name, currency.item, this_unit_amount)
|
||||
local value_given = (this_unit_amount - remaining) * currency.amount
|
||||
account.balance = account.balance - value_given
|
||||
withdrawvalue = withdrawvalue - value_given
|
||||
something_changed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if fields.search_filter then
|
||||
local value = string.lower(fields.search_filter)
|
||||
if account.search ~= value then
|
||||
account.search = value
|
||||
end
|
||||
end
|
||||
|
||||
local process_checkbox = function(property_name, fields, account)
|
||||
if (fields[property_name] == "true" and account[property_name] ~= "true") or
|
||||
(fields[property_name] == "false" and account[property_name] ~= "false") then
|
||||
account[property_name] = fields[property_name]
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
if process_checkbox("filter_participating", fields, account) then something_changed = true end
|
||||
if process_checkbox("show_itemnames", fields, account) then something_changed = true end
|
||||
if process_checkbox("show_icons", fields, account) then something_changed = true end
|
||||
|
||||
if fields.acknowledge_log then
|
||||
account.last_acknowledged = minetest.get_gametime()
|
||||
something_changed = true
|
||||
end
|
||||
|
||||
if fields.apply_search or fields.key_enter_field == "search_filter" then
|
||||
something_changed = true
|
||||
end
|
||||
|
||||
if fields.clear_search then
|
||||
account.search = ""
|
||||
something_changed = true
|
||||
end
|
||||
if something_changed then
|
||||
minetest.show_formspec(name, formname, market:get_formspec(account))
|
||||
end
|
||||
end)
|
|
@ -1,418 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Script to generate the template file and update the translation files.
|
||||
# Copy the script into the mod or modpack root folder and run it there.
|
||||
#
|
||||
# Copyright (C) 2019 Joachim Stolberg, 2020 FaceDeer, 2020 Louis Royer
|
||||
# LGPLv2.1+
|
||||
|
||||
from __future__ import print_function
|
||||
import os, fnmatch, re, shutil, errno
|
||||
from sys import argv as _argv
|
||||
|
||||
# Running params
|
||||
params = {"recursive": False,
|
||||
"help": False,
|
||||
"mods": False,
|
||||
"verbose": False,
|
||||
"folders": []
|
||||
}
|
||||
# Available CLI options
|
||||
options = {"recursive": ['--recursive', '-r'],
|
||||
"help": ['--help', '-h'],
|
||||
"mods": ['--installed-mods'],
|
||||
"verbose": ['--verbose', '-v']
|
||||
}
|
||||
|
||||
# Strings longer than this will have extra space added between
|
||||
# them in the translation files to make it easier to distinguish their
|
||||
# beginnings and endings at a glance
|
||||
doublespace_threshold = 60
|
||||
|
||||
def set_params_folders(tab: list):
|
||||
'''Initialize params["folders"] from CLI arguments.'''
|
||||
# Discarding argument 0 (tool name)
|
||||
for param in tab[1:]:
|
||||
stop_param = False
|
||||
for option in options:
|
||||
if param in options[option]:
|
||||
stop_param = True
|
||||
break
|
||||
if not stop_param:
|
||||
params["folders"].append(os.path.abspath(param))
|
||||
|
||||
def set_params(tab: list):
|
||||
'''Initialize params from CLI arguments.'''
|
||||
for option in options:
|
||||
for option_name in options[option]:
|
||||
if option_name in tab:
|
||||
params[option] = True
|
||||
break
|
||||
|
||||
def print_help(name):
|
||||
'''Prints some help message.'''
|
||||
print(f'''SYNOPSIS
|
||||
{name} [OPTIONS] [PATHS...]
|
||||
DESCRIPTION
|
||||
{', '.join(options["help"])}
|
||||
prints this help message
|
||||
{', '.join(options["recursive"])}
|
||||
run on all subfolders of paths given
|
||||
{', '.join(options["mods"])}
|
||||
run on locally installed modules
|
||||
{', '.join(options["verbose"])}
|
||||
add output information
|
||||
''')
|
||||
|
||||
|
||||
def main():
|
||||
'''Main function'''
|
||||
set_params(_argv)
|
||||
set_params_folders(_argv)
|
||||
if params["help"]:
|
||||
print_help(_argv[0])
|
||||
elif params["recursive"] and params["mods"]:
|
||||
print("Option --installed-mods is incompatible with --recursive")
|
||||
else:
|
||||
# Add recursivity message
|
||||
print("Running ", end='')
|
||||
if params["recursive"]:
|
||||
print("recursively ", end='')
|
||||
# Running
|
||||
if params["mods"]:
|
||||
print(f"on all locally installed modules in {os.path.abspath('~/.minetest/mods/')}")
|
||||
run_all_subfolders("~/.minetest/mods")
|
||||
elif len(params["folders"]) >= 2:
|
||||
print("on folder list:", params["folders"])
|
||||
for f in params["folders"]:
|
||||
if params["recursive"]:
|
||||
run_all_subfolders(f)
|
||||
else:
|
||||
update_folder(f)
|
||||
elif len(params["folders"]) == 1:
|
||||
print("on folder", params["folders"][0])
|
||||
if params["recursive"]:
|
||||
run_all_subfolders(params["folders"][0])
|
||||
else:
|
||||
update_folder(params["folders"][0])
|
||||
else:
|
||||
print("on folder", os.path.abspath("./"))
|
||||
if params["recursive"]:
|
||||
run_all_subfolders(os.path.abspath("./"))
|
||||
else:
|
||||
update_folder(os.path.abspath("./"))
|
||||
|
||||
#group 2 will be the string, groups 1 and 3 will be the delimiters (" or ')
|
||||
#See https://stackoverflow.com/questions/46967465/regex-match-text-in-either-single-or-double-quote
|
||||
pattern_lua = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL)
|
||||
pattern_lua_bracketed = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL)
|
||||
|
||||
# Handles "concatenation" .. " of strings"
|
||||
pattern_concat = re.compile(r'["\'][\s]*\.\.[\s]*["\']', re.DOTALL)
|
||||
|
||||
pattern_tr = re.compile(r'(.+?[^@])=(.*)')
|
||||
pattern_name = re.compile(r'^name[ ]*=[ ]*([^ \n]*)')
|
||||
pattern_tr_filename = re.compile(r'\.tr$')
|
||||
pattern_po_language_code = re.compile(r'(.*)\.po$')
|
||||
|
||||
#attempt to read the mod's name from the mod.conf file. Returns None on failure
|
||||
def get_modname(folder):
|
||||
try:
|
||||
with open(os.path.join(folder, "mod.conf"), "r", encoding='utf-8') as mod_conf:
|
||||
for line in mod_conf:
|
||||
match = pattern_name.match(line)
|
||||
if match:
|
||||
return match.group(1)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return None
|
||||
|
||||
#If there are already .tr files in /locale, returns a list of their names
|
||||
def get_existing_tr_files(folder):
|
||||
out = []
|
||||
for root, dirs, files in os.walk(os.path.join(folder, 'locale/')):
|
||||
for name in files:
|
||||
if pattern_tr_filename.search(name):
|
||||
out.append(name)
|
||||
return out
|
||||
|
||||
# A series of search and replaces that massage a .po file's contents into
|
||||
# a .tr file's equivalent
|
||||
def process_po_file(text):
|
||||
# The first three items are for unused matches
|
||||
text = re.sub(r'#~ msgid "', "", text)
|
||||
text = re.sub(r'"\n#~ msgstr ""\n"', "=", text)
|
||||
text = re.sub(r'"\n#~ msgstr "', "=", text)
|
||||
# comment lines
|
||||
text = re.sub(r'#.*\n', "", text)
|
||||
# converting msg pairs into "=" pairs
|
||||
text = re.sub(r'msgid "', "", text)
|
||||
text = re.sub(r'"\nmsgstr ""\n"', "=", text)
|
||||
text = re.sub(r'"\nmsgstr "', "=", text)
|
||||
# various line breaks and escape codes
|
||||
text = re.sub(r'"\n"', "", text)
|
||||
text = re.sub(r'"\n', "\n", text)
|
||||
text = re.sub(r'\\"', '"', text)
|
||||
text = re.sub(r'\\n', '@n', text)
|
||||
# remove header text
|
||||
text = re.sub(r'=Project-Id-Version:.*\n', "", text)
|
||||
# remove double-spaced lines
|
||||
text = re.sub(r'\n\n', '\n', text)
|
||||
return text
|
||||
|
||||
# Go through existing .po files and, if a .tr file for that language
|
||||
# *doesn't* exist, convert it and create it.
|
||||
# The .tr file that results will subsequently be reprocessed so
|
||||
# any "no longer used" strings will be preserved.
|
||||
# Note that "fuzzy" tags will be lost in this process.
|
||||
def process_po_files(folder, modname):
|
||||
for root, dirs, files in os.walk(os.path.join(folder, 'locale/')):
|
||||
for name in files:
|
||||
code_match = pattern_po_language_code.match(name)
|
||||
if code_match == None:
|
||||
continue
|
||||
language_code = code_match.group(1)
|
||||
tr_name = modname + "." + language_code + ".tr"
|
||||
tr_file = os.path.join(root, tr_name)
|
||||
if os.path.exists(tr_file):
|
||||
if params["verbose"]:
|
||||
print(f"{tr_name} already exists, ignoring {name}")
|
||||
continue
|
||||
fname = os.path.join(root, name)
|
||||
with open(fname, "r", encoding='utf-8') as po_file:
|
||||
if params["verbose"]:
|
||||
print(f"Importing translations from {name}")
|
||||
text = process_po_file(po_file.read())
|
||||
with open(tr_file, "wt", encoding='utf-8') as tr_out:
|
||||
tr_out.write(text)
|
||||
|
||||
# from https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python/600612#600612
|
||||
# Creates a directory if it doesn't exist, silently does
|
||||
# nothing if it already exists
|
||||
def mkdir_p(path):
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except OSError as exc: # Python >2.5
|
||||
if exc.errno == errno.EEXIST and os.path.isdir(path):
|
||||
pass
|
||||
else: raise
|
||||
|
||||
# Converts the template dictionary to a text to be written as a file
|
||||
# dKeyStrings is a dictionary of localized string to source file sets
|
||||
# dOld is a dictionary of existing translations and comments from
|
||||
# the previous version of this text
|
||||
def strings_to_text(dkeyStrings, dOld, mod_name):
|
||||
lOut = [f"# textdomain: {mod_name}\n"]
|
||||
|
||||
dGroupedBySource = {}
|
||||
|
||||
for key in dkeyStrings:
|
||||
sourceList = list(dkeyStrings[key])
|
||||
sourceList.sort()
|
||||
sourceString = "\n".join(sourceList)
|
||||
listForSource = dGroupedBySource.get(sourceString, [])
|
||||
listForSource.append(key)
|
||||
dGroupedBySource[sourceString] = listForSource
|
||||
|
||||
lSourceKeys = list(dGroupedBySource.keys())
|
||||
lSourceKeys.sort()
|
||||
for source in lSourceKeys:
|
||||
localizedStrings = dGroupedBySource[source]
|
||||
localizedStrings.sort()
|
||||
lOut.append("")
|
||||
lOut.append(source)
|
||||
lOut.append("")
|
||||
for localizedString in localizedStrings:
|
||||
val = dOld.get(localizedString, {})
|
||||
translation = val.get("translation", "")
|
||||
comment = val.get("comment")
|
||||
if len(localizedString) > doublespace_threshold and not lOut[-1] == "":
|
||||
lOut.append("")
|
||||
if comment != None:
|
||||
lOut.append(comment)
|
||||
lOut.append(f"{localizedString}={translation}")
|
||||
if len(localizedString) > doublespace_threshold:
|
||||
lOut.append("")
|
||||
|
||||
|
||||
unusedExist = False
|
||||
for key in dOld:
|
||||
if key not in dkeyStrings:
|
||||
val = dOld[key]
|
||||
translation = val.get("translation")
|
||||
comment = val.get("comment")
|
||||
# only keep an unused translation if there was translated
|
||||
# text or a comment associated with it
|
||||
if translation != None and (translation != "" or comment):
|
||||
if not unusedExist:
|
||||
unusedExist = True
|
||||
lOut.append("\n\n##### not used anymore #####\n")
|
||||
if len(key) > doublespace_threshold and not lOut[-1] == "":
|
||||
lOut.append("")
|
||||
if comment != None:
|
||||
lOut.append(comment)
|
||||
lOut.append(f"{key}={translation}")
|
||||
if len(key) > doublespace_threshold:
|
||||
lOut.append("")
|
||||
return "\n".join(lOut) + '\n'
|
||||
|
||||
# Writes a template.txt file
|
||||
# dkeyStrings is the dictionary returned by generate_template
|
||||
def write_template(templ_file, dkeyStrings, mod_name):
|
||||
# read existing template file to preserve comments
|
||||
existing_template = import_tr_file(templ_file)
|
||||
|
||||
text = strings_to_text(dkeyStrings, existing_template[0], mod_name)
|
||||
mkdir_p(os.path.dirname(templ_file))
|
||||
with open(templ_file, "wt", encoding='utf-8') as template_file:
|
||||
template_file.write(text)
|
||||
|
||||
|
||||
# Gets all translatable strings from a lua file
|
||||
def read_lua_file_strings(lua_file):
|
||||
lOut = []
|
||||
with open(lua_file, encoding='utf-8') as text_file:
|
||||
text = text_file.read()
|
||||
#TODO remove comments here
|
||||
|
||||
text = re.sub(pattern_concat, "", text)
|
||||
|
||||
strings = []
|
||||
for s in pattern_lua.findall(text):
|
||||
strings.append(s[1])
|
||||
for s in pattern_lua_bracketed.findall(text):
|
||||
strings.append(s)
|
||||
|
||||
for s in strings:
|
||||
s = re.sub(r'"\.\.\s+"', "", s)
|
||||
s = re.sub("@[^@=0-9]", "@@", s)
|
||||
s = s.replace('\\"', '"')
|
||||
s = s.replace("\\'", "'")
|
||||
s = s.replace("\n", "@n")
|
||||
s = s.replace("\\n", "@n")
|
||||
s = s.replace("=", "@=")
|
||||
lOut.append(s)
|
||||
return lOut
|
||||
|
||||
# Gets strings from an existing translation file
|
||||
# returns both a dictionary of translations
|
||||
# and the full original source text so that the new text
|
||||
# can be compared to it for changes.
|
||||
def import_tr_file(tr_file):
|
||||
dOut = {}
|
||||
text = None
|
||||
if os.path.exists(tr_file):
|
||||
with open(tr_file, "r", encoding='utf-8') as existing_file :
|
||||
# save the full text to allow for comparison
|
||||
# of the old version with the new output
|
||||
text = existing_file.read()
|
||||
existing_file.seek(0)
|
||||
# a running record of the current comment block
|
||||
# we're inside, to allow preceeding multi-line comments
|
||||
# to be retained for a translation line
|
||||
latest_comment_block = None
|
||||
for line in existing_file.readlines():
|
||||
line = line.rstrip('\n')
|
||||
if line[:3] == "###":
|
||||
# Reset comment block if we hit a header
|
||||
latest_comment_block = None
|
||||
continue
|
||||
if line[:1] == "#":
|
||||
# Save the comment we're inside
|
||||
if not latest_comment_block:
|
||||
latest_comment_block = line
|
||||
else:
|
||||
latest_comment_block = latest_comment_block + "\n" + line
|
||||
continue
|
||||
match = pattern_tr.match(line)
|
||||
if match:
|
||||
# this line is a translated line
|
||||
outval = {}
|
||||
outval["translation"] = match.group(2)
|
||||
if latest_comment_block:
|
||||
# if there was a comment, record that.
|
||||
outval["comment"] = latest_comment_block
|
||||
latest_comment_block = None
|
||||
dOut[match.group(1)] = outval
|
||||
return (dOut, text)
|
||||
|
||||
# Walks all lua files in the mod folder, collects translatable strings,
|
||||
# and writes it to a template.txt file
|
||||
# Returns a dictionary of localized strings to source file sets
|
||||
# that can be used with the strings_to_text function.
|
||||
def generate_template(folder, mod_name):
|
||||
dOut = {}
|
||||
for root, dirs, files in os.walk(folder):
|
||||
for name in files:
|
||||
if fnmatch.fnmatch(name, "*.lua"):
|
||||
fname = os.path.join(root, name)
|
||||
found = read_lua_file_strings(fname)
|
||||
if params["verbose"]:
|
||||
print(f"{fname}: {str(len(found))} translatable strings")
|
||||
|
||||
for s in found:
|
||||
sources = dOut.get(s, set())
|
||||
sources.add(f"### {os.path.basename(fname)} ###")
|
||||
dOut[s] = sources
|
||||
|
||||
if len(dOut) == 0:
|
||||
return None
|
||||
templ_file = os.path.join(folder, "locale/template.txt")
|
||||
write_template(templ_file, dOut, mod_name)
|
||||
return dOut
|
||||
|
||||
# Updates an existing .tr file, copying the old one to a ".old" file
|
||||
# if any changes have happened
|
||||
# dNew is the data used to generate the template, it has all the
|
||||
# currently-existing localized strings
|
||||
def update_tr_file(dNew, mod_name, tr_file):
|
||||
if params["verbose"]:
|
||||
print(f"updating {tr_file}")
|
||||
|
||||
tr_import = import_tr_file(tr_file)
|
||||
dOld = tr_import[0]
|
||||
textOld = tr_import[1]
|
||||
|
||||
textNew = strings_to_text(dNew, dOld, mod_name)
|
||||
|
||||
if textOld and textOld != textNew:
|
||||
print(f"{tr_file} has changed.")
|
||||
shutil.copyfile(tr_file, f"{tr_file}.old")
|
||||
|
||||
with open(tr_file, "w", encoding='utf-8') as new_tr_file:
|
||||
new_tr_file.write(textNew)
|
||||
|
||||
# Updates translation files for the mod in the given folder
|
||||
def update_mod(folder):
|
||||
modname = get_modname(folder)
|
||||
if modname is not None:
|
||||
process_po_files(folder, modname)
|
||||
print(f"Updating translations for {modname}")
|
||||
data = generate_template(folder, modname)
|
||||
if data == None:
|
||||
print(f"No translatable strings found in {modname}")
|
||||
else:
|
||||
for tr_file in get_existing_tr_files(folder):
|
||||
update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file))
|
||||
else:
|
||||
print("Unable to find modname in folder " + folder)
|
||||
|
||||
# Determines if the folder being pointed to is a mod or a mod pack
|
||||
# and then runs update_mod accordingly
|
||||
def update_folder(folder):
|
||||
is_modpack = os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf"))
|
||||
if is_modpack:
|
||||
subfolders = [f.path for f in os.scandir(folder) if f.is_dir()]
|
||||
for subfolder in subfolders:
|
||||
update_mod(subfolder + "/")
|
||||
else:
|
||||
update_mod(folder)
|
||||
print("Done.")
|
||||
|
||||
def run_all_subfolders(folder):
|
||||
for modfolder in [f.path for f in os.scandir(folder) if f.is_dir()]:
|
||||
update_folder(modfolder + "/")
|
||||
|
||||
|
||||
main()
|
|
@ -1,6 +0,0 @@
|
|||
commoditymarket = {}
|
||||
|
||||
local MP = minetest.get_modpath(minetest.get_current_modname())
|
||||
dofile(MP.."/formspecs.lua")
|
||||
dofile(MP.."/market.lua")
|
||||
dofile(MP.."/doc.lua")
|
|
@ -1,176 +0,0 @@
|
|||
# textdomain: commoditymarket
|
||||
|
||||
|
||||
### doc.lua ###
|
||||
|
||||
#long-form description of how markets work, for documentation
|
||||
At the core of how a market operates are "buy" and "sell" orders. A buy order is an announcement to the world that you are interested in purchasing a certain quantity of item and are willing to pay a certain amount of currency in exchange for each unit of that item. Conversely, a sell order is an announcement to the world that you are interested in selling a certain quantity of item and will accept a certain amount of currency in exchange for each unit of that item.@n@nThe market price of an item is determined by where the existing buy and sell orders for that item intersect. When you offer to buy an item for a price that someone is offering to sell it at, the item is transferred to you and currency is transferred from your account to theirs to cover the cost. The market will keep track of the most recent price that an item was successfully sold for, but note that this information is for historical interest only - there's no guarantee that anyone is currently willing to match the historical price.@n@nWhen an item is selected in the upper list, the currently existing buy and sell orders for that item will be displayed in the lower list. Sell orders are listed first in descending price, followed by buy orders in ascending price. The current market price will be somewhere in between the lowest sell order and the highest buy order. If you wish to cancel a buy or sell order that you've placed for an item, double-click on the order and the item or currency that you put into that order will be returned to your inventory.@n@nIf you place a buy order and there are already sell orders for the item that meet or are below your price, some or all of your buy order might be immediately fulfilled. Your purchases will be made at the price that the sell orders have been set to - if you were willing to pay 15 units of currency per item but someone was already offering to sell for 2 units of currency per item, you only pay 2 units for each of that offer's items. If there aren't enough compatible sell orders to fulfill your buy order, the remainder will be placed into the market and made available for future sellers to see and fulfill if they agree to your price. Your buy order will immediately deduct the currency required for it from your account's balance, but if you cancel your order you will get that currency back - it's not gone until the order is actually fulfilled.@n@nIf you place a sell order and there are already buy orders that meet or exceed your price, some or all of your sell order may be immediately fulfilled. You'll be paid the price that the buyers are offering rather than the amount you're demanding. If any of your sell offer is left unfulfilled, the sell order will be added to the market for future buyers to see. The items for this offer will be immediately taken from your market inventory but if you cancel your order you will get those items back.=
|
||||
|
||||
#documentation tab text
|
||||
Commodity Markets=
|
||||
|
||||
#long-form description of how markets work, for documentation
|
||||
Each player's account has an inventory that serves as a holding area for items that are destined to be sold or that have been bought by the player but not yet retrieved. This inventory is a bit different from the standard Minetest inventory in that it doesn't hold item "stacks", it just tracks the total number of that item present. Some markets allow for extremely large quantities of an item to be stored here for sale.@n@nTo add an item to your market inventory for eventual sale either shift-click on the item in your player inventory or drag the item stack to the inventory slot below the main market inventory list. Some markets may have restrictions on what items can be bought and sold, if an item is not valid for that market it won't go into the market's inventory. Some items are considered "currency" and will add to your account's currency balance instead of being listed in your market inventory.@n@nTools cannot be added to the market inventory if they have any wear on them. The market also can't handle items with attached metadata such as books that have had text added to them.@n@nTo remove an item from your market inventory, double-click in it in the market inventory list. As much of the item as can fit into your player inventory will be transferred to you, with any remainder staying behind in the market inventory. To withdraw currency from your market balance type the amount you'd like to withdraw in the field next to the "Withdraw" button. The currency will be converted into items and added to your player inventory, with whatever cannot be converted remaining behind in your market balance.=
|
||||
|
||||
#documentation category description
|
||||
Game-wide marketplaces where goods can be bought and sold at prices of your choice.=
|
||||
|
||||
#title of documentation page
|
||||
User Interface: Inventory=
|
||||
#title of documentation page
|
||||
User Interface: Orders=
|
||||
|
||||
### formspecs.lua ###
|
||||
|
||||
#tooltip
|
||||
All the items you've transfered to the market to sell and the items you've@npurchased with buy orders. Double-click on an item to bring it back into your@npersonal inventory.=
|
||||
|
||||
#tooltip
|
||||
Apply search to outputs.=
|
||||
Balance:=
|
||||
#label
|
||||
#button label
|
||||
Buy=
|
||||
#table column header
|
||||
Buy Max=
|
||||
#table column header
|
||||
Buy Vol=
|
||||
#tooltip
|
||||
Clear search.=
|
||||
Days Old=
|
||||
#table column header
|
||||
Description=
|
||||
Description:=
|
||||
#label for inventory drop target. The hard return is needed to fit it into the UI
|
||||
Drop items here to@nadd to your account=
|
||||
#tooltip
|
||||
Enter substring to search item identifiers for.=
|
||||
|
||||
#tooltip
|
||||
Enter the amount of currency you'd like to withdraw then click the 'Withdraw'@nbutton to convert it into items and transfer it to your personal inventory.=
|
||||
|
||||
How many days ago this order was placed.=
|
||||
#label
|
||||
In inventory:=
|
||||
#table column header
|
||||
Inventory=
|
||||
#label
|
||||
Inventory limit:=
|
||||
#table column header
|
||||
Item=
|
||||
#table column header
|
||||
Last Price=
|
||||
|
||||
#tooltip
|
||||
Log entries in yellow are new since last time you marked your log as read.=
|
||||
|
||||
Mark logs as read=
|
||||
#tab label
|
||||
Market Orders=
|
||||
#tooltip
|
||||
Maximum price being offered to buy one of these.=
|
||||
#tooltip
|
||||
Minimum price being demanded to sell one of these.=
|
||||
#checkbox label
|
||||
My orders=
|
||||
#shown if there are no transactions the player has participated in to list in the log
|
||||
No logged activities in this market yet.=
|
||||
#tooltip
|
||||
Number of items available for sale in the market.=
|
||||
#tooltip
|
||||
Number of items there's demand for in the market.=
|
||||
|
||||
#transaction log entry. @1 is a day number, @2 is a player name, @3 is a quantity of items, @4 is the item name, @5 is a player name, @6 is a currency symbol, @7 is a number (price), @8 is the currency symbol again and @9 is a number (price)
|
||||
On day @1 @2 sold @3 @4 to @5 at @6@7 each for a total of @8@9.=
|
||||
|
||||
Order=
|
||||
Player=
|
||||
Price=
|
||||
#tooltip
|
||||
Price paid for one of these the last time one was sold.=
|
||||
Price per=
|
||||
#table column header, field label
|
||||
Quantity=
|
||||
|
||||
#tooltip
|
||||
Quantity of this item that you have in your inventory ready to sell.=
|
||||
|
||||
Select an item to view or place orders.=
|
||||
|
||||
#tooltip
|
||||
Select this to show only the markets where you have either a buy or a sell order pending.=
|
||||
|
||||
Sell=
|
||||
#table column header
|
||||
Sell Min=
|
||||
#table column header
|
||||
Sell Vol=
|
||||
Sell limit:=
|
||||
#checkbox label
|
||||
Show Icons=
|
||||
#checkbox label
|
||||
Show Itemnames=
|
||||
|
||||
The name of the player who placed this order.@nDouble-click your own orders to cancel them.=
|
||||
|
||||
The price per item in this order.=
|
||||
|
||||
The total amount of items available at this price accounting for the other orders also currently being offered.=
|
||||
|
||||
The total amount of items in this particular order.=
|
||||
|
||||
#tooltip
|
||||
This market limits the total number of items a given seller can have for sale at a time.@nYou have @1 items remaining. Cancel old sell orders to free up space.=
|
||||
|
||||
Total Volume=
|
||||
#undefined item
|
||||
Unknown Item=
|
||||
|
||||
#tooltip
|
||||
Use these fields to enter buy and sell orders for the selected item.=
|
||||
|
||||
#button label
|
||||
Withdraw=
|
||||
|
||||
#tooltip
|
||||
You can still receive purchased items if you've exceeded your inventory limit,@nbut you won't be able to transfer items from your personal inventory into@nthe market until you've emptied it back down below the limit again.=
|
||||
|
||||
#tab label
|
||||
Your Inventory=
|
||||
Your Recent Purchases and Sales:=
|
||||
someone=
|
||||
you=
|
||||
yourself=
|
||||
|
||||
### market.lua ###
|
||||
|
||||
1 @1 @= @2@3=
|
||||
|
||||
A market where orders to buy or sell items can be placed and fulfilled.=
|
||||
|
||||
Add all registered items to the provided market=
|
||||
Currency item values:=
|
||||
Market=
|
||||
Market has unlimited inventory space.=
|
||||
Market inventory is limited to @1 items.=
|
||||
Market supports unlimited pending sell orders.=
|
||||
Purging item: @1 from market: @2=
|
||||
Total pending sell orders are limited to @1 items.=
|
||||
You can't afford that many of this item.=
|
||||
You can't pay less than nothing for an item.=
|
||||
You can't sell fewer than one item.=
|
||||
You can't sell items for a negative price.=
|
||||
|
||||
You don't have enough of that item in your inventory to post this sell order.=
|
||||
|
||||
You have to buy at least one item.=
|
||||
|
||||
You have too many items listed for sale in this market, please cancel some sell orders to make room for new ones.=
|
||||
|
||||
list all registered markets=
|
||||
|
||||
remove item from market. All existing buys and sells will be canceled.=
|
||||
|
||||
removes all unknown items from all markets. All existing buys and sells for those items will be canceled.=
|
||||
|
||||
show market interface=
|
|
@ -1,718 +0,0 @@
|
|||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
|
||||
commoditymarket.registered_markets = {}
|
||||
local log_length_limit = 30
|
||||
|
||||
-- from http://lua-users.org/wiki/BinaryInsert
|
||||
--[[
|
||||
table.bininsert( table, value [, comp] )
|
||||
|
||||
Inserts a given value through BinaryInsert into the table sorted by [, comp].
|
||||
|
||||
If 'comp' is given, then it must be a function that receives
|
||||
two table elements, and returns true when the first is less
|
||||
than the second, e.g. comp = function(a, b) return a > b end,
|
||||
will give a sorted table, with the biggest value on position 1.
|
||||
[, comp] behaves as in table.sort(table, value [, comp])
|
||||
returns the index where 'value' was inserted
|
||||
]]--
|
||||
local comp_default = function(a, b) return a < b end
|
||||
function table.bininsert(t, value, comp)
|
||||
-- Initialise compare function
|
||||
local comp = comp or comp_default
|
||||
-- Initialise numbers
|
||||
local iStart, iEnd, iMid, iState = 1, #t, 1, 0
|
||||
-- Get insert position
|
||||
while iStart <= iEnd do
|
||||
-- calculate middle
|
||||
iMid = math.floor( (iStart+iEnd)/2 )
|
||||
-- compare
|
||||
if comp(value, t[iMid]) then
|
||||
iEnd, iState = iMid - 1, 0
|
||||
else
|
||||
iStart, iState = iMid + 1, 1
|
||||
end
|
||||
end
|
||||
local target = iMid+iState
|
||||
table.insert(t, target, value)
|
||||
return target
|
||||
end
|
||||
|
||||
-- lowest price first
|
||||
local buy_comp = function(order1, order2)
|
||||
local price1 = order1.price
|
||||
local price2 = order2.price
|
||||
if price1 < price2 then
|
||||
return true
|
||||
elseif price1 == price2 and order1.timestamp < order2.timestamp then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
-- highest price first
|
||||
local sell_comp = function(order1, order2)
|
||||
local price1 = order1.price
|
||||
local price2 = order2.price
|
||||
if price1 > price2 then
|
||||
return true
|
||||
elseif price1 == price2 and order1.timestamp < order2.timestamp then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---------------------------------
|
||||
|
||||
local get_account = function(market, player_name)
|
||||
local account = market.player_accounts[player_name]
|
||||
if account then
|
||||
return account
|
||||
end
|
||||
account = {}
|
||||
account.search = ""
|
||||
account.name = player_name
|
||||
account.balance = 0 -- currency
|
||||
account.inventory = {} -- items stored in the market inventory that aren't part of sell orders yet. stored as "[item] = count"
|
||||
account.filter_participating = "false"
|
||||
account.log = {} -- might want to use a more sophisticated queue, but this isn't going to be a big list so that's more trouble than it's worth right now.
|
||||
market.player_accounts[player_name] = account
|
||||
return account
|
||||
end
|
||||
|
||||
-- Caution: the data structures produced by sale logging caused me to discover
|
||||
-- issue https://github.com/minetest/minetest/issues/8719 with minetest.serialize()
|
||||
-- I'm working around it by using the code in persistence.lua instead
|
||||
local log_sale = function(item, quantity, price, purchaser, seller)
|
||||
local log_entry = {item=item, quantity=quantity, price=price, purchaser=purchaser, seller=seller, timestamp = minetest.get_gametime()}
|
||||
local purchaser_log = purchaser.log
|
||||
local seller_log = seller.log
|
||||
table.insert(purchaser_log, log_entry)
|
||||
if #purchaser_log > log_length_limit then
|
||||
table.remove(purchaser_log, 1)
|
||||
end
|
||||
if (purchaser ~= seller) then
|
||||
table.insert(seller_log, log_entry)
|
||||
if #seller_log > log_length_limit then
|
||||
table.remove(seller_log, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local remove_orders_by_account = function(orders, account)
|
||||
if not orders then return end
|
||||
local i = 1
|
||||
while i < #orders do
|
||||
local order = orders[i]
|
||||
if order.account == account then
|
||||
table.remove(orders, i)
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local remove_account = function(player_name)
|
||||
local account = player_accounts[player_name]
|
||||
if account == nil then
|
||||
return
|
||||
end
|
||||
|
||||
player_accounts[player_name] = nil
|
||||
for item, lists in pairs(market) do
|
||||
remove_orders_by_account(lists.buy_orders, account)
|
||||
remove_orders_by_account(lists.sell_orders, account)
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------------------------
|
||||
|
||||
local add_inventory_to_account = function(market, account, item, quantity)
|
||||
if quantity < 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
if market.def.currency[item] then
|
||||
account.balance = account.balance + market.def.currency[item] * quantity
|
||||
else
|
||||
account.inventory[item] = (account.inventory[item] or 0) + quantity
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local remove_inventory_from_account = function(account, item, quantity)
|
||||
if quantity < 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
local inventory = account.inventory
|
||||
local current_quantity = inventory[item] or 0
|
||||
if current_quantity < quantity then
|
||||
return false
|
||||
end
|
||||
|
||||
local new_quantity = current_quantity - quantity
|
||||
if new_quantity == 0 then
|
||||
inventory[item] = nil
|
||||
else
|
||||
inventory[item] = new_quantity
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local remove_order = function(order, array)
|
||||
for i, market_order in ipairs(array) do
|
||||
if order == market_order then
|
||||
table.remove(array, i)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------
|
||||
|
||||
local add_sell = function(market, account, item, price, quantity)
|
||||
price = tonumber(price)
|
||||
quantity = tonumber(quantity)
|
||||
|
||||
local sell_limit = market.def.sell_limit
|
||||
local sell_limit_exceeded
|
||||
if sell_limit then
|
||||
local total_sell = 0
|
||||
for item, orders in pairs(market.orders_for_items) do
|
||||
for _, order in ipairs(orders.sell_orders) do
|
||||
if order.account == account then
|
||||
total_sell = total_sell + order.quantity
|
||||
end
|
||||
end
|
||||
end
|
||||
sell_limit_exceeded = total_sell + quantity > sell_limit
|
||||
end
|
||||
|
||||
-- validate that this sell order is possible
|
||||
if sell_limit_exceeded or price < 0 or quantity < 1 or not remove_inventory_from_account(account, item, quantity) then
|
||||
minetest.sound_play({name = "commoditymarket_error", gain = 0.1}, {to_player=account.name})
|
||||
if sell_limit_exceeded then
|
||||
minetest.chat_send_player(account.name, S("You have too many items listed for sale in this market, please cancel some sell orders to make room for new ones."))
|
||||
elseif price < 0 then
|
||||
minetest.chat_send_player(account.name, S("You can't sell items for a negative price."))
|
||||
elseif quantity < 1 then
|
||||
minetest.chat_send_player(account.name, S("You can't sell fewer than one item."))
|
||||
else
|
||||
minetest.chat_send_player(account.name, S("You don't have enough of that item in your inventory to post this sell order."))
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local buy_market = market.orders_for_items[item].buy_orders
|
||||
local buy_order = buy_market[#buy_market]
|
||||
local current_buy_volume = market.orders_for_items[item].buy_volume
|
||||
|
||||
-- go through existing buy orders that are more expensive than or equal to the price
|
||||
-- we're demanding, selling them at the order's price until we run out of
|
||||
-- buy orders or run out of demand
|
||||
while quantity > 0 and buy_order and buy_order.price >= price do
|
||||
local quantity_to_sell = math.min(buy_order.quantity, quantity)
|
||||
quantity = quantity - quantity_to_sell
|
||||
local earned = quantity_to_sell*buy_order.price
|
||||
account.balance = account.balance + earned
|
||||
add_inventory_to_account(market, buy_order.account, item, quantity_to_sell)
|
||||
buy_order.quantity = buy_order.quantity - quantity_to_sell
|
||||
current_buy_volume = current_buy_volume - quantity_to_sell
|
||||
|
||||
if buy_order.account ~= account then
|
||||
-- don't update the last price if a player is just buying and selling from themselves
|
||||
market.orders_for_items[item].last_price = buy_order.price
|
||||
end
|
||||
|
||||
log_sale(item, quantity_to_sell, buy_order.price, buy_order.account, account)
|
||||
|
||||
if buy_order.quantity == 0 then
|
||||
table.remove(buy_market, #buy_market)
|
||||
end
|
||||
buy_order = buy_market[#buy_market]
|
||||
end
|
||||
market.orders_for_items[item].buy_volume = current_buy_volume
|
||||
|
||||
if quantity > 0 then
|
||||
local sell_market = market.orders_for_items[item].sell_orders
|
||||
|
||||
-- create the order and insert it into order arrays
|
||||
local order = {account=account, price=price, quantity=quantity, timestamp=minetest.get_gametime()}
|
||||
table.bininsert(sell_market, order, sell_comp)
|
||||
market.orders_for_items[item].sell_volume = market.orders_for_items[item].sell_volume + quantity
|
||||
end
|
||||
|
||||
minetest.sound_play({name = "commoditymarket_register_opened", gain = 0.1}, {to_player=account.name})
|
||||
return true
|
||||
end
|
||||
|
||||
local cancel_sell = function(market, item, order)
|
||||
local account = order.account
|
||||
local quantity = order.quantity
|
||||
|
||||
local sell_market = market.orders_for_items[item].sell_orders
|
||||
|
||||
remove_order(order, sell_market)
|
||||
market.orders_for_items[item].sell_volume = market.orders_for_items[item].sell_volume - quantity
|
||||
add_inventory_to_account(market, account, item, quantity)
|
||||
|
||||
minetest.sound_play({name = "commoditymarket_register_closed", gain = 0.1}, {to_player=account.name})
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------
|
||||
|
||||
local test_buy = function(market, balance, item, price, quantity)
|
||||
local sell_market = market.orders_for_items[item].sell_orders
|
||||
local test_quantity = quantity
|
||||
local test_balance = balance
|
||||
local i = 0
|
||||
local sell_order = sell_market[#sell_market]
|
||||
while test_quantity > 0 and sell_order and sell_order.price <= price do
|
||||
local quantity_to_buy = math.min(sell_order.quantity, test_quantity)
|
||||
test_quantity = test_quantity - quantity_to_buy
|
||||
test_balance = test_balance - quantity_to_buy*sell_order.price
|
||||
i = i + 1
|
||||
sell_order = sell_market[#sell_market-i]
|
||||
end
|
||||
local spent = balance - test_balance
|
||||
test_balance = test_balance - test_quantity*price
|
||||
if test_balance < 0 then
|
||||
return false, spent, test_quantity
|
||||
end
|
||||
|
||||
return true, spent, test_quantity
|
||||
end
|
||||
|
||||
local add_buy = function(market, account, item, price, quantity)
|
||||
price = tonumber(price)
|
||||
quantity = tonumber(quantity)
|
||||
if price < 0 or quantity < 1 or not test_buy(market, account.balance, item, price, quantity) then
|
||||
minetest.sound_play({name = "commoditymarket_error", gain = 0.1}, {to_player=account.name})
|
||||
if price < 0 then
|
||||
minetest.chat_send_player(account.name, S("You can't pay less than nothing for an item."))
|
||||
elseif quantity < 1 then
|
||||
minetest.chat_send_player(account.name, S("You have to buy at least one item."))
|
||||
else
|
||||
minetest.chat_send_player(account.name, S("You can't afford that many of this item."))
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local sell_market = market.orders_for_items[item].sell_orders
|
||||
local sell_order = sell_market[#sell_market]
|
||||
local current_sell_volume = market.orders_for_items[item].sell_volume
|
||||
|
||||
-- go through existing sell orders that are cheaper than or equal to the price
|
||||
-- we're wanting to offer, buying them up at the offered price until we run out of
|
||||
-- sell orders or run out of supply
|
||||
while quantity > 0 and sell_order and sell_order.price <= price do
|
||||
local quantity_to_buy = math.min(sell_order.quantity, quantity)
|
||||
quantity = quantity - quantity_to_buy
|
||||
local spent = quantity_to_buy*sell_order.price
|
||||
account.balance = account.balance - spent
|
||||
sell_order.account.balance = sell_order.account.balance + spent
|
||||
sell_order.quantity = sell_order.quantity - quantity_to_buy
|
||||
current_sell_volume = current_sell_volume - quantity_to_buy
|
||||
add_inventory_to_account(market, account, item, quantity_to_buy)
|
||||
|
||||
if sell_order.account ~= account then
|
||||
-- don't update the last price if a player is just buying and selling from themselves
|
||||
market.orders_for_items[item].last_price = sell_order.price
|
||||
end
|
||||
|
||||
log_sale(item, quantity_to_buy, sell_order.price, account, sell_order.account)
|
||||
|
||||
-- Sell order completely used up, remove it
|
||||
if sell_order.quantity == 0 then
|
||||
table.remove(sell_market, #sell_market)
|
||||
end
|
||||
|
||||
-- get the next sell order
|
||||
sell_order = sell_market[#sell_market]
|
||||
end
|
||||
market.orders_for_items[item].sell_volume = current_sell_volume
|
||||
|
||||
if quantity > 0 then
|
||||
local buy_market = market.orders_for_items[item].buy_orders
|
||||
-- create the order for the remainder and insert it into order arrays
|
||||
local order = {account=account, price=price, quantity=quantity, timestamp=minetest.get_gametime()}
|
||||
account.balance = account.balance - quantity*price -- buy orders are pre-paid
|
||||
table.bininsert(buy_market, order, buy_comp)
|
||||
market.orders_for_items[item].buy_volume = market.orders_for_items[item].buy_volume + quantity
|
||||
end
|
||||
|
||||
minetest.sound_play({name = "commoditymarket_register_opened", gain = 0.1}, {to_player=account.name})
|
||||
return true
|
||||
end
|
||||
|
||||
local cancel_buy = function(market, item, order)
|
||||
local account = order.account
|
||||
local quantity = order.quantity
|
||||
local price = order.price
|
||||
|
||||
local buy_market = market.orders_for_items[item].buy_orders
|
||||
market.orders_for_items[item].buy_volume = market.orders_for_items[item].buy_volume - quantity
|
||||
|
||||
remove_order(order, buy_market)
|
||||
|
||||
account.balance = account.balance + price*quantity
|
||||
|
||||
minetest.sound_play({name = "commoditymarket_register_closed", gain = 0.1}, {to_player=account.name})
|
||||
end
|
||||
|
||||
local initialize_market_item = function(orders_for_items, item)
|
||||
if orders_for_items[item] == nil then
|
||||
local lists = {}
|
||||
lists.buy_orders = {}
|
||||
lists.sell_orders = {}
|
||||
lists.buy_volume = 0
|
||||
lists.sell_volume = 0
|
||||
lists.item = item
|
||||
-- leave last_price nil to indicate it's never been sold before
|
||||
orders_for_items[item] = lists
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------
|
||||
-- Chat commands
|
||||
|
||||
minetest.register_chatcommand("market.show", {
|
||||
params = "marketname",
|
||||
privs = {server=true},
|
||||
description = S("show market interface"),
|
||||
func = function(name, param)
|
||||
local market = commoditymarket.registered_markets[param]
|
||||
if market == nil then return end
|
||||
local formspec = market:get_formspec(market:get_account(name))
|
||||
minetest.show_formspec(name, "commoditymarket:"..param..":"..name, formspec)
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("market.list", {
|
||||
params = "",
|
||||
privs = {server=true},
|
||||
description = S("list all registered markets"),
|
||||
func = function(name, param)
|
||||
local list = {}
|
||||
for marketname, def in pairs(commoditymarket.registered_markets) do
|
||||
table.insert(list, marketname)
|
||||
end
|
||||
table.sort(list)
|
||||
minetest.chat_send_player(name, "Registered markets: " .. table.concat(list, ", "))
|
||||
end,
|
||||
})
|
||||
|
||||
local remove_market_item = function(market, item)
|
||||
local marketitem = market.orders_for_items[item]
|
||||
if marketitem then
|
||||
local buy_orders = marketitem.buy_orders
|
||||
while #buy_orders > 0 do
|
||||
market:cancel_buy(item, buy_orders[#buy_orders])
|
||||
end
|
||||
local sell_orders = marketitem.sell_orders
|
||||
while #sell_orders > 0 do
|
||||
market:cancel_sell(item, sell_orders[#sell_orders])
|
||||
end
|
||||
market.orders_for_items[item] = nil
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_chatcommand("market.removeitem", {
|
||||
params = "marketname item",
|
||||
privs = {server=true},
|
||||
description = S("remove item from market. All existing buys and sells will be canceled."),
|
||||
func = function(name, param)
|
||||
local params = param:split(" ")
|
||||
if #params ~= 2 then
|
||||
minetest.chat_send_player(name, "Incorrect parameter count")
|
||||
return
|
||||
end
|
||||
local market = commoditymarket.registered_markets[params[1]]
|
||||
if market == nil then
|
||||
minetest.chat_send_player(name, "No such market: " .. params[1])
|
||||
return
|
||||
end
|
||||
remove_market_item(market, params[2])
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("market.purge_unknowns", {
|
||||
params = "",
|
||||
privs = {server=true},
|
||||
description = S("removes all unknown items from all markets. All existing buys and sells for those items will be canceled."),
|
||||
func = function(name, param)
|
||||
for market_name, market in pairs(commoditymarket.registered_markets) do
|
||||
local items_to_remove = {}
|
||||
local items_to_move = {}
|
||||
for item, orders in pairs(market.orders_for_items) do
|
||||
local icon = commoditymarket.get_icon(item)
|
||||
if icon == "unknown_item.png" then
|
||||
table.insert(items_to_remove, item)
|
||||
end
|
||||
end
|
||||
for _, item in ipairs(items_to_remove) do
|
||||
minetest.chat_send_player(name, S("Purging item: @1 from market: @2", tostring(item), market_name))
|
||||
minetest.log("warning", "[commoditymarket] Purging unknown item: " .. tostring(item) .. " from market: " .. market_name)
|
||||
remove_market_item(market, item)
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
-- Used during development and debugging to find items that break the market formspecs when added
|
||||
local debugging_commands = false
|
||||
if debugging_commands then
|
||||
minetest.register_chatcommand("market.addeverything", {
|
||||
params = "marketname",
|
||||
privs = {server=true},
|
||||
description = S("Add all registered items to the provided market"),
|
||||
func = function(name, param)
|
||||
local params = param:split(" ")
|
||||
if #params ~= 1 then
|
||||
minetest.chat_send_player(name, "Incorrect parameter count")
|
||||
return
|
||||
end
|
||||
local market = commoditymarket.registered_markets[params[1]]
|
||||
if market == nil then
|
||||
minetest.chat_send_player(name, "No such market: " .. params[1])
|
||||
return
|
||||
end
|
||||
for item_name, def in pairs(minetest.registered_items) do
|
||||
initialize_market_item(market.orders_for_items, item_name)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- API exposed to the outside world
|
||||
local add_inventory = function(self, player_name, item, quantity)
|
||||
return add_inventory_to_account(self, get_account(self, player_name), item, quantity)
|
||||
end
|
||||
local remove_inventory = function(self, player_name, item, quantity)
|
||||
return remove_inventory_from_account(get_account(self, player_name), item, quantity)
|
||||
end
|
||||
local sell = function(self, player_name, item, quantity, price)
|
||||
return add_sell(self, get_account(self, player_name), item, price, quantity)
|
||||
end
|
||||
local buy = function(self, player_name, item, quantity, price)
|
||||
return add_buy(self, get_account(self, player_name), item, price, quantity)
|
||||
end
|
||||
|
||||
-- Using this instead of minetest.serialize because of https://github.com/minetest/minetest/issues/8719
|
||||
local MP = minetest.get_modpath(minetest.get_current_modname())
|
||||
local persistence_store, persistence_load = dofile(MP.."/persistence.lua")
|
||||
|
||||
local worldpath = minetest.get_worldpath()
|
||||
local load_market_data = function(marketname)
|
||||
local filename = worldpath .. "/market_"..marketname..".lua"
|
||||
return persistence_load(filename)
|
||||
end
|
||||
|
||||
local save_market_data = function(market)
|
||||
local filename = worldpath .. "/market_"..market.name..".lua"
|
||||
local data = {}
|
||||
data.player_accounts = market.player_accounts
|
||||
data.orders_for_items = market.orders_for_items
|
||||
persistence_store(filename, data)
|
||||
return true
|
||||
end
|
||||
|
||||
local make_doc_entry = function() return end
|
||||
if minetest.get_modpath("doc") then
|
||||
make_doc_entry = function(market_name, market_def)
|
||||
local currencies = {}
|
||||
for _, currency_item in ipairs(market_def.currency_ordered) do
|
||||
local item_def = minetest.registered_items[currency_item.item]
|
||||
table.insert(currencies, S("1 @1 = @2@3", item_def.description, market_def.currency_symbol, currency_item.amount))
|
||||
end
|
||||
local inventory_limit
|
||||
if market_def.inventory_limit then
|
||||
inventory_limit = S("Market inventory is limited to @1 items.", market_def.inventory_limit)
|
||||
else
|
||||
inventory_limit = S("Market has unlimited inventory space.")
|
||||
end
|
||||
|
||||
local sell_limit
|
||||
if market_def.sell_limit then
|
||||
sell_limit = S("Total pending sell orders are limited to @1 items.", market_def.inventory_limit)
|
||||
else
|
||||
sell_limit = S("Market supports unlimited pending sell orders.")
|
||||
end
|
||||
|
||||
doc.add_entry("commoditymarket", "market_"..market_name, {
|
||||
name = market_def.description,
|
||||
data = { text = market_def.long_description
|
||||
.."\n\n"
|
||||
..S("Currency item values:") .. "\n " .. table.concat(currencies, "\n ")
|
||||
.."\n\n"
|
||||
..inventory_limit
|
||||
.."\n"
|
||||
..sell_limit
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
commoditymarket.register_market = function(market_name, market_def)
|
||||
assert(not commoditymarket.registered_markets[market_name])
|
||||
assert(market_def.currency)
|
||||
|
||||
market_def.currency_symbol = market_def.currency_symbol or "¤" -- \u{00A4} -- defaults to the generic currency symbol ("scarab")
|
||||
market_def.description = market_def.description or S("Market")
|
||||
market_def.long_description = market_def.long_description or S("A market where orders to buy or sell items can be placed and fulfilled.")
|
||||
|
||||
-- Reprocess currency table into a form easier for the withdraw code to work with
|
||||
market_def.currency_ordered = {}
|
||||
for item, amount in pairs(market_def.currency) do
|
||||
table.insert(market_def.currency_ordered, {item=item, amount=amount})
|
||||
end
|
||||
table.sort(market_def.currency_ordered, function(currency1, currency2) return currency1.amount > currency2.amount end)
|
||||
|
||||
make_doc_entry(market_name, market_def) -- market_def has now been normalized, make documentation for it if doc is installed.
|
||||
|
||||
-- Just in case a developer supplied strings that don't work well in formspecs, escape them now so we don't have to do it
|
||||
-- wherever they're used.
|
||||
market_def.currency_symbol = minetest.formspec_escape(market_def.currency_symbol)
|
||||
market_def.description = minetest.formspec_escape(market_def.description)
|
||||
market_def.long_description = minetest.formspec_escape(market_def.long_description)
|
||||
|
||||
local new_market = {}
|
||||
new_market.def = market_def
|
||||
commoditymarket.registered_markets[market_name] = new_market
|
||||
|
||||
local loaded_data = load_market_data(market_name)
|
||||
if loaded_data then
|
||||
new_market.player_accounts = loaded_data.player_accounts
|
||||
new_market.orders_for_items = loaded_data.orders_for_items
|
||||
else
|
||||
new_market.player_accounts = {}
|
||||
new_market.orders_for_items = {}
|
||||
end
|
||||
|
||||
-- If there's a list of initial items in the market def, initialize them. allow_item can trump this.
|
||||
local initial_items = market_def.initial_items
|
||||
if initial_items then
|
||||
-- defer until after to ensure that all initial items have been registered, so we can guard against invalid items
|
||||
minetest.after(0,
|
||||
function()
|
||||
for _, item in ipairs(initial_items) do
|
||||
if minetest.registered_items[item] and
|
||||
((not market_def.allow_item) or market_def.allow_item(item)) and
|
||||
not market_def.currency[item] then
|
||||
initialize_market_item(new_market.orders_for_items, item)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
market_def.initial_items = nil -- don't need this any more
|
||||
|
||||
new_market.name = market_name
|
||||
|
||||
new_market.add_inventory = add_inventory
|
||||
new_market.remove_inventory = remove_inventory
|
||||
new_market.sell = sell
|
||||
new_market.buy = buy
|
||||
new_market.cancel_sell = cancel_sell
|
||||
new_market.cancel_buy = cancel_buy
|
||||
new_market.get_formspec = commoditymarket.get_formspec
|
||||
new_market.get_account = get_account
|
||||
new_market.save = save_market_data
|
||||
|
||||
-- save markets on shutdown
|
||||
minetest.register_on_shutdown(function() new_market:save() end)
|
||||
|
||||
-- and also every ten minutes, to be on the safe side in case Minetest crashes
|
||||
-- TODO: a more sophisticated approach that checks whether the market data is "dirty" before actually saving
|
||||
local until_next_save = 600
|
||||
minetest.register_globalstep(function(dtime)
|
||||
until_next_save = until_next_save - dtime
|
||||
if until_next_save < 0 then
|
||||
new_market:save()
|
||||
until_next_save = 600
|
||||
end
|
||||
end)
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Detached inventory for adding items into the market
|
||||
|
||||
local inv = minetest.create_detached_inventory("commoditymarket:"..market_name, {
|
||||
allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
|
||||
return 0
|
||||
end,
|
||||
allow_put = function(inv, listname, index, stack, player)
|
||||
local item = stack:get_name()
|
||||
|
||||
-- reject unknown items
|
||||
if minetest.registered_items[item] == nil then
|
||||
return 0
|
||||
end
|
||||
|
||||
-- Currency items are always allowed
|
||||
if new_market.def.currency[item] then
|
||||
return stack:get_count()
|
||||
end
|
||||
|
||||
-- only new tools, no used tools
|
||||
if stack:get_wear() ~= 0 then
|
||||
return 0
|
||||
end
|
||||
|
||||
--nothing with metadata permitted
|
||||
local meta = stack:get_meta():to_table()
|
||||
local fields = meta.fields
|
||||
local inventory = meta.inventory
|
||||
if (fields and next(fields)) or (inventory and next(inventory)) then
|
||||
return 0
|
||||
end
|
||||
|
||||
-- If there's no allow_item function defined, allow everything. Otherwise check if the item is allowed
|
||||
if (not market_def.allow_item) or market_def.allow_item(item) then
|
||||
local allowed_count = stack:get_count()
|
||||
|
||||
if market_def.inventory_limit then
|
||||
-- limit additions to the inventory_limit, if there is one
|
||||
local current_count = 0
|
||||
for _, inventory_quantity in pairs(new_market:get_account(player:get_player_name()).inventory) do
|
||||
current_count = current_count + inventory_quantity
|
||||
end
|
||||
allowed_count = math.min(allowed_count, allowed_count + market_def.inventory_limit - (current_count+allowed_count))
|
||||
if allowed_count <= 0 then return 0 end
|
||||
end
|
||||
|
||||
--ensures the item is in the market listing if it wasn't before
|
||||
initialize_market_item(new_market.orders_for_items, item)
|
||||
return allowed_count
|
||||
end
|
||||
return 0
|
||||
end,
|
||||
allow_take = function(inv, listname, index, stack, player)
|
||||
return 0
|
||||
end,
|
||||
on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
|
||||
end,
|
||||
on_take = function(inv, listname, index, stack, player)
|
||||
end,
|
||||
on_put = function(inv, listname, index, stack, player)
|
||||
if listname == "add" then
|
||||
local item = stack:get_name()
|
||||
local count = stack:get_count()
|
||||
new_market:add_inventory(player:get_player_name(), item, count)
|
||||
inv:set_list("add", {})
|
||||
local name = player:get_player_name()
|
||||
local formspec = new_market:get_formspec(new_market:get_account(name))
|
||||
minetest.show_formspec(name, "commoditymarket:"..market_name..":"..name, formspec)
|
||||
end
|
||||
end
|
||||
})
|
||||
inv:set_size("add", 1)
|
||||
end
|
||||
|
||||
commoditymarket.show_market = function(market_name, player_name)
|
||||
local market = commoditymarket.registered_markets[market_name]
|
||||
if market == nil then return end
|
||||
local formspec = market:get_formspec(market:get_account(player_name))
|
||||
minetest.show_formspec(player_name, "commoditymarket:"..market_name..":"..player_name, formspec)
|
||||
end
|
|
@ -1,3 +0,0 @@
|
|||
name = commoditymarket
|
||||
description = Provides API support for various in-world commodity markets
|
||||
optional_depends = doc
|
|
@ -1,200 +0,0 @@
|
|||
-- Internal persistence library
|
||||
|
||||
--[[ Provides ]]
|
||||
-- persistence.store(path, ...): Stores arbitrary items to the file at the given path
|
||||
-- persistence.load(path): Loads files that were previously stored with store and returns them
|
||||
|
||||
--[[ Limitations ]]
|
||||
-- Does not export userdata, threads or most function values
|
||||
-- Function export is not portable
|
||||
|
||||
--[[ License: MIT (see bottom) ]]
|
||||
|
||||
-- Private methods
|
||||
local write, writeIndent, writers, refCount;
|
||||
|
||||
-- write thing (dispatcher)
|
||||
write = function (file, item, level, objRefNames)
|
||||
writers[type(item)](file, item, level, objRefNames);
|
||||
end;
|
||||
|
||||
-- write indent
|
||||
writeIndent = function (file, level)
|
||||
for i = 1, level do
|
||||
file:write("\t");
|
||||
end;
|
||||
end;
|
||||
|
||||
-- recursively count references
|
||||
refCount = function (objRefCount, item)
|
||||
-- only count reference types (tables)
|
||||
if type(item) == "table" then
|
||||
-- Increase ref count
|
||||
if objRefCount[item] then
|
||||
objRefCount[item] = objRefCount[item] + 1;
|
||||
else
|
||||
objRefCount[item] = 1;
|
||||
-- If first encounter, traverse
|
||||
for k, v in pairs(item) do
|
||||
refCount(objRefCount, k);
|
||||
refCount(objRefCount, v);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
-- Format items for the purpose of restoring
|
||||
writers = {
|
||||
["nil"] = function (file, item)
|
||||
file:write("nil");
|
||||
end;
|
||||
["number"] = function (file, item)
|
||||
file:write(tostring(item));
|
||||
end;
|
||||
["string"] = function (file, item)
|
||||
file:write(string.format("%q", item));
|
||||
end;
|
||||
["boolean"] = function (file, item)
|
||||
if item then
|
||||
file:write("true");
|
||||
else
|
||||
file:write("false");
|
||||
end
|
||||
end;
|
||||
["table"] = function (file, item, level, objRefNames)
|
||||
local refIdx = objRefNames[item];
|
||||
if refIdx then
|
||||
-- Table with multiple references
|
||||
file:write("multiRefObjects["..refIdx.."]");
|
||||
else
|
||||
-- Single use table
|
||||
file:write("{\n");
|
||||
for k, v in pairs(item) do
|
||||
writeIndent(file, level+1);
|
||||
file:write("[");
|
||||
write(file, k, level+1, objRefNames);
|
||||
file:write("] = ");
|
||||
write(file, v, level+1, objRefNames);
|
||||
file:write(";\n");
|
||||
end
|
||||
writeIndent(file, level);
|
||||
file:write("}");
|
||||
end;
|
||||
end;
|
||||
["function"] = function (file, item)
|
||||
-- Does only work for "normal" functions, not those
|
||||
-- with upvalues or c functions
|
||||
local dInfo = debug.getinfo(item, "uS");
|
||||
if dInfo.nups > 0 then
|
||||
file:write("nil --[[functions with upvalue not supported]]");
|
||||
elseif dInfo.what ~= "Lua" then
|
||||
file:write("nil --[[non-lua function not supported]]");
|
||||
else
|
||||
local r, s = pcall(string.dump,item);
|
||||
if r then
|
||||
file:write(string.format("loadstring(%q)", s));
|
||||
else
|
||||
file:write("nil --[[function could not be dumped]]");
|
||||
end
|
||||
end
|
||||
end;
|
||||
["thread"] = function (file, item)
|
||||
file:write("nil --[[thread]]\n");
|
||||
end;
|
||||
["userdata"] = function (file, item)
|
||||
file:write("nil --[[userdata]]\n");
|
||||
end;
|
||||
}
|
||||
|
||||
return function (path, ...)
|
||||
local file, e;
|
||||
if type(path) == "string" then
|
||||
-- Path, open a file
|
||||
file, e = io.open(path, "w");
|
||||
if not file then
|
||||
return error(e);
|
||||
end
|
||||
else
|
||||
-- Just treat it as file
|
||||
file = path;
|
||||
end
|
||||
local n = select("#", ...);
|
||||
-- Count references
|
||||
local objRefCount = {}; -- Stores reference that will be exported
|
||||
for i = 1, n do
|
||||
refCount(objRefCount, (select(i,...)));
|
||||
end;
|
||||
-- Export Objects with more than one ref and assign name
|
||||
-- First, create empty tables for each
|
||||
local objRefNames = {};
|
||||
local objRefIdx = 0;
|
||||
file:write("-- Persistent Data\n");
|
||||
file:write("local multiRefObjects = {\n");
|
||||
for obj, count in pairs(objRefCount) do
|
||||
if count > 1 then
|
||||
objRefIdx = objRefIdx + 1;
|
||||
objRefNames[obj] = objRefIdx;
|
||||
file:write("{};"); -- table objRefIdx
|
||||
end;
|
||||
end;
|
||||
file:write("\n} -- multiRefObjects\n");
|
||||
-- Then fill them (this requires all empty multiRefObjects to exist)
|
||||
for obj, idx in pairs(objRefNames) do
|
||||
for k, v in pairs(obj) do
|
||||
file:write("multiRefObjects["..idx.."][");
|
||||
write(file, k, 0, objRefNames);
|
||||
file:write("] = ");
|
||||
write(file, v, 0, objRefNames);
|
||||
file:write(";\n");
|
||||
end;
|
||||
end;
|
||||
-- Create the remaining objects
|
||||
for i = 1, n do
|
||||
file:write("local ".."obj"..i.." = ");
|
||||
write(file, (select(i,...)), 0, objRefNames);
|
||||
file:write("\n");
|
||||
end
|
||||
-- Return them
|
||||
if n > 0 then
|
||||
file:write("return obj1");
|
||||
for i = 2, n do
|
||||
file:write(" ,obj"..i);
|
||||
end;
|
||||
file:write("\n");
|
||||
else
|
||||
file:write("return\n");
|
||||
end;
|
||||
file:close();
|
||||
end, function (path)
|
||||
local f, e = loadfile(path);
|
||||
if f then
|
||||
return f();
|
||||
else
|
||||
return nil, e;
|
||||
end;
|
||||
end
|
||||
|
||||
--[[
|
||||
Copyright (c) 2010 Gerhard Roethlin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
]]
|
|
@ -1,95 +0,0 @@
|
|||
This mod implements marketplaces where players can post buy and sell offers for various items, allowing for organic market forces to determine the relative values of the resources in a world.
|
||||
|
||||
The basic market interface is the same across all markets and market types, but this mod allows for a variety of different ways that markets can be configured to support different playstyles. Markets can have restrictions on what they will allow to be bought and sold, different types of "currency", and can share a common inventory across multiple locations or can be localized to just one spot at the discretion of the server owner.
|
||||
|
||||
![](screenshot.png)
|
||||
|
||||
## Currency
|
||||
|
||||
Each market has one or more "currency" items defined that are treated differently from the other items that can be bought and sold there. Currency items are translated into a player's currency balance rather than being bought and sold directly.
|
||||
|
||||
For example, some of the markets offered by the "commoditymarket_fantasy" mod have this currency definition:
|
||||
|
||||
{
|
||||
["default:gold_ingot"] = 1000,
|
||||
["commoditymarket_fantasy:gold_coin"] = 1
|
||||
}
|
||||
|
||||
When a gold ingot is added to the player's market account it turns into 1000 units of currency. When a gold coin is added it turns into 1 unit of currency. You can't buy and sell gold directly in this market, it is instead the "standard" by which the value of other items is measured.
|
||||
|
||||
There's no reason that all markets in a given world have to use the same currency. Having variety in currency types adds flavour to the world and also introduces opportunities for enterprising traders to make a profit by moneychanging between different marketplaces.
|
||||
|
||||
## Account Inventory
|
||||
|
||||
In addition to tracking a player's currency balance, each player's account has an inventory that serves as a holding area for items that are destined to be sold or that have been bought by the player but not yet retrieved. This inventory is a bit different from the standard Minetest inventory in that it doesn't hold individual item "stacks", allowing for larger quantities of items to be accumulated than would otherwise be practical. If a player needs to buy 20,000 stone bricks for a major construction project then their account's inventory will hold that.
|
||||
|
||||
To prevent abuse of the market inventory as a free storage space, or just to add some unique flavor to a particular market, a limit on the inventory's size can be added. This limit only affects transfers from a player's personal inventory into the market inventory; the limit can be exceeded by incoming items being sold to the player.
|
||||
|
||||
Note that tools cannot be added to the market inventory if they have any wear on them, nor can the market handle items with attached metadata (such as books that have had text added to them).
|
||||
|
||||
## Placing a "Buy" Order
|
||||
|
||||
A buy order is an offer to give a certain amount of currency in exchange for a particular type of item. To place a buy order go to the "Market Orders" tab of the market's interface and select the item from the list of items on the market. If the item isn't listed it may be that the market is simply "unaware" of the item's existence; try placing an example of the item into your personal inventory and if the item is permitted on the market a new entry will be added to Market Orders.
|
||||
|
||||
Enter the quantity and price you desire and then click the "buy" button to place a buy order.
|
||||
|
||||
If there are already "sell" orders for the item when you place a buy order, some or all of your buy order might be immediately fulfilled provided you are offering a sufficient price. Your purchases will be made at the price that the sell orders have been set to - if you were willing to pay 15 units of currency per item but someone was already offering to sell for 2 units of currency per item, you only pay 2 units for each of that offer's items.
|
||||
|
||||
If there aren't enough compatible sell orders to fulfill your buy order, the remainder will be placed into the market and made available for future sellers to see and fulfill if they agree to your price. Your buy order will immediately deduct the currency required for it from your account's balance, but if you cancel your order you will get that currency back - it's not gone until the order is actually fulfilled.
|
||||
|
||||
Double-click on your order in the orders list to cancel it.
|
||||
|
||||
## Placing a "Sell" Order
|
||||
|
||||
Sell orders are an offer of a certain amount of an item and a price you're willing to accept in exchange for them. They're placed in a similar manner to buy orders, except by clicking the "sell" button instead of the "buy" button.
|
||||
|
||||
If there are already buyers with buy orders that meet or exceed your price, some or all of your sell order may be immediately fulfilled. You'll be paid the price that the buyers are offering rather than the amount you're demanding.
|
||||
|
||||
If any of your sell offer is left unfulfilled, the sell order will be added to the market for future buyers to see. The items for this offer will be immediately taken from your market inventory but if you cancel your order you will get those items back.
|
||||
|
||||
Double-click on your order in the orders list to cancel it.
|
||||
|
||||
## Commands
|
||||
|
||||
This mod has several commands that a server administrator can use:
|
||||
|
||||
* `market.removeitem marketname item` -- cancels all existing buy and sell orders for an item and removes its entry from the market tab. This is useful if you've changed what items are permitted in a particular market and need to clear out items that are no longer allowed.
|
||||
* `market.show marketname` -- opens the market's formspec
|
||||
* `market.list` -- lists the marketnames of all registered markets
|
||||
* `market.purge_unknowns` -- executes "removeitem" for all markets on all items that don't have a definition. Useful for clearing out items that are no longer defined due to a mod being updated or removed.
|
||||
* `market.addeverything marketname` - Adds all registered items to a market's listings. NOTE: this is intended as a debugging tool, not for use in a live server, as it doesn't filter out items that a player cannot actually harvest in-world.
|
||||
|
||||
## Registering a market
|
||||
|
||||
The mod "[commoditymarket_fantasy](https://github.com/FaceDeer/commoditymarket_fantasy)" contains a number of pre-defined markets that provide examples of what's possible with this mod. They include:
|
||||
|
||||
* King's Market - a basic sort of "commoner's marketplace", only open during the day
|
||||
* Night Market - the shadier side of commerce, only open during the night
|
||||
* Trader's Caravan - a type of market that players can build and place themselves, with a small inventory capacity.
|
||||
* Goblin Exchange - a strange marketplace that uses coal as a currency
|
||||
* Undermarket - where dark powers make their trades, using Mese as a currency
|
||||
|
||||
All of these except for the Trader's Caravan are intended to be placed in specific locations by server administrators or mapgen, they don't have crafting recipes. Modifying these markets or creating your own from scratch should hopefully be a fairly straightforward task.
|
||||
|
||||
### Market definition API
|
||||
|
||||
```
|
||||
local market_def = {
|
||||
description = "Night Market", -- A short name for this market, appears as the text of the "info" tab of the market's UI
|
||||
long_description = "When the sun sets and the stalls of the King's Market close, other vendors are just waking up to share their wares. The Night Market is not as voluminous as the King's Market but accepts a wider range of wares. It accepts the same gold coinage of the realm, one thousand coins to the gold ingot.", -- A longer description with flavor text and other information to present to the user, shown in the info tab. Optional.
|
||||
currency = {
|
||||
["default:gold_ingot"] = 1000,
|
||||
["commoditymarket:gold_coins"] = 1
|
||||
}, -- List all items that get translated into "currency" here, along with their conversion rates. Take care to ensure there's no way for a player to multiply their money when crafting currency items into each other (eg, if there was some way to get more than 1000 coin items out of a gold ingot, in this case)
|
||||
currency_symbol = "☼", -- Used in various places in the UI. If not defined, defaults to "¤" (the generic currency symbol)
|
||||
inventory_limit = 10000, -- Optional, when set this prevents the player from adding items to their market inventory when it's over this limit
|
||||
sell_limit = 10000, -- Optional, when set this prevents sell orders from being added if the player already has this many items for sale
|
||||
initial_items = {"default:cobble", "default:wood"}, -- Optional, a list of items that the market will be initialized with on startup. Players can add other items during play.
|
||||
allow_item = function(item) return true end, -- Optional, this function is used to determine whether the market permits a player to add a particular item to its inventory.
|
||||
anonymous = true, -- If set to true then the player won't be able to see the names associated with other player's orders, only their own.
|
||||
}
|
||||
|
||||
commoditymarket.register_market("market_name", market_def)
|
||||
```
|
||||
|
||||
Once a market is defined, use `commoditymarket.show_market(market_name, player_name)` to show the market interface to a player.
|
Before Width: | Height: | Size: 76 KiB |
|
@ -1,8 +0,0 @@
|
|||
#Some item images are very large and break the market formspec.
|
||||
#See https://github.com/minetest/minetest/issues/9300
|
||||
#Alternately, use:
|
||||
#commoditymarket.override_item_icon(item_name, new_icon_texture)
|
||||
#or
|
||||
#commoditymarket.override_image_icon(old_icon_texture, new_icon_texture)
|
||||
#to override a troublesome image directly.
|
||||
commoditymarket_enable_item_icons (Enable item icon images in market formspecs) bool true
|
|
@ -1,3 +0,0 @@
|
|||
commoditymarket_register_closed.ogg - from https://freesound.org/people/bspiller5/sounds/180252/ by bspiller5 under the CC-BY 3.0 license
|
||||
commoditymarket_register_opened.ogg - from https://freesound.org/people/kiddpark/sounds/201159/ by kiddpark under the CC-BY 3.0 license
|
||||
commoditymarket_error.ogg - from https://freesound.org/people/LorenzoTheGreat/sounds/417794/ by LorenzoTheGreat under the CC-BY-SA 3.0 license
|
Before Width: | Height: | Size: 539 B |
Before Width: | Height: | Size: 1.3 KiB |
|
@ -1 +0,0 @@
|
|||
commoditymarket_search.png, commoditymarket_clear.png - Copyright © Diego Martínez (kaeza): CC BY-SA 3.0
|
|
@ -1,2 +0,0 @@
|
|||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
|
@ -1,40 +0,0 @@
|
|||
# Compiled Lua sources
|
||||
luac.out
|
||||
|
||||
# luarocks build files
|
||||
*.src.rock
|
||||
*.zip
|
||||
*.tar.gz
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.os
|
||||
*.ko
|
||||
*.elf
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
*.def
|
||||
*.exp
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 FaceDeer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,418 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Script to generate the template file and update the translation files.
|
||||
# Copy the script into the mod or modpack root folder and run it there.
|
||||
#
|
||||
# Copyright (C) 2019 Joachim Stolberg, 2020 FaceDeer, 2020 Louis Royer
|
||||
# LGPLv2.1+
|
||||
|
||||
from __future__ import print_function
|
||||
import os, fnmatch, re, shutil, errno
|
||||
from sys import argv as _argv
|
||||
|
||||
# Running params
|
||||
params = {"recursive": False,
|
||||
"help": False,
|
||||
"mods": False,
|
||||
"verbose": False,
|
||||
"folders": []
|
||||
}
|
||||
# Available CLI options
|
||||
options = {"recursive": ['--recursive', '-r'],
|
||||
"help": ['--help', '-h'],
|
||||
"mods": ['--installed-mods'],
|
||||
"verbose": ['--verbose', '-v']
|
||||
}
|
||||
|
||||
# Strings longer than this will have extra space added between
|
||||
# them in the translation files to make it easier to distinguish their
|
||||
# beginnings and endings at a glance
|
||||
doublespace_threshold = 60
|
||||
|
||||
def set_params_folders(tab: list):
|
||||
'''Initialize params["folders"] from CLI arguments.'''
|
||||
# Discarding argument 0 (tool name)
|
||||
for param in tab[1:]:
|
||||
stop_param = False
|
||||
for option in options:
|
||||
if param in options[option]:
|
||||
stop_param = True
|
||||
break
|
||||
if not stop_param:
|
||||
params["folders"].append(os.path.abspath(param))
|
||||
|
||||
def set_params(tab: list):
|
||||
'''Initialize params from CLI arguments.'''
|
||||
for option in options:
|
||||
for option_name in options[option]:
|
||||
if option_name in tab:
|
||||
params[option] = True
|
||||
break
|
||||
|
||||
def print_help(name):
|
||||
'''Prints some help message.'''
|
||||
print(f'''SYNOPSIS
|
||||
{name} [OPTIONS] [PATHS...]
|
||||
DESCRIPTION
|
||||
{', '.join(options["help"])}
|
||||
prints this help message
|
||||
{', '.join(options["recursive"])}
|
||||
run on all subfolders of paths given
|
||||
{', '.join(options["mods"])}
|
||||
run on locally installed modules
|
||||
{', '.join(options["verbose"])}
|
||||
add output information
|
||||
''')
|
||||
|
||||
|
||||
def main():
|
||||
'''Main function'''
|
||||
set_params(_argv)
|
||||
set_params_folders(_argv)
|
||||
if params["help"]:
|
||||
print_help(_argv[0])
|
||||
elif params["recursive"] and params["mods"]:
|
||||
print("Option --installed-mods is incompatible with --recursive")
|
||||
else:
|
||||
# Add recursivity message
|
||||
print("Running ", end='')
|
||||
if params["recursive"]:
|
||||
print("recursively ", end='')
|
||||
# Running
|
||||
if params["mods"]:
|
||||
print(f"on all locally installed modules in {os.path.abspath('~/.minetest/mods/')}")
|
||||
run_all_subfolders("~/.minetest/mods")
|
||||
elif len(params["folders"]) >= 2:
|
||||
print("on folder list:", params["folders"])
|
||||
for f in params["folders"]:
|
||||
if params["recursive"]:
|
||||
run_all_subfolders(f)
|
||||
else:
|
||||
update_folder(f)
|
||||
elif len(params["folders"]) == 1:
|
||||
print("on folder", params["folders"][0])
|
||||
if params["recursive"]:
|
||||
run_all_subfolders(params["folders"][0])
|
||||
else:
|
||||
update_folder(params["folders"][0])
|
||||
else:
|
||||
print("on folder", os.path.abspath("./"))
|
||||
if params["recursive"]:
|
||||
run_all_subfolders(os.path.abspath("./"))
|
||||
else:
|
||||
update_folder(os.path.abspath("./"))
|
||||
|
||||
#group 2 will be the string, groups 1 and 3 will be the delimiters (" or ')
|
||||
#See https://stackoverflow.com/questions/46967465/regex-match-text-in-either-single-or-double-quote
|
||||
pattern_lua = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL)
|
||||
pattern_lua_bracketed = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL)
|
||||
|
||||
# Handles "concatenation" .. " of strings"
|
||||
pattern_concat = re.compile(r'["\'][\s]*\.\.[\s]*["\']', re.DOTALL)
|
||||
|
||||
pattern_tr = re.compile(r'(.+?[^@])=(.*)')
|
||||
pattern_name = re.compile(r'^name[ ]*=[ ]*([^ \n]*)')
|
||||
pattern_tr_filename = re.compile(r'\.tr$')
|
||||
pattern_po_language_code = re.compile(r'(.*)\.po$')
|
||||
|
||||
#attempt to read the mod's name from the mod.conf file. Returns None on failure
|
||||
def get_modname(folder):
|
||||
try:
|
||||
with open(os.path.join(folder, "mod.conf"), "r", encoding='utf-8') as mod_conf:
|
||||
for line in mod_conf:
|
||||
match = pattern_name.match(line)
|
||||
if match:
|
||||
return match.group(1)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return None
|
||||
|
||||
#If there are already .tr files in /locale, returns a list of their names
|
||||
def get_existing_tr_files(folder):
|
||||
out = []
|
||||
for root, dirs, files in os.walk(os.path.join(folder, 'locale/')):
|
||||
for name in files:
|
||||
if pattern_tr_filename.search(name):
|
||||
out.append(name)
|
||||
return out
|
||||
|
||||
# A series of search and replaces that massage a .po file's contents into
|
||||
# a .tr file's equivalent
|
||||
def process_po_file(text):
|
||||
# The first three items are for unused matches
|
||||
text = re.sub(r'#~ msgid "', "", text)
|
||||
text = re.sub(r'"\n#~ msgstr ""\n"', "=", text)
|
||||
text = re.sub(r'"\n#~ msgstr "', "=", text)
|
||||
# comment lines
|
||||
text = re.sub(r'#.*\n', "", text)
|
||||
# converting msg pairs into "=" pairs
|
||||
text = re.sub(r'msgid "', "", text)
|
||||
text = re.sub(r'"\nmsgstr ""\n"', "=", text)
|
||||
text = re.sub(r'"\nmsgstr "', "=", text)
|
||||
# various line breaks and escape codes
|
||||
text = re.sub(r'"\n"', "", text)
|
||||
text = re.sub(r'"\n', "\n", text)
|
||||
text = re.sub(r'\\"', '"', text)
|
||||
text = re.sub(r'\\n', '@n', text)
|
||||
# remove header text
|
||||
text = re.sub(r'=Project-Id-Version:.*\n', "", text)
|
||||
# remove double-spaced lines
|
||||
text = re.sub(r'\n\n', '\n', text)
|
||||
return text
|
||||
|
||||
# Go through existing .po files and, if a .tr file for that language
|
||||
# *doesn't* exist, convert it and create it.
|
||||
# The .tr file that results will subsequently be reprocessed so
|
||||
# any "no longer used" strings will be preserved.
|
||||
# Note that "fuzzy" tags will be lost in this process.
|
||||
def process_po_files(folder, modname):
|
||||
for root, dirs, files in os.walk(os.path.join(folder, 'locale/')):
|
||||
for name in files:
|
||||
code_match = pattern_po_language_code.match(name)
|
||||
if code_match == None:
|
||||
continue
|
||||
language_code = code_match.group(1)
|
||||
tr_name = modname + "." + language_code + ".tr"
|
||||
tr_file = os.path.join(root, tr_name)
|
||||
if os.path.exists(tr_file):
|
||||
if params["verbose"]:
|
||||
print(f"{tr_name} already exists, ignoring {name}")
|
||||
continue
|
||||
fname = os.path.join(root, name)
|
||||
with open(fname, "r", encoding='utf-8') as po_file:
|
||||
if params["verbose"]:
|
||||
print(f"Importing translations from {name}")
|
||||
text = process_po_file(po_file.read())
|
||||
with open(tr_file, "wt", encoding='utf-8') as tr_out:
|
||||
tr_out.write(text)
|
||||
|
||||
# from https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python/600612#600612
|
||||
# Creates a directory if it doesn't exist, silently does
|
||||
# nothing if it already exists
|
||||
def mkdir_p(path):
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except OSError as exc: # Python >2.5
|
||||
if exc.errno == errno.EEXIST and os.path.isdir(path):
|
||||
pass
|
||||
else: raise
|
||||
|
||||
# Converts the template dictionary to a text to be written as a file
|
||||
# dKeyStrings is a dictionary of localized string to source file sets
|
||||
# dOld is a dictionary of existing translations and comments from
|
||||
# the previous version of this text
|
||||
def strings_to_text(dkeyStrings, dOld, mod_name):
|
||||
lOut = [f"# textdomain: {mod_name}\n"]
|
||||
|
||||
dGroupedBySource = {}
|
||||
|
||||
for key in dkeyStrings:
|
||||
sourceList = list(dkeyStrings[key])
|
||||
sourceList.sort()
|
||||
sourceString = "\n".join(sourceList)
|
||||
listForSource = dGroupedBySource.get(sourceString, [])
|
||||
listForSource.append(key)
|
||||
dGroupedBySource[sourceString] = listForSource
|
||||
|
||||
lSourceKeys = list(dGroupedBySource.keys())
|
||||
lSourceKeys.sort()
|
||||
for source in lSourceKeys:
|
||||
localizedStrings = dGroupedBySource[source]
|
||||
localizedStrings.sort()
|
||||
lOut.append("")
|
||||
lOut.append(source)
|
||||
lOut.append("")
|
||||
for localizedString in localizedStrings:
|
||||
val = dOld.get(localizedString, {})
|
||||
translation = val.get("translation", "")
|
||||
comment = val.get("comment")
|
||||
if len(localizedString) > doublespace_threshold and not lOut[-1] == "":
|
||||
lOut.append("")
|
||||
if comment != None:
|
||||
lOut.append(comment)
|
||||
lOut.append(f"{localizedString}={translation}")
|
||||
if len(localizedString) > doublespace_threshold:
|
||||
lOut.append("")
|
||||
|
||||
|
||||
unusedExist = False
|
||||
for key in dOld:
|
||||
if key not in dkeyStrings:
|
||||
val = dOld[key]
|
||||
translation = val.get("translation")
|
||||
comment = val.get("comment")
|
||||
# only keep an unused translation if there was translated
|
||||
# text or a comment associated with it
|
||||
if translation != None and (translation != "" or comment):
|
||||
if not unusedExist:
|
||||
unusedExist = True
|
||||
lOut.append("\n\n##### not used anymore #####\n")
|
||||
if len(key) > doublespace_threshold and not lOut[-1] == "":
|
||||
lOut.append("")
|
||||
if comment != None:
|
||||
lOut.append(comment)
|
||||
lOut.append(f"{key}={translation}")
|
||||
if len(key) > doublespace_threshold:
|
||||
lOut.append("")
|
||||
return "\n".join(lOut) + '\n'
|
||||
|
||||
# Writes a template.txt file
|
||||
# dkeyStrings is the dictionary returned by generate_template
|
||||
def write_template(templ_file, dkeyStrings, mod_name):
|
||||
# read existing template file to preserve comments
|
||||
existing_template = import_tr_file(templ_file)
|
||||
|
||||
text = strings_to_text(dkeyStrings, existing_template[0], mod_name)
|
||||
mkdir_p(os.path.dirname(templ_file))
|
||||
with open(templ_file, "wt", encoding='utf-8') as template_file:
|
||||
template_file.write(text)
|
||||
|
||||
|
||||
# Gets all translatable strings from a lua file
|
||||
def read_lua_file_strings(lua_file):
|
||||
lOut = []
|
||||
with open(lua_file, encoding='utf-8') as text_file:
|
||||
text = text_file.read()
|
||||
#TODO remove comments here
|
||||
|
||||
text = re.sub(pattern_concat, "", text)
|
||||
|
||||
strings = []
|
||||
for s in pattern_lua.findall(text):
|
||||
strings.append(s[1])
|
||||
for s in pattern_lua_bracketed.findall(text):
|
||||
strings.append(s)
|
||||
|
||||
for s in strings:
|
||||
s = re.sub(r'"\.\.\s+"', "", s)
|
||||
s = re.sub("@[^@=0-9]", "@@", s)
|
||||
s = s.replace('\\"', '"')
|
||||
s = s.replace("\\'", "'")
|
||||
s = s.replace("\n", "@n")
|
||||
s = s.replace("\\n", "@n")
|
||||
s = s.replace("=", "@=")
|
||||
lOut.append(s)
|
||||
return lOut
|
||||
|
||||
# Gets strings from an existing translation file
|
||||
# returns both a dictionary of translations
|
||||
# and the full original source text so that the new text
|
||||
# can be compared to it for changes.
|
||||
def import_tr_file(tr_file):
|
||||
dOut = {}
|
||||
text = None
|
||||
if os.path.exists(tr_file):
|
||||
with open(tr_file, "r", encoding='utf-8') as existing_file :
|
||||
# save the full text to allow for comparison
|
||||
# of the old version with the new output
|
||||
text = existing_file.read()
|
||||
existing_file.seek(0)
|
||||
# a running record of the current comment block
|
||||
# we're inside, to allow preceeding multi-line comments
|
||||
# to be retained for a translation line
|
||||
latest_comment_block = None
|
||||
for line in existing_file.readlines():
|
||||
line = line.rstrip('\n')
|
||||
if line[:3] == "###":
|
||||
# Reset comment block if we hit a header
|
||||
latest_comment_block = None
|
||||
continue
|
||||
if line[:1] == "#":
|
||||
# Save the comment we're inside
|
||||
if not latest_comment_block:
|
||||
latest_comment_block = line
|
||||
else:
|
||||
latest_comment_block = latest_comment_block + "\n" + line
|
||||
continue
|
||||
match = pattern_tr.match(line)
|
||||
if match:
|
||||
# this line is a translated line
|
||||
outval = {}
|
||||
outval["translation"] = match.group(2)
|
||||
if latest_comment_block:
|
||||
# if there was a comment, record that.
|
||||
outval["comment"] = latest_comment_block
|
||||
latest_comment_block = None
|
||||
dOut[match.group(1)] = outval
|
||||
return (dOut, text)
|
||||
|
||||
# Walks all lua files in the mod folder, collects translatable strings,
|
||||
# and writes it to a template.txt file
|
||||
# Returns a dictionary of localized strings to source file sets
|
||||
# that can be used with the strings_to_text function.
|
||||
def generate_template(folder, mod_name):
|
||||
dOut = {}
|
||||
for root, dirs, files in os.walk(folder):
|
||||
for name in files:
|
||||
if fnmatch.fnmatch(name, "*.lua"):
|
||||
fname = os.path.join(root, name)
|
||||
found = read_lua_file_strings(fname)
|
||||
if params["verbose"]:
|
||||
print(f"{fname}: {str(len(found))} translatable strings")
|
||||
|
||||
for s in found:
|
||||
sources = dOut.get(s, set())
|
||||
sources.add(f"### {os.path.basename(fname)} ###")
|
||||
dOut[s] = sources
|
||||
|
||||
if len(dOut) == 0:
|
||||
return None
|
||||
templ_file = os.path.join(folder, "locale/template.txt")
|
||||
write_template(templ_file, dOut, mod_name)
|
||||
return dOut
|
||||
|
||||
# Updates an existing .tr file, copying the old one to a ".old" file
|
||||
# if any changes have happened
|
||||
# dNew is the data used to generate the template, it has all the
|
||||
# currently-existing localized strings
|
||||
def update_tr_file(dNew, mod_name, tr_file):
|
||||
if params["verbose"]:
|
||||
print(f"updating {tr_file}")
|
||||
|
||||
tr_import = import_tr_file(tr_file)
|
||||
dOld = tr_import[0]
|
||||
textOld = tr_import[1]
|
||||
|
||||
textNew = strings_to_text(dNew, dOld, mod_name)
|
||||
|
||||
if textOld and textOld != textNew:
|
||||
print(f"{tr_file} has changed.")
|
||||
shutil.copyfile(tr_file, f"{tr_file}.old")
|
||||
|
||||
with open(tr_file, "w", encoding='utf-8') as new_tr_file:
|
||||
new_tr_file.write(textNew)
|
||||
|
||||
# Updates translation files for the mod in the given folder
|
||||
def update_mod(folder):
|
||||
modname = get_modname(folder)
|
||||
if modname is not None:
|
||||
process_po_files(folder, modname)
|
||||
print(f"Updating translations for {modname}")
|
||||
data = generate_template(folder, modname)
|
||||
if data == None:
|
||||
print(f"No translatable strings found in {modname}")
|
||||
else:
|
||||
for tr_file in get_existing_tr_files(folder):
|
||||
update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file))
|
||||
else:
|
||||
print("Unable to find modname in folder " + folder)
|
||||
|
||||
# Determines if the folder being pointed to is a mod or a mod pack
|
||||
# and then runs update_mod accordingly
|
||||
def update_folder(folder):
|
||||
is_modpack = os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf"))
|
||||
if is_modpack:
|
||||
subfolders = [f.path for f in os.scandir(folder) if f.is_dir()]
|
||||
for subfolder in subfolders:
|
||||
update_mod(subfolder + "/")
|
||||
else:
|
||||
update_mod(folder)
|
||||
print("Done.")
|
||||
|
||||
def run_all_subfolders(folder):
|
||||
for modfolder in [f.path for f in os.scandir(folder) if f.is_dir()]:
|
||||
update_folder(modfolder + "/")
|
||||
|
||||
|
||||
main()
|
|
@ -1,544 +0,0 @@
|
|||
local modpath = minetest.get_modpath(minetest.get_current_modname())
|
||||
|
||||
minetest.register_alias("commoditymarket:kings_market", "commoditymarket_fantasy:kings_market")
|
||||
minetest.register_alias("commoditymarket:gold_coins", "commoditymarket_fantasy:gold_coins")
|
||||
minetest.register_alias("commoditymarket:night_market", "commoditymarket_fantasy:night_market")
|
||||
minetest.register_alias("commoditymarket:goblin_market", "commoditymarket_fantasy:goblin_market")
|
||||
minetest.register_alias("commoditymarket:under_market", "commoditymarket_fantasy:under_market")
|
||||
minetest.register_alias("commoditymarket:caravan_post", "commoditymarket_fantasy:caravan_post")
|
||||
minetest.register_alias("commoditymarket:caravan_market_1", "commoditymarket_fantasy:caravan_market_1")
|
||||
minetest.register_alias("commoditymarket:caravan_market_2", "commoditymarket_fantasy:caravan_market_2")
|
||||
minetest.register_alias("commoditymarket:caravan_market_3", "commoditymarket_fantasy:caravan_market_3")
|
||||
minetest.register_alias("commoditymarket:caravan_market_4", "commoditymarket_fantasy:caravan_market_4")
|
||||
minetest.register_alias("commoditymarket:caravan_market_5", "commoditymarket_fantasy:caravan_market_5")
|
||||
minetest.register_alias("commoditymarket:caravan_market_permanent", "commoditymarket_fantasy:caravan_market_permanent")
|
||||
|
||||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
|
||||
dofile(modpath.."/mapgen_dungeon_markets.lua")
|
||||
|
||||
local coins_per_ingot = math.floor(tonumber(minetest.settings:get("commoditymarket_coins_per_ingot")) or 1000)
|
||||
|
||||
-- Only register gold coins once, if required
|
||||
local gold_coins_registered = false
|
||||
local register_gold_coins = function()
|
||||
if not gold_coins_registered then
|
||||
minetest.register_craftitem("commoditymarket_fantasy:gold_coins", {
|
||||
description = S("Gold Coins"),
|
||||
_doc_items_longdesc = S("A gold ingot is far too valuable to use as a basic unit of value, so it has become common practice to divide the standard gold bar into @1 small disks to make trade easier.", coins_per_ingot),
|
||||
_doc_items_usagehelp = S("Gold coins can be deposited and withdrawn from markets that accept them as currency. These markets can make change if you have @1 coins and would like them back in ingot form again.", coins_per_ingot),
|
||||
inventory_image = "commoditymarket_gold_coins.png",
|
||||
stack_max = coins_per_ingot,
|
||||
})
|
||||
gold_coins_registered = true
|
||||
end
|
||||
end
|
||||
|
||||
local default_items = {"default:axe_bronze","default:axe_diamond","default:axe_mese","default:axe_steel","default:axe_steel","default:axe_stone","default:axe_wood","default:pick_bronze","default:pick_diamond","default:pick_mese","default:pick_steel","default:pick_stone","default:pick_wood","default:shovel_bronze","default:shovel_diamond","default:shovel_mese","default:shovel_steel","default:shovel_stone","default:shovel_wood","default:sword_bronze","default:sword_diamond","default:sword_mese","default:sword_steel","default:sword_stone","default:sword_wood", "default:blueberries", "default:book", "default:bronze_ingot", "default:clay_brick", "default:clay_lump", "default:coal_lump", "default:copper_ingot", "default:copper_lump", "default:diamond", "default:flint", "default:gold_ingot", "default:gold_lump", "default:iron_lump", "default:mese_crystal", "default:mese_crystal_fragment", "default:obsidian_shard", "default:paper", "default:steel_ingot", "default:stick", "default:tin_ingot", "default:tin_lump", "default:acacia_tree", "default:acacia_wood", "default:apple", "default:aspen_tree", "default:aspen_wood", "default:blueberry_bush_sapling", "default:bookshelf", "default:brick", "default:bronzeblock", "default:bush_sapling", "default:cactus", "default:clay", "default:coalblock", "default:cobble", "default:copperblock", "default:desert_cobble", "default:desert_sand", "default:desert_sandstone", "default:desert_sandstone_block", "default:desert_sandstone_brick", "default:desert_stone", "default:desert_stone_block", "default:desert_stonebrick", "default:diamondblock", "default:dirt", "default:glass", "default:goldblock", "default:gravel", "default:ice", "default:junglegrass", "default:junglesapling", "default:jungletree", "default:junglewood", "default:ladder_steel", "default:ladder_wood", "default:large_cactus_seedling", "default:mese", "default:mese_post_light", "default:meselamp", "default:mossycobble", "default:obsidian", "default:obsidian_block", "default:obsidian_glass", "default:obsidianbrick", "default:papyrus", "default:pine_sapling", "default:pine_tree", "default:pine_wood", "default:sand", "default:sandstone", "default:sandstone_block", "default:sandstonebrick", "default:sapling", "default:silver_sand", "default:silver_sandstone", "default:silver_sandstone_block", "default:silver_sandstone_brick", "default:snow", "default:snowblock", "default:steelblock", "default:stone", "default:stone_block", "default:stonebrick", "default:tinblock", "default:tree", "default:wood",}
|
||||
|
||||
local usage_help = S("Right-click on this to open the market interface.")
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
-- King's Market
|
||||
|
||||
if minetest.settings:get_bool("commoditymarket_enable_kings_market", true) then
|
||||
|
||||
local kings_def = {
|
||||
description = S("King's Market"),
|
||||
long_description = S("The largest and most accessible market for the common man, the King's Market uses gold coins as its medium of exchange (or the equivalent in gold ingots - @1 coins to the ingot). However, as a respectable institution of the surface world, the King's Market operates only during the hours of daylight. The purchase and sale of swords and explosives is prohibited in the King's Market. Gold coins are represented by a '☼' symbol.", coins_per_ingot),
|
||||
currency = {
|
||||
["default:gold_ingot"] = coins_per_ingot,
|
||||
["commoditymarket_fantasy:gold_coins"] = 1
|
||||
},
|
||||
currency_symbol = "☼", -- "\u{263C}" Alchemical symbol for gold
|
||||
allow_item = function(item)
|
||||
if item:sub(1,13) == "default:sword" or item:sub(1,4) == "tnt:" then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end,
|
||||
inventory_limit = 100000,
|
||||
--sell_limit =, -- no sell limit for the King's Market
|
||||
initial_items = default_items,
|
||||
}
|
||||
|
||||
register_gold_coins()
|
||||
|
||||
commoditymarket.register_market("kings", kings_def)
|
||||
|
||||
local kings_protect = minetest.settings:get_bool("commoditymarket_protect_kings_market", true)
|
||||
local on_blast
|
||||
if kings_protect then
|
||||
on_blast = function() end
|
||||
end
|
||||
|
||||
minetest.register_node("commoditymarket_fantasy:kings_market", {
|
||||
description = kings_def.description,
|
||||
_doc_items_longdesc = kings_def.long_description,
|
||||
_doc_items_usagehelp = usage_help,
|
||||
tiles = {"default_chest_top.png","default_chest_top.png",
|
||||
"default_chest_side.png","default_chest_side.png",
|
||||
"commoditymarket_empty_shelf.png","default_chest_side.png^commoditymarket_crown.png",},
|
||||
paramtype2 = "facedir",
|
||||
is_ground_content = false,
|
||||
groups = {choppy = 2, oddly_breakable_by_hand = 1,},
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
|
||||
local timeofday = minetest.get_timeofday()
|
||||
if timeofday > 0.2 and timeofday < 0.8 then
|
||||
commoditymarket.show_market("kings", clicker:get_player_name())
|
||||
else
|
||||
minetest.chat_send_player(clicker:get_player_name(), S("At this time of day the King's Market is closed."))
|
||||
minetest.sound_play({name = "commoditymarket_error", gain = 0.1}, {to_player=clicker:get_player_name()})
|
||||
end
|
||||
end,
|
||||
can_dig = function(pos, player)
|
||||
return not kings_protect or minetest.check_player_privs(player, "protection_bypass")
|
||||
end,
|
||||
on_blast = on_blast,
|
||||
})
|
||||
end
|
||||
-------------------------------------------------------------------------------
|
||||
-- Night Market
|
||||
|
||||
if minetest.settings:get_bool("commoditymarket_enable_night_market", true) then
|
||||
local night_def = {
|
||||
description = S("Night Market"),
|
||||
long_description = S("When the sun sets and the stalls of the King's Market close, other vendors are just waking up to share their wares. The Night Market is not as voluminous as the King's Market but accepts a wider range of wares. It accepts the same gold coinage of the realm, @1 coins to the gold ingot.", coins_per_ingot),
|
||||
currency = {
|
||||
["default:gold_ingot"] = coins_per_ingot,
|
||||
["commoditymarket_fantasy:gold_coins"] = 1
|
||||
},
|
||||
currency_symbol = "☼", --"\u{263C}"
|
||||
inventory_limit = 10000,
|
||||
--sell_limit =, -- no sell limit for the Night Market
|
||||
initial_items = default_items,
|
||||
anonymous = true,
|
||||
}
|
||||
|
||||
register_gold_coins()
|
||||
|
||||
commoditymarket.register_market("night", night_def)
|
||||
|
||||
local night_protect = minetest.settings:get_bool("commoditymarket_protect_night_market", true)
|
||||
local on_blast
|
||||
if night_protect then
|
||||
on_blast = function() end
|
||||
end
|
||||
|
||||
minetest.register_node("commoditymarket_fantasy:night_market", {
|
||||
description = night_def.description,
|
||||
_doc_items_longdesc = night_def.long_description,
|
||||
_doc_items_usagehelp = usage_help,
|
||||
tiles = {"default_chest_top.png","default_chest_top.png",
|
||||
"default_chest_side.png","default_chest_side.png",
|
||||
"commoditymarket_empty_shelf.png","default_chest_side.png^commoditymarket_moon.png",},
|
||||
paramtype2 = "facedir",
|
||||
is_ground_content = false,
|
||||
groups = {choppy = 2, oddly_breakable_by_hand = 1,},
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
|
||||
local timeofday = minetest.get_timeofday()
|
||||
if timeofday < 0.2 or timeofday > 0.8 then
|
||||
commoditymarket.show_market("night", clicker:get_player_name())
|
||||
else
|
||||
minetest.chat_send_player(clicker:get_player_name(), S("At this time of day the Night Market is closed."))
|
||||
minetest.sound_play({name = "commoditymarket_error", gain = 0.1}, {to_player=clicker:get_player_name()})
|
||||
end
|
||||
end,
|
||||
can_dig = function(pos, player)
|
||||
return not night_protect or minetest.check_player_privs(player, "protection_bypass")
|
||||
end,
|
||||
on_blast = on_blast,
|
||||
})
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
if minetest.settings:get_bool("commoditymarket_enable_caravan_market", true) then
|
||||
-- "Trader's Caravan" - small-capacity market that players can summon
|
||||
|
||||
local time_until_caravan = 120 -- caravan arrives in two minutes
|
||||
local dwell_time = 600 -- caravan leaves ten minutes after last usage
|
||||
|
||||
local caravan_def = {
|
||||
description = S("Trader's Caravan"),
|
||||
long_description = S("Unlike most markets that have well-known fixed locations that travelers congregate to, the network of Trader's Caravans is fluid and dynamic in their locations. A Trader's Caravan can show up anywhere, make modest trades, and then be gone the next time you visit them. These caravans accept gold and gold coins as a currency (one gold ingot to @1 gold coins exchange rate). Any reasonably-wealthy person can create a signpost marking a location where Trader's Caravans will make a stop.", coins_per_ingot),
|
||||
currency = {
|
||||
["default:gold_ingot"] = coins_per_ingot,
|
||||
["commoditymarket_fantasy:gold_coins"] = 1
|
||||
},
|
||||
currency_symbol = "☼", --"\u{263C}"
|
||||
inventory_limit = 1000,
|
||||
sell_limit = 1000,
|
||||
initial_items = default_items,
|
||||
}
|
||||
|
||||
register_gold_coins()
|
||||
|
||||
minetest.register_craft({
|
||||
output = "commoditymarket_fantasy:caravan_post",
|
||||
recipe = {
|
||||
{'group:wood', 'group:wood', ''},
|
||||
{'group:wood', "default:gold_ingot", ''},
|
||||
{'group:wood', "default:chest_locked", ''},
|
||||
}
|
||||
})
|
||||
|
||||
commoditymarket.register_market("caravan", caravan_def)
|
||||
|
||||
local create_caravan_def = function(override_table)
|
||||
local def = {
|
||||
description = caravan_def.description,
|
||||
_doc_items_longdesc = caravan_def.long_description,
|
||||
_doc_items_usagehelp = usage_help,
|
||||
drawtype = "mesh",
|
||||
mesh = "commoditymarket_wagon.obj",
|
||||
tiles = {
|
||||
{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
|
||||
{ name = "default_wood.png", backface_culling = true }, -- base wood
|
||||
{ name = "default_fence_rail_wood.png", backface_culling = true }, -- wheel sides
|
||||
{ name = "default_coal_block.png", backface_culling = true }, -- wheel tyre
|
||||
{ name = "commoditymarket_shingles_wood.png", backface_culling = true }, -- roof
|
||||
{ name = "default_junglewood.png", backface_culling = true }, -- corner wood
|
||||
},
|
||||
collision_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
-- Note: this oversized nodebox may cause slight problems when you stand on it.
|
||||
-- https://github.com/minetest/minetest/issues/9322
|
||||
{-0.75, -0.5, -1.25, 0.75, 1.5, 1.25},
|
||||
},
|
||||
},
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{-0.75, -0.5, -1.25, 0.75, 1.5, 1.25},
|
||||
},
|
||||
},
|
||||
|
||||
paramtype2 = "facedir",
|
||||
drop = "",
|
||||
groups = {choppy = 2, oddly_breakable_by_hand = 1, not_in_creative_inventory = 1},
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
|
||||
commoditymarket.show_market("caravan", clicker:get_player_name())
|
||||
local timer = minetest.get_node_timer(pos)
|
||||
timer:start(dwell_time)
|
||||
end,
|
||||
after_destruct = function(pos, oldnode)
|
||||
local facedir = oldnode.param2
|
||||
local dir = minetest.facedir_to_dir(facedir)
|
||||
local target = vector.add(pos, vector.multiply(dir,-3))
|
||||
local target_node = minetest.get_node(target)
|
||||
if target_node.name == "commoditymarket_fantasy:caravan_post" then
|
||||
local meta = minetest.get_meta(target)
|
||||
meta:set_string("infotext", S("Right-click to summon a trader's caravan"))
|
||||
end
|
||||
end,
|
||||
on_timer = function(pos, elapsed)
|
||||
minetest.set_node(pos, {name="air"})
|
||||
minetest.sound_play("commoditymarket_register_closed", {
|
||||
pos = pos,
|
||||
gain = 1.0, -- default
|
||||
max_hear_distance = 32, -- default, uses an euclidean metric
|
||||
})
|
||||
end,
|
||||
}
|
||||
if override_table then
|
||||
for k, v in pairs(override_table) do
|
||||
def[k] = v
|
||||
end
|
||||
end
|
||||
return def
|
||||
end
|
||||
|
||||
-- Create five caravans with different textures, randomly pick which one shows up.
|
||||
minetest.register_node("commoditymarket_fantasy:caravan_market_1", create_caravan_def())
|
||||
minetest.register_node("commoditymarket_fantasy:caravan_market_2", create_caravan_def({
|
||||
tiles = {
|
||||
{ name = "commoditymarket_door_wood.png^[multiply:#CCCCFF", backface_culling = true }, -- door
|
||||
{ name = "default_acacia_wood.png", backface_culling = true }, -- base wood
|
||||
{ name = "default_fence_rail_wood.png", backface_culling = true }, -- wheel sides
|
||||
{ name = "default_copper_block.png", backface_culling = true }, -- wheel tyre
|
||||
{ name = "commoditymarket_shingles_wood.png^[multiply:#CC8888", backface_culling = true }, -- roof
|
||||
{ name = "default_wood.png", backface_culling = true }, -- corner wood
|
||||
}
|
||||
}))
|
||||
minetest.register_node("commoditymarket_fantasy:caravan_market_3", create_caravan_def({
|
||||
tiles = {
|
||||
{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
|
||||
{ name = "default_aspen_wood.png", backface_culling = true }, -- base wood
|
||||
{ name = "default_fence_aspen_wood.png", backface_culling = true }, -- wheel sides
|
||||
{ name = "default_cobble.png", backface_culling = true }, -- wheel tyre
|
||||
{ name = "default_stone_brick.png", backface_culling = true }, -- roof
|
||||
{ name = "default_pine_tree.png", backface_culling = true }, -- corner wood
|
||||
}
|
||||
}))
|
||||
minetest.register_node("commoditymarket_fantasy:caravan_market_4", create_caravan_def({
|
||||
tiles = {
|
||||
{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
|
||||
{ name = "default_junglewood.png", backface_culling = true }, -- base wood
|
||||
{ name = "default_fence_rail_junglewood.png", backface_culling = true }, -- wheel sides
|
||||
{ name = "default_obsidian.png", backface_culling = true }, -- wheel tyre
|
||||
{ name = "commoditymarket_shingles_wood.png^[multiply:#88FF88", backface_culling = true }, -- roof
|
||||
{ name = "default_tree.png", backface_culling = true }, -- corner wood
|
||||
}
|
||||
}))
|
||||
minetest.register_node("commoditymarket_fantasy:caravan_market_5", create_caravan_def({
|
||||
tiles = {
|
||||
{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
|
||||
{ name = "default_pine_wood.png", backface_culling = true }, -- base wood
|
||||
{ name = "default_chest_lock.png", backface_culling = true }, -- wheel sides
|
||||
{ name = "default_chest_top.png", backface_culling = true }, -- wheel tyre
|
||||
{ name = "default_furnace_top.png", backface_culling = true }, -- roof
|
||||
{ name = "default_wood.png", backface_culling = true }, -- corner wood
|
||||
}
|
||||
}))
|
||||
|
||||
local caravan_protect = minetest.settings:get_bool("commoditymarket_protect_caravan_market", true)
|
||||
local on_blast
|
||||
if caravan_protect then
|
||||
on_blast = function() end
|
||||
end
|
||||
|
||||
-- This one doesn't delete itself, server admins can place a permanent instance of it that way. Maybe inside towns next to bigger stationary markets.
|
||||
minetest.register_node("commoditymarket_fantasy:caravan_market_permanent", {
|
||||
description = caravan_def.description,
|
||||
_doc_items_longdesc = caravan_def.long_description,
|
||||
_doc_items_usagehelp = usage_help,
|
||||
drawtype = "mesh",
|
||||
mesh = "commoditymarket_wagon.obj",
|
||||
tiles = {
|
||||
{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
|
||||
{ name = "default_wood.png", backface_culling = true }, -- base wood
|
||||
{ name = "default_fence_rail_wood.png", backface_culling = true }, -- wheel sides
|
||||
{ name = "default_coal_block.png", backface_culling = true }, -- wheel tyre
|
||||
{ name = "commoditymarket_shingles_wood.png", backface_culling = true }, -- roof
|
||||
{ name = "default_junglewood.png", backface_culling = true }, -- corner wood
|
||||
},
|
||||
collision_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{-0.75, -0.5, -1.25, 0.75, 1.5, 1.25},
|
||||
},
|
||||
},
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{-0.75, -0.5, -1.25, 0.75, 1.5, 1.25},
|
||||
},
|
||||
},
|
||||
|
||||
paramtype2 = "facedir",
|
||||
is_ground_content = false,
|
||||
groups = {choppy = 2, oddly_breakable_by_hand = 1,},
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
|
||||
commoditymarket.show_market("caravan", clicker:get_player_name())
|
||||
end,
|
||||
can_dig = function(pos, player)
|
||||
return not caravan_protect or minetest.check_player_privs(player, "protection_bypass")
|
||||
end,
|
||||
on_blast = on_blast,
|
||||
})
|
||||
|
||||
-- is a 5x3 area centered around pos clear of obstruction and has usable ground?
|
||||
local is_suitable_caravan_space = function(pos, facedir)
|
||||
local x_dim = 2
|
||||
local z_dim = 2
|
||||
local dir = minetest.facedir_to_dir(facedir)
|
||||
if dir.x ~= 0 then
|
||||
z_dim = 1
|
||||
elseif dir.z ~= 0 then
|
||||
x_dim = 1
|
||||
end
|
||||
|
||||
-- walkable ground?
|
||||
for x = pos.x - x_dim, pos.x + x_dim, 1 do
|
||||
for z = pos.z - z_dim, pos.z + z_dim, 1 do
|
||||
local node = minetest.get_node({x=x, y=pos.y-1, z=z})
|
||||
local node_def = minetest.registered_nodes[node.name]
|
||||
if node_def == nil or node_def.walkable ~= true then return false end
|
||||
end
|
||||
end
|
||||
-- buildable_to in the rest?
|
||||
for y = pos.y, pos.y+2, 1 do
|
||||
for x = pos.x - x_dim, pos.x + x_dim, 1 do
|
||||
for z = pos.z - z_dim, pos.z + z_dim, 1 do
|
||||
local node = minetest.get_node({x=x, y=y, z=z})
|
||||
local node_def = minetest.registered_nodes[node.name]
|
||||
if node_def == nil or node_def.buildable_to ~= true then return false end
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
minetest.register_node("commoditymarket_fantasy:caravan_post", {
|
||||
description = S("Trading Post"),
|
||||
_long_items_longdesc = S("This post signals passing caravan traders that customers can be found here, and signals to customers that caravan traders can be found here. If no caravan is present, right-click to summon one."),
|
||||
_doc_items_usagehelp = S("The trader's caravan requires a suitable open space next to the trading post for it to arrive, and takes some time to arrive after being summoned. The post gives a countdown to the caravan's arrival when moused over."),
|
||||
tiles = {"commoditymarket_sign.png^[transformR90", "commoditymarket_sign.png^[transformR270",
|
||||
"commoditymarket_sign.png^commoditymarket_caravan_sign.png", "commoditymarket_sign.png^commoditymarket_caravan_sign.png^[transformFX",
|
||||
"commoditymarket_sign_post.png", "commoditymarket_sign_post.png"},
|
||||
groups = {choppy = 2, oddly_breakable_by_hand = 1,},
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
inventory_image = "commoditymarket_caravan_sign_inventory.png",
|
||||
paramtype= "light",
|
||||
paramtype2 = "facedir",
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{-0.125,-0.5,-0.5,0.125,2.0625,-0.25},
|
||||
{-0.0625,1.4375,-0.25,0.0625,2.0,0.5},
|
||||
},
|
||||
},
|
||||
on_construct = function(pos)
|
||||
local timer = minetest.get_node_timer(pos)
|
||||
timer:start(1.0)
|
||||
end,
|
||||
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
|
||||
local timer = minetest.get_node_timer(pos)
|
||||
timer:start(1.0)
|
||||
end,
|
||||
on_timer = function(pos, elapsed)
|
||||
local node = minetest.get_node(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
if node.name ~= "commoditymarket_fantasy:caravan_post" then
|
||||
return -- the node was removed
|
||||
end
|
||||
local facedir = node.param2
|
||||
local dir = minetest.facedir_to_dir(facedir)
|
||||
local target = vector.add(pos, vector.multiply(dir,3))
|
||||
|
||||
local target_node = minetest.get_node(target)
|
||||
|
||||
if target_node.name:sub(1,string.len("commoditymarket_fantasy:caravan_market")) == "commoditymarket_fantasy:caravan_market" then
|
||||
-- It's already here somehow, shut down timer.
|
||||
meta:set_string("infotext", "")
|
||||
meta:set_float("wait_time", 0)
|
||||
return
|
||||
end
|
||||
|
||||
local is_suitable_space = is_suitable_caravan_space(target, facedir)
|
||||
|
||||
if not is_suitable_space then
|
||||
meta:set_string("infotext", S("Indicated parking area isn't suitable.\nA 5x3 open space with solid ground\nis required for a caravan."))
|
||||
meta:set_float("wait_time", 0)
|
||||
local timer = minetest.get_node_timer(pos)
|
||||
timer:start(1.0)
|
||||
return
|
||||
end
|
||||
|
||||
local wait_time = (meta:get_float("wait_time") or 0) + elapsed
|
||||
meta:set_float("wait_time", wait_time)
|
||||
if wait_time < time_until_caravan then
|
||||
meta:set_string("infotext", S("Caravan summoned\nETA: @1 seconds.", math.floor(time_until_caravan - wait_time)))
|
||||
local timer = minetest.get_node_timer(pos)
|
||||
timer:start(1.0)
|
||||
return
|
||||
end
|
||||
|
||||
-- spawn the caravan. We've already established that the target pos is clear.
|
||||
minetest.set_node(target, {name="commoditymarket_fantasy:caravan_market_"..math.random(1,5), param2=facedir})
|
||||
minetest.sound_play("commoditymarket_register_opened", {
|
||||
pos = target,
|
||||
gain = 1.0, -- default
|
||||
max_hear_distance = 32, -- default, uses an euclidean metric
|
||||
})
|
||||
local timer = minetest.get_node_timer(target)
|
||||
timer:start(dwell_time)
|
||||
meta:set_string("infotext", "")
|
||||
meta:set_float("wait_time", 0)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- "Goblin Exchange"
|
||||
if minetest.settings:get_bool("commoditymarket_enable_goblin_market", true) then
|
||||
|
||||
local goblin_def = {
|
||||
description = S("Goblin Exchange"),
|
||||
long_description = S("One does not usually associate Goblins with the sort of sophistication that running a market requires. Usually one just associates Goblins with savagery and violence. But they understand the principle of tit-for-tat exchange, and if approached correctly they actually respect the concepts of ownership and debt. However, for some peculiar reason they understand this concept in the context of coal lumps. Goblins deal in the standard coal lump as their form of currency, conceptually divided into 100 coal centilumps (though Goblin brokers prefer to \"keep the change\" when giving back actual coal lumps)."),
|
||||
currency = {
|
||||
["default:coal_lump"] = 100
|
||||
},
|
||||
currency_symbol = "¢", --"\u{00A2}" cent symbol
|
||||
inventory_limit = 1000,
|
||||
--sell_limit =, -- no sell limit
|
||||
}
|
||||
|
||||
commoditymarket.register_market("goblin", goblin_def)
|
||||
|
||||
local goblin_protect = minetest.settings:get_bool("commoditymarket_protect_goblin_market", true)
|
||||
local on_blast
|
||||
if goblin_protect then
|
||||
on_blast = function() end
|
||||
end
|
||||
|
||||
minetest.register_node("commoditymarket_fantasy:goblin_market", {
|
||||
description = goblin_def.description,
|
||||
_doc_items_longdesc = goblin_def.long_description,
|
||||
_doc_items_usagehelp = usage_help,
|
||||
tiles = {"default_chest_top.png^(default_coal_block.png^[opacity:128)","default_chest_top.png^(default_coal_block.png^[opacity:128)",
|
||||
"default_chest_side.png^(default_coal_block.png^[opacity:128)","default_chest_side.png^(default_coal_block.png^[opacity:128)",
|
||||
"commoditymarket_empty_shelf.png^(default_coal_block.png^[opacity:128)","default_chest_side.png^(default_coal_block.png^[opacity:128)^commoditymarket_goblin.png",},
|
||||
paramtype2 = "facedir",
|
||||
is_ground_content = false,
|
||||
groups = {choppy = 2, oddly_breakable_by_hand = 1,},
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
|
||||
commoditymarket.show_market("goblin", clicker:get_player_name())
|
||||
end,
|
||||
can_dig = function(pos, player)
|
||||
return not goblin_protect or minetest.check_player_privs(player, "protection_bypass")
|
||||
end,
|
||||
on_blast = on_blast,
|
||||
})
|
||||
end
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
if minetest.settings:get_bool("commoditymarket_enable_under_market", true) then
|
||||
local undermarket_def = {
|
||||
description = S("Undermarket"),
|
||||
long_description = S("Deep in the bowels of the world, below even the goblin-infested warrens and ancient delvings of the dwarves, dark and mysterious beings once dwelt. A few still linger to this day, and facilitate barter for those brave souls willing to travel in their lost realms. The Undermarket uses Mese chips ('₥') as a currency - twenty chips to the Mese fragment. Though traders are loathe to physically break Mese crystals up into units that small, as it renders it useless for other purposes."),
|
||||
currency = {
|
||||
["default:mese"] = 9*9*20,
|
||||
["default:mese_crystal"] = 9*20,
|
||||
["default:mese_crystal_fragment"] = 20
|
||||
},
|
||||
currency_symbol = "₥", --"\u{20A5}" mill sign
|
||||
inventory_limit = 10000,
|
||||
--sell_limit =, -- no sell limit
|
||||
}
|
||||
|
||||
commoditymarket.register_market("under", undermarket_def)
|
||||
|
||||
local under_protect = minetest.settings:get_bool("commoditymarket_protect_under_market", true)
|
||||
local on_blast
|
||||
if under_protect then
|
||||
on_blast = function() end
|
||||
end
|
||||
|
||||
minetest.register_node("commoditymarket_fantasy:under_market", {
|
||||
description = undermarket_def.description,
|
||||
_doc_items_longdesc = undermarket_def.long_description,
|
||||
_doc_items_usagehelp = usage_help,
|
||||
tiles = {"commoditymarket_under_top.png","commoditymarket_under_top.png",
|
||||
"commoditymarket_under.png","commoditymarket_under.png","commoditymarket_under.png","commoditymarket_under.png"},
|
||||
paramtype2 = "facedir",
|
||||
is_ground_content = false,
|
||||
groups = {choppy = 2, oddly_breakable_by_hand = 1,},
|
||||
sounds = default.node_sound_stone_defaults(),
|
||||
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
|
||||
commoditymarket.show_market("under", clicker:get_player_name())
|
||||
end,
|
||||
can_dig = function(pos, player)
|
||||
return not under_protect or minetest.check_player_privs(player, "protection_bypass")
|
||||
end,
|
||||
on_blast = on_blast,
|
||||
})
|
||||
end
|
||||
------------------------------------------------------------------
|
|
@ -1,42 +0,0 @@
|
|||
# textdomain: commoditymarket_fantasy
|
||||
|
||||
|
||||
### init.lua ###
|
||||
|
||||
A gold ingot is far too valuable to use as a basic unit of value, so it has become common practice to divide the standard gold bar into @1 small disks to make trade easier.=
|
||||
|
||||
At this time of day the King's Market is closed.=
|
||||
At this time of day the Night Market is closed.=
|
||||
Caravan summoned@nETA: @1 seconds.=
|
||||
|
||||
Deep in the bowels of the world, below even the goblin-infested warrens and ancient delvings of the dwarves, dark and mysterious beings once dwelt. A few still linger to this day, and facilitate barter for those brave souls willing to travel in their lost realms. The Undermarket uses Mese chips ('₥') as a currency - twenty chips to the Mese fragment. Though traders are loathe to physically break Mese crystals up into units that small, as it renders it useless for other purposes.=
|
||||
|
||||
Goblin Exchange=
|
||||
Gold Coins=
|
||||
|
||||
Gold coins can be deposited and withdrawn from markets that accept them as currency. These markets can make change if you have @1 coins and would like them back in ingot form again.=
|
||||
|
||||
Indicated parking area isn't suitable.@nA 5x3 open space with solid ground@nis required for a caravan.=
|
||||
|
||||
King's Market=
|
||||
Night Market=
|
||||
|
||||
One does not usually associate Goblins with the sort of sophistication that running a market requires. Usually one just associates Goblins with savagery and violence. But they understand the principle of tit-for-tat exchange, and if approached correctly they actually respect the concepts of ownership and debt. However, for some peculiar reason they understand this concept in the context of coal lumps. Goblins deal in the standard coal lump as their form of currency, conceptually divided into 100 coal centilumps (though Goblin brokers prefer to "keep the change" when giving back actual coal lumps).=
|
||||
|
||||
Right-click on this to open the market interface.=
|
||||
Right-click to summon a trader's caravan=
|
||||
|
||||
The largest and most accessible market for the common man, the King's Market uses gold coins as its medium of exchange (or the equivalent in gold ingots - @1 coins to the ingot). However, as a respectable institution of the surface world, the King's Market operates only during the hours of daylight. The purchase and sale of swords and explosives is prohibited in the King's Market. Gold coins are represented by a '☼' symbol.=
|
||||
|
||||
The trader's caravan requires a suitable open space next to the trading post for it to arrive, and takes some time to arrive after being summoned. The post gives a countdown to the caravan's arrival when moused over.=
|
||||
|
||||
This post signals passing caravan traders that customers can be found here, and signals to customers that caravan traders can be found here. If no caravan is present, right-click to summon one.=
|
||||
|
||||
Trader's Caravan=
|
||||
Trading Post=
|
||||
Undermarket=
|
||||
|
||||
Unlike most markets that have well-known fixed locations that travelers congregate to, the network of Trader's Caravans is fluid and dynamic in their locations. A Trader's Caravan can show up anywhere, make modest trades, and then be gone the next time you visit them. These caravans accept gold and gold coins as a currency (one gold ingot to @1 gold coins exchange rate). Any reasonably-wealthy person can create a signpost marking a location where Trader's Caravans will make a stop.=
|
||||
|
||||
When the sun sets and the stalls of the King's Market close, other vendors are just waking up to share their wares. The Night Market is not as voluminous as the King's Market but accepts a wider range of wares. It accepts the same gold coinage of the realm, @1 coins to the gold ingot.=
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
local goblin_enabled = minetest.settings:get_bool("commoditymarket_enable_goblin_market")
|
||||
local under_enabled = minetest.settings:get_bool("commoditymarket_enable_under_market")
|
||||
local goblin_prob = tonumber(minetest.settings:get("commoditymarket_goblin_market_dungeon_prob")) or 0.25
|
||||
local under_prob = tonumber(minetest.settings:get("commoditymarket_under_market_dungeon_prob")) or 0.1
|
||||
|
||||
local goblin_max = tonumber(minetest.settings:get("commoditymarket_goblin_market_dungeon_max")) or 100
|
||||
local goblin_min = tonumber(minetest.settings:get("commoditymarket_goblin_market_dungeon_min")) or -400
|
||||
local under_max = tonumber(minetest.settings:get("commoditymarket_under_market_dungeon_max")) or -500
|
||||
local under_min = tonumber(minetest.settings:get("commoditymarket_under_market_dungeon_min")) or -31000
|
||||
|
||||
local bad_goblin_range = goblin_min >= goblin_max
|
||||
local bad_under_range = under_min >= under_max
|
||||
|
||||
if bad_goblin_range then
|
||||
minetest.log("error", "[commoditymarket] Goblin market dungeon generation range has a higher minimum y than maximum y")
|
||||
end
|
||||
if bad_under_range then
|
||||
minetest.log("error", "[commoditymarket] Undermarket dungeon generation range has a higher minimum y than maximum y")
|
||||
end
|
||||
|
||||
local gen_goblin = goblin_enabled and goblin_prob > 0 and not bad_goblin_range
|
||||
local gen_under = under_enabled and under_prob > 0 and not bad_under_range
|
||||
|
||||
if not (gen_goblin or gen_under) then
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------
|
||||
-- The following is shamelessly copied from dungeon_loot and tweaked for placing markets instead of chests
|
||||
--Licensed under the MIT License (MIT) Copyright (C) 2017 sfan5
|
||||
|
||||
minetest.set_gen_notify({dungeon = true, temple = true})
|
||||
|
||||
local function noise3d_integer(noise, pos)
|
||||
return math.abs(math.floor(noise:get_3d(pos) * 0x7fffffff))
|
||||
end
|
||||
|
||||
local is_wall = function(node)
|
||||
return node.name ~= "air" and node.name ~= "ignore"
|
||||
end
|
||||
|
||||
local function find_walls(cpos)
|
||||
local dirs = {{x=1, z=0}, {x=-1, z=0}, {x=0, z=1}, {x=0, z=-1}}
|
||||
local get_node = minetest.get_node
|
||||
|
||||
local ret = {}
|
||||
local mindist = {x=0, z=0}
|
||||
local min = function(a, b) return a ~= 0 and math.min(a, b) or b end
|
||||
for _, dir in ipairs(dirs) do
|
||||
for i = 1, 9 do -- 9 = max room size / 2
|
||||
local pos = vector.add(cpos, {x=dir.x*i, y=0, z=dir.z*i})
|
||||
|
||||
-- continue in that direction until we find a wall-like node
|
||||
local node = get_node(pos)
|
||||
if is_wall(node) then
|
||||
local front_below = vector.subtract(pos, {x=dir.x, y=1, z=dir.z})
|
||||
local above = vector.add(pos, {x=0, y=1, z=0})
|
||||
|
||||
-- check that it:
|
||||
--- is at least 2 nodes high (not a staircase)
|
||||
--- has a floor
|
||||
if is_wall(get_node(front_below)) and is_wall(get_node(above)) then
|
||||
pos = vector.subtract(pos, {x=dir.x, y=0, z=dir.z}) -- move goblin markets one node away from the wall
|
||||
table.insert(ret, {pos = pos, facing = {x=-dir.x, y=0, z=-dir.z}})
|
||||
if dir.z == 0 then
|
||||
mindist.x = min(mindist.x, i-1)
|
||||
else
|
||||
mindist.z = min(mindist.z, i-1)
|
||||
end
|
||||
end
|
||||
-- abort even if it wasn't a wall cause something is in the way
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
walls = ret,
|
||||
size = {x=mindist.x*2, z=mindist.z*2},
|
||||
cpos = cpos,
|
||||
}
|
||||
end
|
||||
|
||||
minetest.register_on_generated(function(minp, maxp, blockseed)
|
||||
local min_y = minp.y
|
||||
local max_y = maxp.y
|
||||
|
||||
local gen_goblin_range = gen_goblin and not (min_y > goblin_max or max_y < goblin_min)
|
||||
local gen_under_range = gen_under and not (min_y > under_max or max_y < under_min)
|
||||
|
||||
if not (gen_goblin_range or gen_under_range) then
|
||||
-- out of both ranges
|
||||
return
|
||||
end
|
||||
|
||||
local gennotify = minetest.get_mapgen_object("gennotify")
|
||||
local poslist = gennotify["dungeon"] or {}
|
||||
for _, entry in ipairs(gennotify["temple"] or {}) do
|
||||
table.insert(poslist, entry)
|
||||
end
|
||||
if #poslist == 0 then return end
|
||||
|
||||
local noise = minetest.get_perlin(151994, 4, 0.5, 1)
|
||||
local rand = PcgRandom(noise3d_integer(noise, poslist[1]))
|
||||
|
||||
local rooms = {}
|
||||
-- process at most 8 rooms to keep runtime of this predictable
|
||||
local num_process = math.min(#poslist, 8)
|
||||
for i = 1, num_process do
|
||||
local room = find_walls(poslist[i])
|
||||
-- skip small rooms and everything that doesn't at least have 3 walls
|
||||
if math.min(room.size.x, room.size.z) >= 4 and #room.walls >= 3 then
|
||||
table.insert(rooms, room)
|
||||
end
|
||||
end
|
||||
if #rooms == 0 then return end
|
||||
|
||||
if gen_under_range and rand:next(0, 2147483647)/2147483647 < under_prob then
|
||||
-- choose a random room
|
||||
local room = rooms[rand:next(1, #rooms)]
|
||||
local under_loc = room.cpos
|
||||
|
||||
-- put undermarkets in the center of the room
|
||||
if minetest.get_node(under_loc).name == "air"
|
||||
and is_wall(vector.subtract(under_loc, {x=0, y=1, z=0})) then
|
||||
minetest.add_node(under_loc, {name="commoditymarket:under_market"})
|
||||
end
|
||||
end
|
||||
|
||||
if gen_goblin_range and rand:next(0, 2147483647)/2147483647 < goblin_prob then
|
||||
-- choose a random room
|
||||
local room = rooms[rand:next(1, #rooms)]
|
||||
|
||||
-- choose place somewhere in front of any of the walls
|
||||
local wall = room.walls[rand:next(1, #room.walls)]
|
||||
local v, vi -- vector / axis that runs alongside the wall
|
||||
if wall.facing.x ~= 0 then
|
||||
v, vi = {x=0, y=0, z=1}, "z"
|
||||
else
|
||||
v, vi = {x=1, y=0, z=0}, "x"
|
||||
end
|
||||
local marketpos = vector.add(wall.pos, wall.facing)
|
||||
local off = rand:next(-room.size[vi]/2 + 1, room.size[vi]/2 - 1)
|
||||
marketpos = vector.add(marketpos, vector.multiply(v, off))
|
||||
|
||||
if minetest.get_node(marketpos).name == "air" then
|
||||
-- make it face inwards to the room
|
||||
local facedir = minetest.dir_to_facedir(vector.multiply(wall.facing, -1))
|
||||
minetest.add_node(marketpos, {name = "commoditymarket:goblin_market", param2 = facedir})
|
||||
end
|
||||
end
|
||||
end)
|
|
@ -1,4 +0,0 @@
|
|||
name = commoditymarket_fantasy
|
||||
description = Adds a number of fantasy-themed marketplaces
|
||||
depends = commoditymarket, default
|
||||
optional_depends = doc
|
|
@ -1,852 +0,0 @@
|
|||
# Blender v2.79 (sub 0) OBJ File: 'wagon.blend'
|
||||
# www.blender.org
|
||||
g Cube.004_Cube.012
|
||||
v 0.234629 1.147573 -1.166732
|
||||
v -0.234629 1.147573 -1.166732
|
||||
v 0.234629 0.678315 -1.166732
|
||||
v -0.234629 0.678315 -1.166732
|
||||
v 0.731823 1.147573 -0.234629
|
||||
v 0.731823 1.147573 0.234629
|
||||
v 0.731823 0.678315 -0.234629
|
||||
v 0.731823 0.678315 0.234629
|
||||
v -0.731823 1.147573 0.234629
|
||||
v -0.731823 1.147573 -0.234629
|
||||
v -0.731823 0.678315 0.234629
|
||||
v -0.731823 0.678315 -0.234629
|
||||
v -0.259667 0.016670 1.168107
|
||||
v 0.259668 0.016670 1.168107
|
||||
v -0.259667 1.170112 1.168107
|
||||
v 0.259668 1.170112 1.168107
|
||||
vt 0.000000 0.533333
|
||||
vt 0.000000 1.000000
|
||||
vt 1.000000 1.000000
|
||||
vt 1.000000 0.533333
|
||||
vt -0.000000 0.533333
|
||||
vt 1.000000 0.533333
|
||||
vt 1.000000 1.000000
|
||||
vt -0.000000 1.000000
|
||||
vt 0.000000 0.533333
|
||||
vt 1.000000 0.533333
|
||||
vt 1.000000 1.000000
|
||||
vt 0.000000 1.000000
|
||||
vt 0.000000 0.000000
|
||||
vt 1.000000 0.000000
|
||||
vt 1.000000 1.000000
|
||||
vt 0.000000 1.000000
|
||||
vn 0.0000 0.0000 -1.0000
|
||||
vn 1.0000 0.0000 0.0000
|
||||
vn -1.0000 0.0000 0.0000
|
||||
vn 0.0000 0.0000 1.0000
|
||||
s off
|
||||
f 4/1/1 2/2/1 1/3/1 3/4/1
|
||||
f 8/5/2 7/6/2 5/7/2 6/8/2
|
||||
f 12/9/3 11/10/3 9/11/3 10/12/3
|
||||
f 13/13/4 14/14/4 16/15/4 15/16/4
|
||||
g Cube.003_Cube.011
|
||||
v -0.720902 -0.096672 1.153442
|
||||
v -0.720901 -0.096672 -1.153443
|
||||
v -0.720902 1.200951 1.153442
|
||||
v -0.720902 1.200951 -1.153443
|
||||
v -0.509754 1.608755 -1.153443
|
||||
v -0.509754 1.608755 1.153443
|
||||
v -0.792992 1.200951 -1.268787
|
||||
v -0.792992 1.200951 1.268787
|
||||
v -0.560730 1.649535 -1.268787
|
||||
v -0.560730 1.649535 1.268787
|
||||
v -0.726391 1.178882 -1.268787
|
||||
v -0.726391 1.178882 1.268787
|
||||
v -0.509756 1.608756 -1.268787
|
||||
v -0.509756 1.608756 1.268787
|
||||
v -0.692620 0.037741 -1.018928
|
||||
v -0.692620 0.163735 -1.089024
|
||||
v -0.692620 -0.523026 -2.026881
|
||||
v -0.692620 -0.397032 -2.096977
|
||||
v -0.512394 0.037741 -1.018928
|
||||
v -0.512394 0.163735 -1.089024
|
||||
v -0.512394 -0.523026 -2.026881
|
||||
v -0.512394 -0.397032 -2.096977
|
||||
v -0.324406 0.582331 -1.134006
|
||||
v -0.324406 0.654421 -1.134006
|
||||
v -0.324406 0.582331 -1.441803
|
||||
v -0.324406 0.654421 -1.441803
|
||||
v 0.720901 -0.096672 -1.153443
|
||||
v 0.720902 -0.096672 1.153442
|
||||
v -0.000000 1.200951 1.153443
|
||||
v 0.720902 1.200951 1.153442
|
||||
v -0.000000 1.200951 -1.153443
|
||||
v -0.000000 1.777673 -1.153443
|
||||
v -0.000000 1.777673 1.153443
|
||||
v 0.720902 1.200951 -1.153443
|
||||
v 0.509754 1.608755 -1.153443
|
||||
v 0.509754 1.608755 1.153443
|
||||
v -0.000000 1.835345 1.268787
|
||||
v -0.000000 1.835345 -1.268787
|
||||
v 0.792992 1.200951 -1.268787
|
||||
v 0.792992 1.200951 1.268787
|
||||
v 0.560730 1.649535 -1.268787
|
||||
v 0.560730 1.649535 1.268787
|
||||
v -0.000000 1.777673 1.268787
|
||||
v -0.000000 1.777673 -1.268787
|
||||
v 0.726391 1.178882 -1.268787
|
||||
v 0.726391 1.178882 1.268787
|
||||
v 0.509756 1.608756 -1.268787
|
||||
v 0.509756 1.608756 1.268787
|
||||
v 0.692620 0.037741 -1.018928
|
||||
v 0.692620 0.163735 -1.089024
|
||||
v 0.692620 -0.523026 -2.026881
|
||||
v 0.692620 -0.397032 -2.096977
|
||||
v 0.512394 0.037741 -1.018928
|
||||
v 0.512394 0.163735 -1.089024
|
||||
v 0.512394 -0.523026 -2.026881
|
||||
v 0.512394 -0.397032 -2.096977
|
||||
v 0.324406 0.582331 -1.134006
|
||||
v 0.324406 0.654421 -1.134006
|
||||
v 0.324406 0.582331 -1.441803
|
||||
v 0.324406 0.654421 -1.441803
|
||||
vt -1.425823 3.001615
|
||||
vt -1.425823 2.882734
|
||||
vt -0.355896 2.882734
|
||||
vt -0.355896 3.001615
|
||||
vt 0.375155 -3.373241
|
||||
vt 0.375155 -2.579424
|
||||
vt -3.809447 -2.579426
|
||||
vt -3.809447 -3.373241
|
||||
vt 0.375154 -1.693858
|
||||
vt -3.809447 -1.693859
|
||||
vt 0.381165 -0.154615
|
||||
vt 4.565710 -0.154608
|
||||
vt 4.565710 -0.038908
|
||||
vt 0.381165 -0.038908
|
||||
vt -0.235621 2.375158
|
||||
vt -0.235621 3.208172
|
||||
vt -0.341187 3.187105
|
||||
vt -0.349886 2.393338
|
||||
vt 2.764791 -0.422899
|
||||
vt 2.764791 -1.255914
|
||||
vt 2.879056 -1.237734
|
||||
vt 2.870357 -0.443966
|
||||
vt -1.425823 3.804063
|
||||
vt -0.451704 3.804062
|
||||
vt -0.481619 3.894339
|
||||
vt -1.367183 3.894337
|
||||
vt -0.451699 3.798052
|
||||
vt -1.425823 3.798051
|
||||
vt -1.367184 3.707779
|
||||
vt -0.481614 3.707775
|
||||
vt 2.764791 -2.832267
|
||||
vt 2.764791 -3.070028
|
||||
vt 4.666879 -3.070028
|
||||
vt 4.666879 -2.832265
|
||||
vt 2.885066 -1.553966
|
||||
vt 2.885066 -1.791727
|
||||
vt 3.182267 -1.791727
|
||||
vt 3.182267 -1.553966
|
||||
vt 4.666881 -2.826254
|
||||
vt 4.666881 -2.588493
|
||||
vt 2.764791 -2.588493
|
||||
vt 2.764791 -2.826255
|
||||
vt 3.182267 -1.547955
|
||||
vt 3.182267 -1.310194
|
||||
vt 2.885066 -1.310194
|
||||
vt 2.885066 -1.547955
|
||||
vt 1.902838 4.586475
|
||||
vt 1.902838 4.883677
|
||||
vt 0.000747 4.883678
|
||||
vt 0.000747 4.586475
|
||||
vt 1.902835 4.283262
|
||||
vt 1.902835 4.580463
|
||||
vt 0.000747 4.580466
|
||||
vt 0.000747 4.283262
|
||||
vt 2.764791 -0.298008
|
||||
vt 2.764791 -0.416888
|
||||
vt 3.272367 -0.416888
|
||||
vt 3.272367 -0.298008
|
||||
vt -1.425823 2.375158
|
||||
vt -0.355896 2.375158
|
||||
vt -0.355896 3.509191
|
||||
vt -1.425823 3.509191
|
||||
vt -3.809448 2.369149
|
||||
vt -3.809448 -0.008467
|
||||
vt -0.005263 -0.008466
|
||||
vt -0.005264 2.369148
|
||||
vt 0.375154 -0.014477
|
||||
vt -3.809448 -0.014477
|
||||
vt -3.809448 -0.808292
|
||||
vt 0.375154 -0.808291
|
||||
vt 0.381165 -0.160630
|
||||
vt 0.381165 -0.276331
|
||||
vt 4.565711 -0.276331
|
||||
vt 4.565711 -0.160625
|
||||
vt 2.764791 -2.094939
|
||||
vt 2.879056 -2.076759
|
||||
vt 2.870358 -1.282992
|
||||
vt 2.764791 -1.261924
|
||||
vt -0.115346 3.208172
|
||||
vt -0.220912 3.187105
|
||||
vt -0.229611 2.393338
|
||||
vt -0.115346 2.375158
|
||||
vt -0.451703 3.515201
|
||||
vt -0.510342 3.605475
|
||||
vt -1.395907 3.605478
|
||||
vt -1.425823 3.515201
|
||||
vt -1.425823 3.701766
|
||||
vt -1.395907 3.611488
|
||||
vt -0.510338 3.611492
|
||||
vt -0.451699 3.701765
|
||||
vt 2.764791 -2.338711
|
||||
vt 4.666883 -2.338711
|
||||
vt 4.666883 -2.100949
|
||||
vt 2.764791 -2.100949
|
||||
vt 3.122827 -2.094939
|
||||
vt 3.122827 -1.797738
|
||||
vt 2.885066 -1.797738
|
||||
vt 2.885066 -2.094939
|
||||
vt 4.666883 -2.344721
|
||||
vt 2.764791 -2.344721
|
||||
vt 2.764791 -2.582482
|
||||
vt 4.666883 -2.582483
|
||||
vt -0.349886 3.214182
|
||||
vt -0.052684 3.214182
|
||||
vt -0.052684 3.451943
|
||||
vt -0.349886 3.451943
|
||||
vt 4.666882 -3.076038
|
||||
vt 2.764791 -3.076038
|
||||
vt 2.764791 -3.373240
|
||||
vt 4.666883 -3.373241
|
||||
vt 3.810932 4.580466
|
||||
vt 1.908845 4.580463
|
||||
vt 1.908845 4.283263
|
||||
vt 3.810934 4.283262
|
||||
vt 2.885066 -1.304184
|
||||
vt 3.392642 -1.304184
|
||||
vt 3.392642 -1.185303
|
||||
vt 2.885066 -1.185303
|
||||
vt 0.061193 0.029274
|
||||
vt 2.438806 0.029274
|
||||
vt 2.438807 2.169126
|
||||
vt 1.250000 2.169127
|
||||
vt 0.061193 2.169127
|
||||
vt 2.438741 0.029448
|
||||
vt 2.438741 2.169301
|
||||
vt 6.242925 2.169302
|
||||
vt 6.242926 0.029448
|
||||
vt 2.090614 2.841618
|
||||
vt 1.250000 2.169127
|
||||
vt 0.409386 2.841618
|
||||
vt 0.061192 2.169127
|
||||
vt 1.250000 3.120173
|
||||
vt 1.250000 3.120173
|
||||
vt 2.438532 0.029230
|
||||
vt 6.242717 0.029228
|
||||
vt 6.242717 2.169081
|
||||
vt 2.438534 2.169083
|
||||
vt 0.409386 2.841618
|
||||
vt 2.438807 2.169127
|
||||
vt 2.090614 2.841617
|
||||
vt 0.061193 0.029273
|
||||
vt 2.438807 0.029273
|
||||
vn 0.0000 0.0000 -1.0000
|
||||
vn 0.8930 -0.4500 -0.0000
|
||||
vn 0.3145 -0.9492 0.0000
|
||||
vn -0.3145 -0.9492 0.0000
|
||||
vn 0.0000 0.0000 1.0000
|
||||
vn -1.0000 0.0000 0.0000
|
||||
vn 0.0000 -0.4862 -0.8739
|
||||
vn 1.0000 -0.0000 0.0000
|
||||
vn 0.0000 0.4862 0.8739
|
||||
vn 0.0000 -0.8739 0.4862
|
||||
vn 0.0000 0.8739 -0.4862
|
||||
vn 0.0000 1.0000 0.0000
|
||||
vn 0.0000 -1.0000 0.0000
|
||||
vn -0.8930 -0.4500 -0.0000
|
||||
s off
|
||||
f 41/17/5 42/18/5 76/19/5 75/20/5
|
||||
f 27/21/6 29/22/6 30/23/6 28/24/6
|
||||
f 29/22/7 60/25/7 59/26/7 30/23/7
|
||||
f 24/27/8 23/28/8 27/29/8 28/30/8
|
||||
f 23/31/5 25/32/5 29/33/5 27/34/5
|
||||
f 26/35/9 24/36/9 28/37/9 30/38/9
|
||||
f 25/39/5 54/40/5 60/41/5 29/42/5
|
||||
f 53/43/9 26/44/9 30/45/9 59/46/9
|
||||
f 31/47/10 32/48/10 34/49/10 33/50/10
|
||||
f 33/51/11 34/52/11 38/53/11 37/54/11
|
||||
f 37/55/12 38/56/12 36/57/12 35/58/12
|
||||
f 35/59/13 36/60/13 32/61/13 31/62/13
|
||||
f 33/63/14 37/64/14 35/65/14 31/66/14
|
||||
f 38/67/15 34/68/15 32/69/15 36/70/15
|
||||
f 39/71/10 40/72/10 42/73/10 41/74/10
|
||||
f 76/19/16 42/18/16 40/75/16 74/76/16
|
||||
f 41/17/17 75/20/17 73/77/17 39/78/17
|
||||
f 44/79/17 17/80/17 18/81/17 43/82/17
|
||||
f 61/83/18 62/84/18 64/85/18 63/86/18
|
||||
f 63/86/8 64/85/8 59/26/8 60/25/8
|
||||
f 56/87/7 62/88/7 61/89/7 55/90/7
|
||||
f 55/91/5 61/92/5 63/93/5 57/94/5
|
||||
f 58/95/9 64/96/9 62/97/9 56/98/9
|
||||
f 57/99/5 63/100/5 60/101/5 54/102/5
|
||||
f 53/103/9 59/104/9 64/105/9 58/106/9
|
||||
f 65/107/12 67/108/12 68/109/12 66/110/12
|
||||
f 67/111/11 71/112/11 72/113/11 68/114/11
|
||||
f 71/115/10 69/116/10 70/117/10 72/118/10
|
||||
f 69/119/13 65/120/13 66/121/13 70/122/13
|
||||
f 67/123/14 65/124/14 69/125/14 71/126/14
|
||||
f 72/127/15 70/128/15 66/129/15 68/130/15
|
||||
f 73/131/12 75/132/12 76/133/12 74/134/12
|
||||
f 43/135/5 18/136/5 20/137/5 47/138/5 50/139/5
|
||||
f 17/140/10 19/141/10 20/142/10 18/143/10
|
||||
f 47/138/5 20/137/5 21/144/5
|
||||
f 45/145/9 22/146/9 19/147/9
|
||||
f 47/138/5 21/144/5 48/148/5
|
||||
f 45/145/9 49/149/9 22/146/9
|
||||
f 44/150/12 43/151/12 50/152/12 46/153/12
|
||||
f 47/138/5 51/154/5 50/139/5
|
||||
f 45/145/9 46/155/9 52/156/9
|
||||
f 47/138/5 48/148/5 51/154/5
|
||||
f 45/145/9 52/156/9 49/149/9
|
||||
f 45/145/9 19/147/9 17/157/9 44/158/9 46/155/9
|
||||
g Cube.002_Cube.010
|
||||
v -0.901127 0.047509 -0.576721
|
||||
v -0.720902 0.047509 -0.576721
|
||||
v -0.901127 0.268210 -1.109542
|
||||
v -0.720902 0.268210 -1.109542
|
||||
v -0.901127 -0.173193 -1.109542
|
||||
v -0.720902 -0.173193 -1.109542
|
||||
v -0.901127 -0.485312 -0.797423
|
||||
v -0.720902 -0.485312 -0.797423
|
||||
v -0.901127 -0.485312 -0.356020
|
||||
v -0.720902 -0.485312 -0.356020
|
||||
v -0.901127 -0.173193 -0.043900
|
||||
v -0.720902 -0.173193 -0.043900
|
||||
v -0.901127 0.268210 -0.043900
|
||||
v -0.720902 0.268210 -0.043900
|
||||
v -0.901127 0.580330 -0.356020
|
||||
v -0.720902 0.580330 -0.356020
|
||||
v -0.901127 0.580330 -0.797423
|
||||
v -0.720902 0.580330 -0.797423
|
||||
v -0.901127 0.047509 0.576721
|
||||
v -0.720902 0.047509 0.576721
|
||||
v -0.901127 0.268210 0.043900
|
||||
v -0.720902 0.268210 0.043900
|
||||
v -0.901127 -0.173193 0.043900
|
||||
v -0.720902 -0.173193 0.043900
|
||||
v -0.901127 -0.485312 0.356020
|
||||
v -0.720902 -0.485312 0.356020
|
||||
v -0.901127 -0.485312 0.797423
|
||||
v -0.720902 -0.485312 0.797423
|
||||
v -0.901127 -0.173193 1.109542
|
||||
v -0.720902 -0.173193 1.109542
|
||||
v -0.901127 0.268210 1.109542
|
||||
v -0.720902 0.268210 1.109542
|
||||
v -0.901127 0.580330 0.797423
|
||||
v -0.720902 0.580330 0.797423
|
||||
v -0.901127 0.580330 0.356020
|
||||
v -0.720902 0.580330 0.356020
|
||||
v 0.901127 0.047509 -0.576721
|
||||
v 0.720902 0.047509 -0.576721
|
||||
v 0.901127 0.268210 -1.109542
|
||||
v 0.720902 0.268210 -1.109542
|
||||
v 0.901127 -0.173193 -1.109542
|
||||
v 0.720902 -0.173193 -1.109542
|
||||
v 0.901127 -0.485312 -0.797423
|
||||
v 0.720902 -0.485312 -0.797423
|
||||
v 0.901127 -0.485312 -0.356020
|
||||
v 0.720902 -0.485312 -0.356020
|
||||
v 0.901127 -0.173193 -0.043900
|
||||
v 0.720902 -0.173193 -0.043900
|
||||
v 0.901127 0.268210 -0.043900
|
||||
v 0.720902 0.268210 -0.043900
|
||||
v 0.901127 0.580330 -0.356020
|
||||
v 0.720902 0.580330 -0.356020
|
||||
v 0.901127 0.580330 -0.797423
|
||||
v 0.720902 0.580330 -0.797423
|
||||
v 0.901127 0.047509 0.576721
|
||||
v 0.720902 0.047509 0.576721
|
||||
v 0.901127 0.268210 0.043900
|
||||
v 0.720902 0.268210 0.043900
|
||||
v 0.901127 -0.173193 0.043900
|
||||
v 0.720902 -0.173193 0.043900
|
||||
v 0.901127 -0.485312 0.356020
|
||||
v 0.720902 -0.485312 0.356020
|
||||
v 0.901127 -0.485312 0.797423
|
||||
v 0.720902 -0.485312 0.797423
|
||||
v 0.901127 -0.173193 1.109542
|
||||
v 0.720902 -0.173193 1.109542
|
||||
v 0.901127 0.268210 1.109542
|
||||
v 0.720902 0.268210 1.109542
|
||||
v 0.901127 0.580330 0.797423
|
||||
v 0.720902 0.580330 0.797423
|
||||
v 0.901127 0.580330 0.356020
|
||||
v 0.720902 0.580330 0.356020
|
||||
vt 0.499873 0.992213
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.499873 0.992213
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.499873 0.992213
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.499873 0.992213
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.499873 0.992213
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.499873 0.992213
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.499873 0.992213
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.499873 0.992213
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.914261 -0.008208
|
||||
vt 0.085485 -0.008208
|
||||
vn -1.0000 0.0000 0.0000
|
||||
vn 1.0000 0.0000 0.0000
|
||||
s off
|
||||
f 77/159/19 79/160/19 81/161/19
|
||||
f 78/162/20 82/163/20 80/164/20
|
||||
f 77/159/19 81/165/19 83/166/19
|
||||
f 78/162/20 84/167/20 82/168/20
|
||||
f 77/159/19 83/169/19 85/170/19
|
||||
f 78/162/20 86/171/20 84/172/20
|
||||
f 77/159/19 85/173/19 87/174/19
|
||||
f 78/162/20 88/175/20 86/176/20
|
||||
f 77/159/19 87/177/19 89/178/19
|
||||
f 78/162/20 90/179/20 88/180/20
|
||||
f 77/159/19 89/181/19 91/182/19
|
||||
f 78/162/20 92/183/20 90/184/20
|
||||
f 77/159/19 91/185/19 93/186/19
|
||||
f 78/162/20 94/187/20 92/188/20
|
||||
f 77/159/19 93/189/19 79/190/19
|
||||
f 78/162/20 80/191/20 94/192/20
|
||||
f 95/193/19 97/194/19 99/195/19
|
||||
f 96/196/20 100/197/20 98/198/20
|
||||
f 95/193/19 99/199/19 101/200/19
|
||||
f 96/196/20 102/201/20 100/202/20
|
||||
f 95/193/19 101/203/19 103/204/19
|
||||
f 96/196/20 104/205/20 102/206/20
|
||||
f 95/193/19 103/207/19 105/208/19
|
||||
f 96/196/20 106/209/20 104/210/20
|
||||
f 95/193/19 105/211/19 107/212/19
|
||||
f 96/196/20 108/213/20 106/214/20
|
||||
f 95/193/19 107/215/19 109/216/19
|
||||
f 96/196/20 110/217/20 108/218/20
|
||||
f 95/193/19 109/219/19 111/220/19
|
||||
f 96/196/20 112/221/20 110/222/20
|
||||
f 95/193/19 111/223/19 97/224/19
|
||||
f 96/196/20 98/225/20 112/226/20
|
||||
f 113/227/20 117/228/20 115/229/20
|
||||
f 114/230/19 116/231/19 118/232/19
|
||||
f 113/227/20 119/233/20 117/234/20
|
||||
f 114/230/19 118/235/19 120/236/19
|
||||
f 113/227/20 121/237/20 119/238/20
|
||||
f 114/230/19 120/239/19 122/240/19
|
||||
f 113/227/20 123/241/20 121/242/20
|
||||
f 114/230/19 122/243/19 124/244/19
|
||||
f 113/227/20 125/245/20 123/246/20
|
||||
f 114/230/19 124/247/19 126/248/19
|
||||
f 113/227/20 127/249/20 125/250/20
|
||||
f 114/230/19 126/251/19 128/252/19
|
||||
f 113/227/20 129/253/20 127/254/20
|
||||
f 114/230/19 128/255/19 130/256/19
|
||||
f 113/227/20 115/257/20 129/258/20
|
||||
f 114/230/19 130/259/19 116/260/19
|
||||
f 131/261/20 135/262/20 133/263/20
|
||||
f 132/264/19 134/265/19 136/266/19
|
||||
f 131/261/20 137/267/20 135/268/20
|
||||
f 132/264/19 136/269/19 138/270/19
|
||||
f 131/261/20 139/271/20 137/272/20
|
||||
f 132/264/19 138/273/19 140/274/19
|
||||
f 131/261/20 141/275/20 139/276/20
|
||||
f 132/264/19 140/277/19 142/278/19
|
||||
f 131/261/20 143/279/20 141/280/20
|
||||
f 132/264/19 142/281/19 144/282/19
|
||||
f 131/261/20 145/283/20 143/284/20
|
||||
f 132/264/19 144/285/19 146/286/19
|
||||
f 131/261/20 147/287/20 145/288/20
|
||||
f 132/264/19 146/289/19 148/290/19
|
||||
f 131/261/20 133/291/20 147/292/20
|
||||
f 132/264/19 148/293/19 134/294/19
|
||||
g Cube.001_Cube.009
|
||||
v -0.901127 0.268210 -1.109542
|
||||
v -0.720902 0.268210 -1.109542
|
||||
v -0.901127 -0.173193 -1.109542
|
||||
v -0.720902 -0.173193 -1.109542
|
||||
v -0.901127 -0.485312 -0.797423
|
||||
v -0.720902 -0.485312 -0.797423
|
||||
v -0.901127 -0.485312 -0.356020
|
||||
v -0.720902 -0.485312 -0.356020
|
||||
v -0.901127 -0.173193 -0.043900
|
||||
v -0.720902 -0.173193 -0.043900
|
||||
v -0.901127 0.268210 -0.043900
|
||||
v -0.720902 0.268210 -0.043900
|
||||
v -0.901127 0.580330 -0.356020
|
||||
v -0.720902 0.580330 -0.356020
|
||||
v -0.901127 0.580330 -0.797423
|
||||
v -0.720902 0.580330 -0.797423
|
||||
v -0.901127 0.268210 0.043900
|
||||
v -0.720902 0.268210 0.043900
|
||||
v -0.901127 -0.173193 0.043900
|
||||
v -0.720902 -0.173193 0.043900
|
||||
v -0.901127 -0.485312 0.356020
|
||||
v -0.720902 -0.485312 0.356020
|
||||
v -0.901127 -0.485312 0.797423
|
||||
v -0.720902 -0.485312 0.797423
|
||||
v -0.901127 -0.173193 1.109542
|
||||
v -0.720902 -0.173193 1.109542
|
||||
v -0.901127 0.268210 1.109542
|
||||
v -0.720902 0.268210 1.109542
|
||||
v -0.901127 0.580330 0.797423
|
||||
v -0.720902 0.580330 0.797423
|
||||
v -0.901127 0.580330 0.356020
|
||||
v -0.720902 0.580330 0.356020
|
||||
v 0.901127 0.268210 -1.109542
|
||||
v 0.720902 0.268210 -1.109542
|
||||
v 0.901127 -0.173193 -1.109542
|
||||
v 0.720902 -0.173193 -1.109542
|
||||
v 0.901127 -0.485312 -0.797423
|
||||
v 0.720902 -0.485312 -0.797423
|
||||
v 0.901127 -0.485312 -0.356020
|
||||
v 0.720902 -0.485312 -0.356020
|
||||
v 0.901127 -0.173193 -0.043900
|
||||
v 0.720902 -0.173193 -0.043900
|
||||
v 0.901127 0.268210 -0.043900
|
||||
v 0.720902 0.268210 -0.043900
|
||||
v 0.901127 0.580330 -0.356020
|
||||
v 0.720902 0.580330 -0.356020
|
||||
v 0.901127 0.580330 -0.797423
|
||||
v 0.720902 0.580330 -0.797423
|
||||
v 0.901127 0.268210 0.043900
|
||||
v 0.720902 0.268210 0.043900
|
||||
v 0.901127 -0.173193 0.043900
|
||||
v 0.720902 -0.173193 0.043900
|
||||
v 0.901127 -0.485312 0.356020
|
||||
v 0.720902 -0.485312 0.356020
|
||||
v 0.901127 -0.485312 0.797423
|
||||
v 0.720902 -0.485312 0.797423
|
||||
v 0.901127 -0.173193 1.109542
|
||||
v 0.720902 -0.173193 1.109542
|
||||
v 0.901127 0.268210 1.109542
|
||||
v 0.720902 0.268210 1.109542
|
||||
v 0.901127 0.580330 0.797423
|
||||
v 0.720902 0.580330 0.797423
|
||||
v 0.901127 0.580330 0.356020
|
||||
v 0.720902 0.580330 0.356020
|
||||
vt 0.279164 0.493799
|
||||
vt 0.072560 0.493799
|
||||
vt 0.072560 -0.012210
|
||||
vt 0.279164 -0.012210
|
||||
vt 0.072559 -0.518219
|
||||
vt 0.279163 -0.518219
|
||||
vt 0.072559 -1.024229
|
||||
vt 0.279163 -1.024229
|
||||
vt 0.072559 -1.530238
|
||||
vt 0.279163 -1.530238
|
||||
vt 0.279165 2.517837
|
||||
vt 0.072561 2.517837
|
||||
vt 0.072561 2.011827
|
||||
vt 0.279165 2.011827
|
||||
vt 0.072561 1.505818
|
||||
vt 0.279165 1.505818
|
||||
vt 0.072560 0.999809
|
||||
vt 0.279165 0.999809
|
||||
vt 0.694571 2.011827
|
||||
vt 0.901175 2.011827
|
||||
vt 0.901175 2.517837
|
||||
vt 0.694571 2.517837
|
||||
vt 0.694571 -1.530238
|
||||
vt 0.901175 -1.530238
|
||||
vt 0.901175 -1.024228
|
||||
vt 0.694571 -1.024228
|
||||
vt 0.901175 -0.518219
|
||||
vt 0.694571 -0.518219
|
||||
vt 0.901175 -0.012210
|
||||
vt 0.694571 -0.012210
|
||||
vt 0.901174 0.493799
|
||||
vt 0.694570 0.493799
|
||||
vt 0.901174 0.999809
|
||||
vt 0.694570 0.999809
|
||||
vt 0.901175 1.505817
|
||||
vt 0.694570 1.505818
|
||||
vt 0.279897 0.493799
|
||||
vt 0.279898 -0.012210
|
||||
vt 0.486502 -0.012210
|
||||
vt 0.486501 0.493799
|
||||
vt 0.279898 -0.518219
|
||||
vt 0.486502 -0.518219
|
||||
vt 0.279898 -1.024228
|
||||
vt 0.486502 -1.024229
|
||||
vt 0.279898 -1.530238
|
||||
vt 0.486502 -1.530238
|
||||
vt 0.279897 2.517837
|
||||
vt 0.279897 2.011827
|
||||
vt 0.486501 2.011827
|
||||
vt 0.486501 2.517837
|
||||
vt 0.279897 1.505818
|
||||
vt 0.486501 1.505818
|
||||
vt 0.279897 0.999809
|
||||
vt 0.486501 0.999809
|
||||
vt 0.693838 2.011827
|
||||
vt 0.693838 2.517837
|
||||
vt 0.487234 2.517837
|
||||
vt 0.487234 2.011827
|
||||
vt 0.693839 -1.530238
|
||||
vt 0.693838 -1.024229
|
||||
vt 0.487234 -1.024229
|
||||
vt 0.487234 -1.530238
|
||||
vt 0.693838 -0.518220
|
||||
vt 0.487234 -0.518220
|
||||
vt 0.693838 -0.012210
|
||||
vt 0.487234 -0.012210
|
||||
vt 0.693838 0.493800
|
||||
vt 0.487234 0.493800
|
||||
vt 0.693838 0.999809
|
||||
vt 0.487234 0.999809
|
||||
vt 0.693838 1.505818
|
||||
vt 0.487234 1.505818
|
||||
vn 0.0000 0.0000 -1.0000
|
||||
vn -0.0000 -0.7071 -0.7071
|
||||
vn -0.0000 -1.0000 -0.0000
|
||||
vn -0.0000 -0.7071 0.7071
|
||||
vn 0.0000 0.0000 1.0000
|
||||
vn 0.0000 0.7071 0.7071
|
||||
vn 0.0000 1.0000 0.0000
|
||||
vn 0.0000 0.7071 -0.7071
|
||||
s off
|
||||
f 149/295/21 150/296/21 152/297/21 151/298/21
|
||||
f 151/298/22 152/297/22 154/299/22 153/300/22
|
||||
f 153/300/23 154/299/23 156/301/23 155/302/23
|
||||
f 155/302/24 156/301/24 158/303/24 157/304/24
|
||||
f 157/305/25 158/306/25 160/307/25 159/308/25
|
||||
f 159/308/26 160/307/26 162/309/26 161/310/26
|
||||
f 161/310/27 162/309/27 164/311/27 163/312/27
|
||||
f 163/312/28 164/311/28 150/296/28 149/295/28
|
||||
f 165/313/21 166/314/21 168/315/21 167/316/21
|
||||
f 167/317/22 168/318/22 170/319/22 169/320/22
|
||||
f 169/320/23 170/319/23 172/321/23 171/322/23
|
||||
f 171/322/24 172/321/24 174/323/24 173/324/24
|
||||
f 173/324/25 174/323/25 176/325/25 175/326/25
|
||||
f 175/326/26 176/325/26 178/327/26 177/328/26
|
||||
f 177/328/27 178/327/27 180/329/27 179/330/27
|
||||
f 179/330/28 180/329/28 166/314/28 165/313/28
|
||||
f 181/331/21 183/332/21 184/333/21 182/334/21
|
||||
f 183/332/22 185/335/22 186/336/22 184/333/22
|
||||
f 185/335/23 187/337/23 188/338/23 186/336/23
|
||||
f 187/337/24 189/339/24 190/340/24 188/338/24
|
||||
f 189/341/25 191/342/25 192/343/25 190/344/25
|
||||
f 191/342/26 193/345/26 194/346/26 192/343/26
|
||||
f 193/345/27 195/347/27 196/348/27 194/346/27
|
||||
f 195/347/28 181/331/28 182/334/28 196/348/28
|
||||
f 197/349/21 199/350/21 200/351/21 198/352/21
|
||||
f 199/353/22 201/354/22 202/355/22 200/356/22
|
||||
f 201/354/23 203/357/23 204/358/23 202/355/23
|
||||
f 203/357/24 205/359/24 206/360/24 204/358/24
|
||||
f 205/359/25 207/361/25 208/362/25 206/360/25
|
||||
f 207/361/26 209/363/26 210/364/26 208/362/26
|
||||
f 209/363/27 211/365/27 212/366/27 210/364/27
|
||||
f 211/365/28 197/349/28 198/352/28 212/366/28
|
||||
g Cube.000_Cube.008
|
||||
v -0.792992 1.200951 -1.268787
|
||||
v -0.792992 1.200951 1.268787
|
||||
v -0.560730 1.649535 -1.268787
|
||||
v -0.560730 1.649535 1.268787
|
||||
v -0.000000 1.835345 1.268787
|
||||
v -0.000000 1.835345 -1.268787
|
||||
v 0.792992 1.200951 -1.268787
|
||||
v 0.792992 1.200951 1.268787
|
||||
v 0.560730 1.649535 -1.268787
|
||||
v 0.560730 1.649535 1.268787
|
||||
vt 3.173871 2.353881
|
||||
vt -2.391578 2.353881
|
||||
vt -2.391578 1.288979
|
||||
vt 3.173871 1.288979
|
||||
vt -2.391578 0.224077
|
||||
vt 3.173871 0.224077
|
||||
vt 3.173871 -1.905729
|
||||
vt 3.173871 -0.840826
|
||||
vt -2.391578 -0.840826
|
||||
vt -2.391578 -1.905728
|
||||
vn -0.8880 0.4598 0.0000
|
||||
vn -0.3146 0.9492 0.0000
|
||||
vn 0.8880 0.4598 0.0000
|
||||
vn 0.3146 0.9492 0.0000
|
||||
s off
|
||||
f 213/367/29 214/368/29 216/369/29 215/370/29
|
||||
f 215/370/30 216/369/30 217/371/30 218/372/30
|
||||
f 219/373/31 221/374/31 222/375/31 220/376/31
|
||||
f 221/374/32 218/372/32 217/371/32 222/375/32
|
||||
g Cube.006_Cube.007
|
||||
v -0.722861 -0.094866 1.158711
|
||||
v -0.722861 1.240430 1.158711
|
||||
v -0.722861 -0.094866 1.059710
|
||||
v -0.722861 1.240430 1.059710
|
||||
v -0.623860 -0.094866 1.158711
|
||||
v -0.623860 1.240430 1.158711
|
||||
v -0.725927 -0.094866 -1.158643
|
||||
v -0.725927 1.240430 -1.158643
|
||||
v -0.725927 -0.094866 -1.059642
|
||||
v -0.725927 1.240430 -1.059642
|
||||
v -0.626926 -0.094866 -1.158643
|
||||
v -0.626926 1.240430 -1.158643
|
||||
v 0.722861 -0.094866 1.158711
|
||||
v 0.722861 1.240430 1.158711
|
||||
v 0.722861 -0.094866 1.059710
|
||||
v 0.722861 1.240430 1.059710
|
||||
v 0.623860 -0.094866 1.158711
|
||||
v 0.623860 1.240430 1.158711
|
||||
v 0.725927 -0.094866 -1.158643
|
||||
v 0.725927 1.240430 -1.158643
|
||||
v 0.725927 -0.094866 -1.059642
|
||||
v 0.725927 1.240430 -1.059642
|
||||
v 0.626926 -0.094866 -1.158643
|
||||
v 0.626926 1.240430 -1.158643
|
||||
vt -0.339657 0.628705
|
||||
vt 1.340090 0.628704
|
||||
vt 1.340090 0.753243
|
||||
vt -0.339657 0.753243
|
||||
vt -0.339658 0.504167
|
||||
vt 1.340090 0.504165
|
||||
vt -0.339657 0.129512
|
||||
vt -0.339657 0.004973
|
||||
vt 1.340090 0.004973
|
||||
vt 1.340090 0.129512
|
||||
vt -0.339657 0.254051
|
||||
vt 1.340090 0.254050
|
||||
vt -0.339658 0.878300
|
||||
vt -0.339658 0.753761
|
||||
vt 1.340090 0.753762
|
||||
vt 1.340090 0.878300
|
||||
vt -0.339657 1.002839
|
||||
vt 1.340090 1.002838
|
||||
vt -0.339657 0.379109
|
||||
vt 1.340090 0.379108
|
||||
vt 1.340090 0.503647
|
||||
vt -0.339657 0.503647
|
||||
vt -0.339658 0.254571
|
||||
vt 1.340090 0.254569
|
||||
vn -1.0000 0.0000 0.0000
|
||||
vn 0.0000 0.0000 1.0000
|
||||
vn 0.0000 0.0000 -1.0000
|
||||
vn 1.0000 0.0000 0.0000
|
||||
s off
|
||||
f 223/377/33 224/378/33 226/379/33 225/380/33
|
||||
f 227/381/34 228/382/34 224/378/34 223/377/34
|
||||
f 229/383/33 231/384/33 232/385/33 230/386/33
|
||||
f 233/387/35 229/383/35 230/386/35 234/388/35
|
||||
f 235/389/36 237/390/36 238/391/36 236/392/36
|
||||
f 239/393/34 235/389/34 236/392/34 240/394/34
|
||||
f 241/395/36 242/396/36 244/397/36 243/398/36
|
||||
f 245/399/35 246/400/35 242/396/35 241/395/35
|
|
@ -1,15 +0,0 @@
|
|||
This mod depends on [commoditymarket](https://github.com/FaceDeer/commoditymarket). It implements a number of fantasy-themed marketplaces where players can post buy and sell offers for various items, allowing for organic market forces to determine the relative values of the resources in a world.
|
||||
|
||||
* King's Market - a basic sort of "commoner's marketplace", only open during the day
|
||||
* Night Market - the shadier side of commerce, only open during the night
|
||||
* Trader's Caravan - a type of market that players can build and place themselves, with a small inventory capacity.
|
||||
* Goblin Exchange - a strange marketplace that uses coal as a currency
|
||||
* Undermarket - where dark powers make their trades, using Mese as a currency
|
||||
|
||||
![](screenshot.jpg)
|
||||
|
||||
All of these except for the Trader's Caravan are intended to be placed in specific locations by server administrators, they don't have crafting recipes.
|
||||
|
||||
An option exists to allow Goblin markets and Undermarkets to be automatically placed inside dungeons by world gen.
|
||||
|
||||
The [settlements](https://github.com/FaceDeer/settlements) mod scatters small settlements around on the surface of the world that can contain King's Markets and Night Markets.
|
Before Width: | Height: | Size: 108 KiB |
|
@ -1,24 +0,0 @@
|
|||
[Enable default markets]
|
||||
commoditymarket_enable_kings_market (Enable King's Market) bool true
|
||||
commoditymarket_enable_night_market (Enable Night Market) bool true
|
||||
commoditymarket_enable_caravan_market (Enable Trader's Caravan) bool true
|
||||
commoditymarket_enable_goblin_market (Enable Goblin Exchange) bool true
|
||||
commoditymarket_enable_under_market (Enable Undermarket) bool true
|
||||
|
||||
[Market node protection]
|
||||
commoditymarket_protect_kings_market (Protect King's Market node) bool true
|
||||
commoditymarket_protect_night_market (Protect Night Market node) bool true
|
||||
commoditymarket_protect_caravan_market (Protect permanent Caravan node) bool true
|
||||
commoditymarket_protect_goblin_market (Protect Goblin Market node) bool true
|
||||
commoditymarket_protect_under_market (Protect Undermarket node) bool true
|
||||
|
||||
[Currency denominations]
|
||||
commoditymarket_coins_per_ingot (Coins per ingot) int 1000
|
||||
|
||||
[Dungeon market generation]
|
||||
commoditymarket_goblin_market_dungeon_prob (Goblin market probability per dungeon mapblock) float 0.25 0 1
|
||||
commoditymarket_goblin_market_dungeon_max (Upper y limit of goblin markets) int 100
|
||||
commoditymarket_goblin_market_dungeon_min (Lower y limit of goblin markets) int -400
|
||||
commoditymarket_under_market_dungeon_prob (Undermarket probability per dungeon mapblock) float 0.1 0 1
|
||||
commoditymarket_under_market_dungeon_max (Upper y limit of undermarkets) int -500
|
||||
commoditymarket_under_market_dungeon_min (Lower y limit of undermarkets) int -31000
|
Before Width: | Height: | Size: 194 B |
Before Width: | Height: | Size: 444 B |
Before Width: | Height: | Size: 721 B |
Before Width: | Height: | Size: 430 B |
Before Width: | Height: | Size: 206 B |
Before Width: | Height: | Size: 436 B |
Before Width: | Height: | Size: 457 B |
Before Width: | Height: | Size: 723 B |
Before Width: | Height: | Size: 312 B |
Before Width: | Height: | Size: 305 B |
Before Width: | Height: | Size: 207 B |
Before Width: | Height: | Size: 1001 B |
Before Width: | Height: | Size: 759 B |
|
@ -1,11 +0,0 @@
|
|||
commoditymarket_gold_coins.png - from https://commons.wikimedia.org/wiki/File:Farm-Fresh_coins.png, by FatCow under the CC-BY 3.0 license
|
||||
commoditymarket_crown.png - from https://commons.wikimedia.org/wiki/File:Farm-Fresh_crown_gold.png by FatCow under the CC-BY 3.0 Unported license
|
||||
commoditymarket_moon.png - from https://commons.wikimedia.org/wiki/File:Luneta08.svg by Arturo D. Castillo —Zoram.hakaan— under the CC-BY 3.0 Unported license
|
||||
commoditymarket_goblin.png - cropped from the "goblins" mod, Copyright 2015 by Francisco "FreeLikeGNU" Athens Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
|
||||
commoditymarket_empty_shelf.png - from the moreblocks mod's "moreblocks_empty_shelf", under the zlib license by Hugo Locurcio and contributors
|
||||
commoditymarket_shingles_wood.png is cottages_homedecor_shingles_wood.png from the cottages mod by VanessaE (CC-by-SA 3.0)
|
||||
|
||||
commoditymarket_trade.png, commoditymarket_trade_flag.png, commoditymarket_under.png, and commoditymarket_under_top were created by FaceDeer and released under the CC0 public domain license
|
||||
|
||||
commoditymarket_sign_post.png and commoditymarket_sign.png are made from elements of default_tree.png and default_aspen_wood.png
|
||||
commoditymarket_door_wood.png is doors_door_wood.png from the doors mod
|
|
@ -19,4 +19,4 @@ For developers, `craftguide` also has a [modding API](https://github.com/minetes
|
|||
Love this mod? Donations are appreciated: https://www.paypal.me/jpg84240
|
||||
|
||||
|
||||
![Preview2](https://i.imgur.com/w7KMS9G.png)
|
||||
![Preview2](https://content.minetest.net/uploads/wAGr5rE3fI.png)
|
||||
|
|
|
@ -7,13 +7,15 @@ local searches = {}
|
|||
local recipes_cache = {}
|
||||
local usages_cache = {}
|
||||
local fuel_cache = {}
|
||||
|
||||
local toolrepair
|
||||
|
||||
local progressive_mode = core.settings:get_bool("craftguide_progressive_mode")
|
||||
local sfinv_only = core.settings:get_bool("craftguide_sfinv_only") and rawget(_G, "sfinv")
|
||||
local progressive_mode = core.settings:get_bool "craftguide_progressive_mode"
|
||||
local sfinv_only = core.settings:get_bool "craftguide_sfinv_only" and rawget(_G, "sfinv")
|
||||
local autocache = core.settings:get_bool "craftguide_autocache"
|
||||
|
||||
local http = core.request_http_api()
|
||||
local storage = core.get_mod_storage()
|
||||
local singleplayer = core.is_singleplayer()
|
||||
|
||||
local reg_items = core.registered_items
|
||||
local reg_tools = core.registered_tools
|
||||
|
@ -41,7 +43,7 @@ local get_player_info = core.get_player_information
|
|||
local on_receive_fields = core.register_on_player_receive_fields
|
||||
|
||||
local ESC = core.formspec_escape
|
||||
local S = core.get_translator("craftguide")
|
||||
local S = core.get_translator "craftguide"
|
||||
|
||||
local ES = function(...)
|
||||
return ESC(S(...))
|
||||
|
@ -56,12 +58,14 @@ local fmt, find, gmatch, match, sub, split, upper, lower =
|
|||
string.sub, string.split, string.upper, string.lower
|
||||
|
||||
local min, max, floor, ceil = math.min, math.max, math.floor, math.ceil
|
||||
local pairs, next, type, tostring, io = pairs, next, type, tostring, io
|
||||
local pairs, next, type, tostring, unpack = pairs, next, type, tostring, unpack
|
||||
local vec_add, vec_mul = vector.add, vector.multiply
|
||||
|
||||
local ROWS = 9
|
||||
local FORMSPEC_MINIMAL_VERSION = 3
|
||||
|
||||
local ROWS = 9
|
||||
local LINES = sfinv_only and 5 or 9
|
||||
local IPP = ROWS * LINES
|
||||
local IPP = ROWS * LINES
|
||||
local WH_LIMIT = 8
|
||||
|
||||
local XOFFSET = sfinv_only and 3.83 or 11.2
|
||||
|
@ -79,6 +83,7 @@ local PNG = {
|
|||
fire_anim = "craftguide_fire_anim.png",
|
||||
book = "craftguide_book.png",
|
||||
sign = "craftguide_sign.png",
|
||||
nothing = "craftguide_no.png",
|
||||
selected = "craftguide_selected.png",
|
||||
furnace_anim = "craftguide_furnace_anim.png",
|
||||
|
||||
|
@ -96,7 +101,7 @@ local FMT = {
|
|||
tooltip = "tooltip[%f,%f;%f,%f;%s]",
|
||||
item_image = "item_image[%f,%f;%f,%f;%s]",
|
||||
image_button = "image_button[%f,%f;%f,%f;%s;%s;%s]",
|
||||
animated_image = "animated_image[%f,%f;%f,%f;%s:%u,%u]",
|
||||
animated_image = "animated_image[%f,%f;%f,%f;;%s;%u;%u]",
|
||||
item_image_button = "item_image_button[%f,%f;%f,%f;%s;%s;%s]",
|
||||
arrow = "image_button[%f,%f;0.8,0.8;%s;%s;;;false;%s]",
|
||||
}
|
||||
|
@ -106,6 +111,20 @@ local function get_fs_version(name)
|
|||
return info and info.formspec_version or 1
|
||||
end
|
||||
|
||||
local function outdated(name)
|
||||
local fs = fmt([[
|
||||
size[6.6,1.3]
|
||||
image[0,0;1,1;%s]
|
||||
label[1,0;%s]
|
||||
button_exit[2.8,0.8;1,1;;OK]
|
||||
]],
|
||||
PNG.book,
|
||||
"Your Minetest client is outdated.\n" ..
|
||||
"Get the latest version on minetest.net to use the Crafting Guide.")
|
||||
|
||||
return show_formspec(name, "craftguide", fs)
|
||||
end
|
||||
|
||||
local function mul_elem(elem, n)
|
||||
local fstr, elems = "", {}
|
||||
|
||||
|
@ -120,6 +139,8 @@ end
|
|||
craftguide.group_stereotypes = {
|
||||
dye = "dye:white",
|
||||
wool = "wool:white",
|
||||
wood = "default:wood",
|
||||
tree = "default:tree",
|
||||
coal = "default:coal_lump",
|
||||
vessel = "vessels:glass_bottle",
|
||||
flower = "flowers:dandelion_yellow",
|
||||
|
@ -127,12 +148,48 @@ craftguide.group_stereotypes = {
|
|||
mesecon_conductor_craftable = "mesecons:wire_00000000_off",
|
||||
}
|
||||
|
||||
local group_names = {
|
||||
coal = S"Any coal",
|
||||
wool = S"Any wool",
|
||||
wood = S"Any wood planks",
|
||||
sand = S"Any sand",
|
||||
stick = S"Any stick",
|
||||
stone = S"Any kind of stone block",
|
||||
tree = S"Any tree",
|
||||
vessel = S"Any vessel",
|
||||
|
||||
["color_red,flower"] = S"Any red flower",
|
||||
["color_blue,flower"] = S"Any blue flower",
|
||||
["color_black,flower"] = S"Any black flower",
|
||||
["color_white,flower"] = S"Any white flower",
|
||||
["color_green,flower"] = S"Any green flower",
|
||||
["color_orange,flower"] = S"Any orange flower",
|
||||
["color_yellow,flower"] = S"Any yellow flower",
|
||||
["color_violet,flower"] = S"Any violet flower",
|
||||
|
||||
["color_red,dye"] = S"Any red dye",
|
||||
["color_blue,dye"] = S"Any blue dye",
|
||||
["color_grey,dye"] = S"Any grey dye",
|
||||
["color_pink,dye"] = S"Any pink dye",
|
||||
["color_cyan,dye"] = S"Any cyan dye",
|
||||
["color_black,dye"] = S"Any black dye",
|
||||
["color_white,dye"] = S"Any white dye",
|
||||
["color_brown,dye"] = S"Any brown dye",
|
||||
["color_green,dye"] = S"Any green dye",
|
||||
["color_orange,dye"] = S"Any orange dye",
|
||||
["color_yellow,dye"] = S"Any yellow dye",
|
||||
["color_violet,dye"] = S"Any violet dye",
|
||||
["color_magenta,dye"] = S"Any magenta dye",
|
||||
["color_dark_grey,dye"] = S"Any dark grey dye",
|
||||
["color_dark_green,dye"] = S"Any dark green dye",
|
||||
}
|
||||
|
||||
local function err(str)
|
||||
return log("error", str)
|
||||
end
|
||||
|
||||
local function msg(name, str)
|
||||
return chat_send(name, fmt("[craftguide] %s", clr("#f00", str)))
|
||||
return chat_send(name, fmt("[craftguide] %s", str))
|
||||
end
|
||||
|
||||
local function is_str(x)
|
||||
|
@ -194,6 +251,7 @@ local function table_eq(T1, T2)
|
|||
|
||||
local function recurse(t1, t2)
|
||||
if type(t1) ~= type(t2) then return end
|
||||
|
||||
if not is_table(t1) then
|
||||
return t1 == t2
|
||||
end
|
||||
|
@ -274,7 +332,7 @@ local craft_types = {}
|
|||
|
||||
function craftguide.register_craft_type(name, def)
|
||||
if not true_str(name) then
|
||||
return err"craftguide.register_craft_type(): name missing"
|
||||
return err "craftguide.register_craft_type(): name missing"
|
||||
end
|
||||
|
||||
if not is_str(def.description) then
|
||||
|
@ -293,8 +351,9 @@ function craftguide.register_craft(def)
|
|||
|
||||
if true_str(def.url) then
|
||||
if not http then
|
||||
return err"No HTTP support for this mod. " ..
|
||||
"Add it to the `secure.http_mods` or `secure.trusted_mods` setting."
|
||||
return err(fmt([[craftguide.register_craft(): Unable to reach %s.
|
||||
No HTTP support for this mod: add it to the `secure.http_mods` or
|
||||
`secure.trusted_mods` setting.]], def.url))
|
||||
end
|
||||
|
||||
http.fetch({url = def.url}, function(result)
|
||||
|
@ -310,7 +369,7 @@ function craftguide.register_craft(def)
|
|||
end
|
||||
|
||||
if not is_table(def) or not next(def) then
|
||||
return err"craftguide.register_craft(): craft definition missing"
|
||||
return err "craftguide.register_craft(): craft definition missing"
|
||||
end
|
||||
|
||||
if #def > 1 then
|
||||
|
@ -326,7 +385,7 @@ function craftguide.register_craft(def)
|
|||
end
|
||||
|
||||
if not true_str(def.output) then
|
||||
return err"craftguide.register_craft(): output missing"
|
||||
return err "craftguide.register_craft(): output missing"
|
||||
end
|
||||
|
||||
if not is_table(def.items) then
|
||||
|
@ -396,9 +455,9 @@ local recipe_filters = {}
|
|||
|
||||
function craftguide.add_recipe_filter(name, f)
|
||||
if not true_str(name) then
|
||||
return err"craftguide.add_recipe_filter(): name missing"
|
||||
return err "craftguide.add_recipe_filter(): name missing"
|
||||
elseif not is_func(f) then
|
||||
return err"craftguide.add_recipe_filter(): function missing"
|
||||
return err "craftguide.add_recipe_filter(): function missing"
|
||||
end
|
||||
|
||||
recipe_filters[name] = f
|
||||
|
@ -406,9 +465,9 @@ end
|
|||
|
||||
function craftguide.set_recipe_filter(name, f)
|
||||
if not is_str(name) then
|
||||
return err"craftguide.set_recipe_filter(): name missing"
|
||||
return err "craftguide.set_recipe_filter(): name missing"
|
||||
elseif not is_func(f) then
|
||||
return err"craftguide.set_recipe_filter(): function missing"
|
||||
return err "craftguide.set_recipe_filter(): function missing"
|
||||
end
|
||||
|
||||
recipe_filters = {[name] = f}
|
||||
|
@ -434,9 +493,9 @@ local search_filters = {}
|
|||
|
||||
function craftguide.add_search_filter(name, f)
|
||||
if not true_str(name) then
|
||||
return err"craftguide.add_search_filter(): name missing"
|
||||
return err "craftguide.add_search_filter(): name missing"
|
||||
elseif not is_func(f) then
|
||||
return err"craftguide.add_search_filter(): function missing"
|
||||
return err "craftguide.add_search_filter(): function missing"
|
||||
end
|
||||
|
||||
search_filters[name] = f
|
||||
|
@ -465,6 +524,7 @@ end
|
|||
|
||||
local function item_in_recipe(item, recipe)
|
||||
local clean_item = reg_aliases[item] or item
|
||||
|
||||
for _, recipe_item in pairs(recipe.items) do
|
||||
local clean_recipe_item = reg_aliases[recipe_item] or recipe_item
|
||||
if clean_recipe_item == clean_item then
|
||||
|
@ -602,7 +662,7 @@ local function get_recipes(item, data, player)
|
|||
local no_usages = not usages or #usages == 0
|
||||
|
||||
return not no_recipes and recipes or nil,
|
||||
not no_usages and usages or nil
|
||||
not no_usages and usages or nil
|
||||
end
|
||||
|
||||
local function groups_to_items(groups, get_all)
|
||||
|
@ -651,6 +711,10 @@ local function is_fav(data)
|
|||
return fav, i
|
||||
end
|
||||
|
||||
local function check_newline(def)
|
||||
return def and def.description and find(def.description, "\n")
|
||||
end
|
||||
|
||||
local function get_desc(name)
|
||||
if sub(name, 1, 1) == "_" then
|
||||
name = sub(name, 2)
|
||||
|
@ -667,15 +731,20 @@ local function get_tooltip(name, info)
|
|||
local tooltip
|
||||
|
||||
if info.groups then
|
||||
local groupstr, c = {}, 0
|
||||
sort(info.groups)
|
||||
tooltip = group_names[concat(info.groups, ",")]
|
||||
|
||||
for i = 1, #info.groups do
|
||||
c = c + 1
|
||||
groupstr[c] = clr("#ff0", info.groups[i])
|
||||
if not tooltip then
|
||||
local groupstr, c = {}, 0
|
||||
|
||||
for i = 1, #info.groups do
|
||||
c = c + 1
|
||||
groupstr[c] = clr("#ff0", info.groups[i])
|
||||
end
|
||||
|
||||
groupstr = concat(groupstr, ", ")
|
||||
tooltip = S("Any item belonging to the group(s): @1", groupstr)
|
||||
end
|
||||
|
||||
groupstr = concat(groupstr, ", ")
|
||||
tooltip = S("Any item belonging to the group(s): @1", groupstr)
|
||||
else
|
||||
tooltip = get_desc(name)
|
||||
end
|
||||
|
@ -728,60 +797,50 @@ local function get_output_fs(data, fs, L)
|
|||
end
|
||||
|
||||
local pos_x = L.rightest + L.btn_size + 0.1
|
||||
local pos_y = YOFFSET + (sfinv_only and 0.25 or -0.45)
|
||||
local pos_y = YOFFSET + (sfinv_only and 0.25 or -0.45) + L.spacing
|
||||
|
||||
if data.fs_version >= 3 and sub(icon, 1, 18) == "craftguide_furnace" then
|
||||
if sub(icon, 1, 18) == "craftguide_furnace" then
|
||||
fs[#fs + 1] = fmt(FMT.animated_image,
|
||||
pos_x, pos_y + L.spacing, 0.5, 0.5, PNG.furnace_anim, 8, 180)
|
||||
pos_x, pos_y, 0.5, 0.5, PNG.furnace_anim, 8, 180)
|
||||
else
|
||||
fs[#fs + 1] = fmt(FMT.image, pos_x, pos_y + L.spacing, 0.5, 0.5, icon)
|
||||
fs[#fs + 1] = fmt(FMT.image, pos_x, pos_y, 0.5, 0.5, icon)
|
||||
end
|
||||
|
||||
local tooltip = custom_recipe and custom_recipe.description or
|
||||
L.shapeless and S"Shapeless" or S"Cooking"
|
||||
|
||||
fs[#fs + 1] = fmt(FMT.tooltip, pos_x, pos_y + L.spacing, 0.5, 0.5, ESC(tooltip))
|
||||
fs[#fs + 1] = fmt(FMT.tooltip, pos_x, pos_y, 0.5, 0.5, ESC(tooltip))
|
||||
end
|
||||
|
||||
local arrow_X = L.rightest + (L._btn_size or 1.1)
|
||||
local output_X = arrow_X + 0.9
|
||||
local Y = YOFFSET + (sfinv_only and 0.7 or 0) + L.spacing
|
||||
|
||||
fs[#fs + 1] = fmt(FMT.image,
|
||||
arrow_X, YOFFSET + (sfinv_only and 0.9 or 0.2) + L.spacing,
|
||||
0.9, 0.7, PNG.arrow)
|
||||
fs[#fs + 1] = fmt(FMT.image, arrow_X, Y + 0.2, 0.9, 0.7, PNG.arrow)
|
||||
|
||||
if L.recipe.type == "fuel" then
|
||||
if data.fs_version >= 3 then
|
||||
fs[#fs + 1] = fmt(FMT.animated_image,
|
||||
output_X, YOFFSET + (sfinv_only and 0.7 or 0) + L.spacing,
|
||||
1.1, 1.1, PNG.fire_anim, 8, 180)
|
||||
else
|
||||
fs[#fs + 1] = fmt(FMT.image,
|
||||
output_X, YOFFSET + (sfinv_only and 0.7 or 0) + L.spacing,
|
||||
1.1, 1.1, PNG.fire)
|
||||
end
|
||||
fs[#fs + 1] = fmt(FMT.animated_image, output_X, Y, 1.1, 1.1, PNG.fire_anim, 8, 180)
|
||||
else
|
||||
local item = L.recipe.output
|
||||
item = clean_name(item)
|
||||
local name = match(item, "%S*")
|
||||
|
||||
if data.fs_version >= 3 then
|
||||
fs[#fs + 1] = fmt(FMT.image,
|
||||
output_X, YOFFSET + (sfinv_only and 0.7 or 0) + L.spacing,
|
||||
1.1, 1.1, PNG.selected)
|
||||
end
|
||||
fs[#fs + 1] = fmt(FMT.image, output_X, Y, 1.1, 1.1, PNG.selected)
|
||||
|
||||
local _name = sfinv_only and name or fmt("_%s", name)
|
||||
|
||||
fs[#fs + 1] = fmt("item_image_button[%f,%f;%f,%f;%s;%s;%s]",
|
||||
output_X, YOFFSET + (sfinv_only and 0.7 or 0) + L.spacing,
|
||||
1.1, 1.1, item, _name, "")
|
||||
output_X, Y, 1.1, 1.1, item, _name, "")
|
||||
|
||||
|
||||
local def = reg_items[name]
|
||||
|
||||
local infos = {
|
||||
unknown = not reg_items[name] or nil,
|
||||
unknown = not def or nil,
|
||||
burntime = fuel_cache[name],
|
||||
repair = repairable(name),
|
||||
rarity = L.rarity,
|
||||
newline = check_newline(def),
|
||||
}
|
||||
|
||||
if next(infos) then
|
||||
|
@ -793,15 +852,9 @@ local function get_output_fs(data, fs, L)
|
|||
output_X + 1, YOFFSET + (sfinv_only and 0.7 or 0.1) + L.spacing,
|
||||
0.6, 0.4, PNG.arrow)
|
||||
|
||||
if data.fs_version >= 3 then
|
||||
fs[#fs + 1] = fmt(FMT.animated_image,
|
||||
output_X + 1.6, YOFFSET + (sfinv_only and 0.55 or 0) + L.spacing,
|
||||
0.6, 0.6, PNG.fire_anim, 8, 180)
|
||||
else
|
||||
fs[#fs + 1] = fmt(FMT.image,
|
||||
output_X + 1.6, YOFFSET + (sfinv_only and 0.55 or 0) + L.spacing,
|
||||
0.6, 0.6, PNG.fire)
|
||||
end
|
||||
fs[#fs + 1] = fmt(FMT.animated_image,
|
||||
output_X + 1.6, YOFFSET + (sfinv_only and 0.55 or 0) + L.spacing,
|
||||
0.6, 0.6, PNG.fire_anim, 8, 180)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -856,9 +909,9 @@ local function get_grid_fs(data, fs, rcp, spacing)
|
|||
_btn_size = btn_size
|
||||
|
||||
X = (btn_size * ((i - 1) % width) + XOFFSET -
|
||||
(sfinv_only and 2.83 or 0.5)) * (0.83 - (x_y / 5))
|
||||
(sfinv_only and 2.83 or 0)) * (0.83 - (x_y / 5))
|
||||
Y = (btn_size * floor((i - 1) / width) +
|
||||
(sfinv_only and 5.81 or 5.5) + x_y) * (0.86 - (x_y / 5))
|
||||
(sfinv_only and 5.81 or 3.92) + x_y) * (0.86 - (x_y / 5))
|
||||
end
|
||||
|
||||
if X > rightest then
|
||||
|
@ -885,21 +938,24 @@ local function get_grid_fs(data, fs, rcp, spacing)
|
|||
end
|
||||
end
|
||||
|
||||
if data.fs_version >= 3 and not large_recipe then
|
||||
fs[#fs + 1] = fmt(FMT.image,
|
||||
X, Y + (sfinv_only and 0.7 or 0), btn_size, btn_size, PNG.selected)
|
||||
Y = Y + (sfinv_only and 0.7 or 0)
|
||||
|
||||
if not large_recipe then
|
||||
fs[#fs + 1] = fmt(FMT.image, X, Y, btn_size, btn_size, PNG.selected)
|
||||
end
|
||||
|
||||
fs[#fs + 1] = fmt(FMT.item_image_button,
|
||||
X, Y + (sfinv_only and 0.7 or 0),
|
||||
btn_size, btn_size, item, item, ESC(label))
|
||||
X, Y, btn_size, btn_size, item, item, label)
|
||||
|
||||
local def = reg_items[name]
|
||||
|
||||
local infos = {
|
||||
unknown = not reg_items[name] or nil,
|
||||
unknown = not def or nil,
|
||||
groups = groups,
|
||||
burntime = fuel_cache[name],
|
||||
cooktime = cooktime,
|
||||
replace = replace,
|
||||
newline = check_newline(def),
|
||||
}
|
||||
|
||||
if next(infos) then
|
||||
|
@ -923,19 +979,17 @@ local function get_grid_fs(data, fs, rcp, spacing)
|
|||
end
|
||||
|
||||
local function get_panels(data, fs)
|
||||
local start_y = data.fs_version >= 3 and (sfinv_only and 0.33 or 0) or 0.33
|
||||
local start_y = sfinv_only and 0.33 or 0
|
||||
|
||||
local panels = {
|
||||
{dat = data.usages or {}, height = 3.5},
|
||||
{dat = data.recipes or {}, height = 3.5},
|
||||
}
|
||||
|
||||
if data.fs_version >= 3 and not sfinv_only then
|
||||
panels.favs = {dat = {}, height = 2.19}
|
||||
|
||||
elseif sfinv_only then
|
||||
panels = data.show_usages and
|
||||
{{dat = data.usages}} or {{dat = data.recipes}}
|
||||
if not sfinv_only then
|
||||
panels.favs = {height = 2.19}
|
||||
else
|
||||
panels = data.show_usages and {{dat = data.usages}} or {{dat = data.recipes}}
|
||||
end
|
||||
|
||||
for k, v in pairs(panels) do
|
||||
|
@ -946,7 +1000,7 @@ local function get_panels(data, fs)
|
|||
fs[#fs + 1] = fmt("background9[8.1,%f;6.6,%f;%s;false;%d]",
|
||||
-0.2 + spacing, v.height, PNG.bg_full, 10)
|
||||
|
||||
if data.fs_version >= 3 and k == 2 then
|
||||
if k == 2 then
|
||||
local fav = is_fav(data)
|
||||
local nfavs = #data.favs
|
||||
|
||||
|
@ -966,7 +1020,7 @@ local function get_panels(data, fs)
|
|||
end
|
||||
end
|
||||
|
||||
local rn = #v.dat
|
||||
local rn = v.dat and #v.dat or -1
|
||||
local _rn = tostring(rn)
|
||||
local xu = tostring(data.unum) .. _rn
|
||||
local xr = tostring(data.rnum) .. _rn
|
||||
|
@ -974,10 +1028,16 @@ local function get_panels(data, fs)
|
|||
xr = max(-0.3, -((#xr - 3) * 0.05))
|
||||
|
||||
local is_recipe = sfinv_only and not data.show_usages or k == 2
|
||||
local lbl
|
||||
local lbl = ""
|
||||
|
||||
if not sfinv_only and rn == 0 then
|
||||
lbl = clr("#f00", is_recipe and ES"No recipes" or ES"No usages")
|
||||
local X = XOFFSET - 0.7
|
||||
local Y = YOFFSET - 0.4 + spacing
|
||||
|
||||
fs[#fs + 1] = fmt(FMT.image, X, Y, 2, 2, PNG.nothing)
|
||||
|
||||
fs[#fs + 1] = fmt(FMT.tooltip,
|
||||
X, Y, 2, 2, is_recipe and ES"No recipes" or ES"No usages")
|
||||
|
||||
elseif (not sfinv_only and is_recipe) or
|
||||
(sfinv_only and not data.show_usages) then
|
||||
|
@ -1003,39 +1063,31 @@ local function get_panels(data, fs)
|
|||
local x_arrow = XOFFSET + (sfinv_only and 1.7 or 1)
|
||||
local y_arrow = YOFFSET + (sfinv_only and 3.3 or 1.4 + spacing)
|
||||
|
||||
if data.fs_version >= 3 then
|
||||
fs[#fs + 1] = fmt(mul_elem(FMT.arrow, 2),
|
||||
x_arrow + (is_recipe and xr or xu), y_arrow,
|
||||
PNG.prev, prev_name, "",
|
||||
x_arrow + 1.8, y_arrow, PNG.next, next_name, "")
|
||||
else
|
||||
fs[#fs + 1] = fmt(mul_elem(FMT.arrow, 2),
|
||||
x_arrow + (is_recipe and xr or xu),
|
||||
y_arrow, PNG.prev, prev_name, PNG.prev_hover,
|
||||
x_arrow + 1.8, y_arrow, PNG.next, next_name, PNG.next_hover)
|
||||
end
|
||||
fs[#fs + 1] = fmt(mul_elem(FMT.arrow, 2),
|
||||
x_arrow + (is_recipe and xr or xu), y_arrow,
|
||||
PNG.prev, prev_name, "",
|
||||
x_arrow + 1.8, y_arrow, PNG.next, next_name, "")
|
||||
end
|
||||
|
||||
local rcp = is_recipe and v.dat[data.rnum] or v.dat[data.unum]
|
||||
local rcp = v.dat and (is_recipe and v.dat[data.rnum] or v.dat[data.unum])
|
||||
if rcp then
|
||||
get_grid_fs(data, fs, rcp, spacing)
|
||||
end
|
||||
|
||||
if k == "favs" and data.fs_version >= 3 and not sfinv_only then
|
||||
if k == "favs" and not sfinv_only then
|
||||
fs[#fs + 1] = fmt(FMT.label, 8.3, spacing - 0.1, ES"Bookmarks")
|
||||
|
||||
for i = 1, #data.favs do
|
||||
local item = data.favs[i]
|
||||
local X = 7.85 + (i - 0.5)
|
||||
local Y = spacing + 0.45
|
||||
|
||||
if data.query_item == item then
|
||||
fs[#fs + 1] = fmt(FMT.image,
|
||||
7.85 + (i - 0.5), spacing + 0.45,
|
||||
1.1, 1.1, PNG.selected)
|
||||
fs[#fs + 1] = fmt(FMT.image, X, Y, 1.1, 1.1, PNG.selected)
|
||||
end
|
||||
|
||||
fs[#fs + 1] = fmt(FMT.item_image_button,
|
||||
7.85 + (i - 0.5), spacing + 0.45,
|
||||
1.1, 1.1, item, item, "")
|
||||
X, Y, 1.1, 1.1, item, item, "")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1049,72 +1101,57 @@ local function make_fs(data)
|
|||
no_prepend[]
|
||||
bgcolor[#0000]
|
||||
]],
|
||||
ROWS + (data.query_item and 6.7 or 0) - 1.2, LINES - 0.3)
|
||||
9 + (data.query_item and 6.7 or 0) - 1.2, LINES - 0.3)
|
||||
|
||||
if not sfinv_only then
|
||||
fs[#fs + 1] = fmt("background9[-0.15,-0.2;%f,%f;%s;false;%d]",
|
||||
ROWS - 0.9, LINES + 0.4, PNG.bg_full, 10)
|
||||
9 - 0.9, LINES + 0.4, PNG.bg_full, 10)
|
||||
end
|
||||
|
||||
fs[#fs + 1] = fmt([[
|
||||
style[filter;border=false]
|
||||
field[0.4,0.2;2.5,1;filter;;%s]
|
||||
field_close_on_enter[filter;false]
|
||||
box[0,0;2.4,0.6;#ffffff25]
|
||||
box[0,0;2.4,0.6;#bababa25]
|
||||
]],
|
||||
ESC(data.filter))
|
||||
|
||||
if data.fs_version >= 3 then
|
||||
fs[#fs + 1] = fmt([[
|
||||
style_type[image_button;border=false]
|
||||
style_type[item_image_button;border=false;bgimg_hovered=%s;bgimg_pressed=%s]
|
||||
style[search;fgimg=%s;fgimg_hovered=%s]
|
||||
style[clear;fgimg=%s;fgimg_hovered=%s]
|
||||
style[prev_page;fgimg=%s;fgimg_hovered=%s;fgimg_pressed=%s]
|
||||
style[next_page;fgimg=%s;fgimg_hovered=%s;fgimg_pressed=%s]
|
||||
]],
|
||||
PNG.selected, PNG.selected,
|
||||
PNG.search, PNG.search_hover,
|
||||
PNG.clear, PNG.clear_hover,
|
||||
PNG.prev, PNG.prev_hover, PNG.prev_hover,
|
||||
PNG.next, PNG.next_hover, PNG.next_hover)
|
||||
fs[#fs + 1] = fmt([[
|
||||
style_type[image_button;border=false]
|
||||
style_type[item_image_button;border=false;bgimg_hovered=%s;bgimg_pressed=%s]
|
||||
style[search;fgimg=%s;fgimg_hovered=%s]
|
||||
style[clear;fgimg=%s;fgimg_hovered=%s]
|
||||
style[prev_page;fgimg=%s;fgimg_hovered=%s;fgimg_pressed=%s]
|
||||
style[next_page;fgimg=%s;fgimg_hovered=%s;fgimg_pressed=%s]
|
||||
]],
|
||||
PNG.selected, PNG.selected,
|
||||
PNG.search, PNG.search_hover,
|
||||
PNG.clear, PNG.clear_hover,
|
||||
PNG.prev, PNG.prev_hover, PNG.prev_hover,
|
||||
PNG.next, PNG.next_hover, PNG.next_hover)
|
||||
|
||||
fs[#fs + 1] = fmt(mul_elem(FMT.image_button, 4),
|
||||
sfinv_only and 2.6 or 2.54, -0.06, 0.85, 0.85, "", "search", "",
|
||||
sfinv_only and 3.3 or 3.25, -0.06, 0.85, 0.85, "", "clear", "",
|
||||
sfinv_only and 5.45 or (ROWS * 6.83) / 11, -0.06, 0.85, 0.85, "", "prev_page", "",
|
||||
sfinv_only and 7.2 or (ROWS * 8.75) / 11, -0.06, 0.85, 0.85, "", "next_page", "")
|
||||
else
|
||||
fs[#fs + 1] = fmt([[
|
||||
image_button[%f,-0.06;0.85,0.85;%s;search;;;false;%s]
|
||||
image_button[%f,-0.06;0.85,0.85;%s;clear;;;false;%s]
|
||||
]],
|
||||
sfinv_only and 2.6 or 2.54, PNG.search, PNG.search_hover,
|
||||
sfinv_only and 3.3 or 3.25, PNG.clear, PNG.clear_hover)
|
||||
|
||||
fs[#fs + 1] = fmt(mul_elem(FMT.arrow, 2),
|
||||
sfinv_only and 5.45 or (ROWS * 6.83) / 11, -0.05,
|
||||
PNG.prev, "prev_page", PNG.prev_hover,
|
||||
sfinv_only and 7.2 or (ROWS * 8.75) / 11, -0.05,
|
||||
PNG.next, "next_page", PNG.next_hover)
|
||||
end
|
||||
fs[#fs + 1] = fmt(mul_elem(FMT.image_button, 4),
|
||||
sfinv_only and 2.6 or 2.54, -0.06, 0.85, 0.85, "", "search", "",
|
||||
sfinv_only and 3.3 or 3.25, -0.06, 0.85, 0.85, "", "clear", "",
|
||||
sfinv_only and 5.45 or (9 * 6.83) / 11, -0.06, 0.85, 0.85, "", "prev_page", "",
|
||||
sfinv_only and 7.2 or (9 * 8.75) / 11, -0.06, 0.85, 0.85, "", "next_page", "")
|
||||
|
||||
data.pagemax = max(1, ceil(#data.items / IPP))
|
||||
|
||||
fs[#fs + 1] = fmt("label[%f,%f;%s / %u]",
|
||||
sfinv_only and 6.35 or (ROWS * 7.85) / 11,
|
||||
sfinv_only and 6.35 or (9 * 7.85) / 11,
|
||||
0.06, clr("#ff0", data.pagenum), data.pagemax)
|
||||
|
||||
if #data.items == 0 then
|
||||
local no_item = S"No item to show"
|
||||
local pos = ROWS / 3
|
||||
local no_item = ES"No item to show"
|
||||
local pos = 3
|
||||
|
||||
if next(recipe_filters) and #init_items > 0 and data.filter == "" then
|
||||
no_item = S"Collect items to reveal more recipes"
|
||||
no_item = ES"Collect items to reveal more recipes"
|
||||
pos = pos - 1
|
||||
end
|
||||
|
||||
fs[#fs + 1] = fmt(FMT.label, pos, 2, ESC(no_item))
|
||||
fs[#fs + 1] = fmt(FMT.label, pos, 2, no_item)
|
||||
end
|
||||
|
||||
local first_item = (data.pagenum - 1) * IPP
|
||||
|
@ -1125,18 +1162,15 @@ local function make_fs(data)
|
|||
|
||||
local X = i % ROWS
|
||||
local Y = (i % IPP - X) / ROWS + 1
|
||||
X = X - (X * (sfinv_only and 0.12 or 0.14)) - 0.05
|
||||
Y = Y - (Y * 0.1) - 0.1
|
||||
|
||||
if data.fs_version >= 3 and data.query_item == item then
|
||||
fs[#fs + 1] = fmt(FMT.image,
|
||||
X - (X * (sfinv_only and 0.12 or 0.14)) - 0.05,
|
||||
Y - (Y * 0.1) - 0.1,
|
||||
1, 1, PNG.selected)
|
||||
if data.query_item == item then
|
||||
fs[#fs + 1] = fmt(FMT.image, X, Y, 1, 1, PNG.selected)
|
||||
end
|
||||
|
||||
fs[#fs + 1] = fmt("item_image_button[%f,%f;%f,%f;%s;%s_inv;]",
|
||||
X - (X * (sfinv_only and 0.12 or 0.14)) - 0.05,
|
||||
Y - (Y * 0.1) - 0.1,
|
||||
1, 1, item, item)
|
||||
X, Y, 1, 1, item, item)
|
||||
end
|
||||
|
||||
if (data.recipes and #data.recipes > 0) or (data.usages and #data.usages > 0) then
|
||||
|
@ -1157,12 +1191,12 @@ end
|
|||
|
||||
craftguide.register_craft_type("digging", {
|
||||
description = ES"Digging",
|
||||
icon = "default_tool_steelpick.png",
|
||||
icon = "craftguide_steelpick.png",
|
||||
})
|
||||
|
||||
craftguide.register_craft_type("digging_chance", {
|
||||
description = ES"Digging Chance",
|
||||
icon = "default_tool_mesepick.png",
|
||||
icon = "craftguide_mesepick.png",
|
||||
})
|
||||
|
||||
local function search(data)
|
||||
|
@ -1399,8 +1433,7 @@ local function handle_drops_table(name, drop)
|
|||
end
|
||||
end
|
||||
|
||||
local function register_drops(name, def)
|
||||
local drop = def.drop
|
||||
local function register_drops(name, drop)
|
||||
local dstack = ItemStack(drop)
|
||||
|
||||
if not dstack:is_empty() and dstack:get_name() ~= name then
|
||||
|
@ -1458,59 +1491,46 @@ local function show_item(def)
|
|||
def.description and def.description ~= ""
|
||||
end
|
||||
|
||||
local function tablelen(t)
|
||||
local c = 0
|
||||
for _ in pairs(t) do
|
||||
c = c + 1
|
||||
end
|
||||
|
||||
return c
|
||||
end
|
||||
|
||||
local function get_init_items()
|
||||
local ic, it, last_str = 0, tablelen(reg_items), ""
|
||||
local hash = {}
|
||||
local init_items_bak = storage:get "init_items"
|
||||
|
||||
local function iop(str)
|
||||
io.write(("\b \b"):rep(#last_str))
|
||||
io.write(str)
|
||||
io.flush()
|
||||
last_str = str
|
||||
end
|
||||
if autocache == false and init_items_bak then
|
||||
init_items = dslz(init_items_bak)
|
||||
fuel_cache = dslz(storage:get "fuel_cache")
|
||||
usages_cache = dslz(storage:get "usages_cache")
|
||||
recipes_cache = dslz(storage:get "recipes_cache")
|
||||
else
|
||||
print "[craftguide] Caching data (this may take a while)"
|
||||
local hash = {}
|
||||
|
||||
local full_char, empty_char = "#", " "
|
||||
for name, def in pairs(reg_items) do
|
||||
if show_item(def) then
|
||||
if not fuel_cache[name] then
|
||||
cache_fuel(name)
|
||||
end
|
||||
|
||||
for name, def in pairs(reg_items) do
|
||||
ic = ic + 1
|
||||
local percent, bar, len = (ic * 100) / it, "[", 20
|
||||
if not recipes_cache[name] then
|
||||
cache_recipes(name)
|
||||
end
|
||||
|
||||
for i = 1, len do
|
||||
bar = bar .. (i <= percent / (100 / len) and full_char or empty_char)
|
||||
end
|
||||
cache_usages(name)
|
||||
register_drops(name, def.drop)
|
||||
|
||||
iop(fmt("[craftguide] Caching data %s %u%%\r", bar .. "]", percent))
|
||||
|
||||
if show_item(def) then
|
||||
if not fuel_cache[name] then
|
||||
cache_fuel(name)
|
||||
end
|
||||
|
||||
if not recipes_cache[name] then
|
||||
cache_recipes(name)
|
||||
end
|
||||
|
||||
cache_usages(name)
|
||||
register_drops(name, def)
|
||||
|
||||
if name ~= "" and recipes_cache[name] or usages_cache[name] then
|
||||
init_items[#init_items + 1] = name
|
||||
hash[name] = true
|
||||
if name ~= "" and recipes_cache[name] or usages_cache[name] then
|
||||
init_items[#init_items + 1] = name
|
||||
hash[name] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
handle_aliases(hash)
|
||||
sort(init_items)
|
||||
handle_aliases(hash)
|
||||
sort(init_items)
|
||||
|
||||
storage:set_string("init_items", slz(init_items))
|
||||
storage:set_string("fuel_cache", slz(fuel_cache))
|
||||
storage:set_string("usages_cache", slz(usages_cache))
|
||||
storage:set_string("recipes_cache", slz(recipes_cache))
|
||||
end
|
||||
|
||||
if http and true_str(craftguide.export_url) then
|
||||
local post_data = {
|
||||
|
@ -1524,8 +1544,6 @@ local function get_init_items()
|
|||
post_data = write_json(post_data),
|
||||
}
|
||||
end
|
||||
|
||||
print()
|
||||
end
|
||||
|
||||
local function init_data(name)
|
||||
|
@ -1556,12 +1574,15 @@ on_mods_loaded(get_init_items)
|
|||
on_joinplayer(function(player)
|
||||
local name = player:get_player_name()
|
||||
init_data(name)
|
||||
|
||||
if pdata[name].fs_version < FORMSPEC_MINIMAL_VERSION then
|
||||
outdated(name)
|
||||
end
|
||||
end)
|
||||
|
||||
local function fields(player, _f)
|
||||
local name = player:get_player_name()
|
||||
local data = pdata[name]
|
||||
--print(dump(_f))
|
||||
|
||||
if _f.clear then
|
||||
reset_data(data)
|
||||
|
@ -1651,19 +1672,18 @@ end
|
|||
|
||||
if sfinv_only then
|
||||
sfinv.register_page("craftguide:craftguide", {
|
||||
title = S"Guide",
|
||||
title = S"Craft Guide",
|
||||
|
||||
is_in_nav = function(self, player, context)
|
||||
local name = player:get_player_name()
|
||||
return get_fs_version(name) >= 2
|
||||
return get_fs_version(name) >= FORMSPEC_MINIMAL_VERSION
|
||||
end,
|
||||
|
||||
get = function(self, player, context)
|
||||
local name = player:get_player_name()
|
||||
local data = pdata[name]
|
||||
local formspec = make_fs(data)
|
||||
|
||||
return sfinv.make_formspec(player, context, formspec)
|
||||
return sfinv.make_formspec(player, context, make_fs(data))
|
||||
end,
|
||||
|
||||
on_enter = function(self, player, context)
|
||||
|
@ -1691,9 +1711,8 @@ else
|
|||
local name = user:get_player_name()
|
||||
local data = pdata[name]
|
||||
|
||||
if data.fs_version == 1 then
|
||||
return msg(name, "Your Minetest client is outdated. " ..
|
||||
"Get the latest version on minetest.net to use the Crafting Guide.")
|
||||
if data.fs_version < FORMSPEC_MINIMAL_VERSION then
|
||||
return outdated(name)
|
||||
end
|
||||
|
||||
if next(recipe_filters) then
|
||||
|
@ -1724,7 +1743,12 @@ else
|
|||
paramtype = "light",
|
||||
paramtype2 = "wallmounted",
|
||||
sunlight_propagates = true,
|
||||
groups = {oddly_breakable_by_hand = 1, flammable = 3},
|
||||
groups = {
|
||||
choppy = 1,
|
||||
attached_node = 1,
|
||||
oddly_breakable_by_hand = 1,
|
||||
flammable = 3,
|
||||
},
|
||||
node_box = {
|
||||
type = "wallmounted",
|
||||
wall_top = {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
|
||||
|
@ -1757,7 +1781,7 @@ else
|
|||
core.register_craft{
|
||||
output = "craftguide:sign",
|
||||
type = "shapeless",
|
||||
recipe = {"signs:sign"}
|
||||
recipe = {"default:sign_wall_wood"}
|
||||
}
|
||||
|
||||
core.register_craft{
|
||||
|
@ -1769,7 +1793,7 @@ else
|
|||
if rawget(_G, "sfinv_buttons") then
|
||||
sfinv_buttons.register_button("craftguide", {
|
||||
title = S"Crafting Guide",
|
||||
tooltip = S"Shows a list of available crafting recipes, cooking recipes and fuels",
|
||||
tooltip = S"Shows a list of available crafting recipes",
|
||||
image = PNG.book,
|
||||
action = function(player)
|
||||
on_use(player)
|
||||
|
@ -1779,7 +1803,6 @@ else
|
|||
end
|
||||
|
||||
if progressive_mode then
|
||||
local PLAYERS = {}
|
||||
local POLL_FREQ = 0.25
|
||||
local HUD_TIMER_MAX = 1.5
|
||||
|
||||
|
@ -1839,11 +1862,7 @@ if progressive_mode then
|
|||
return filtered
|
||||
end
|
||||
|
||||
local item_lists = {
|
||||
"main",
|
||||
"craft",
|
||||
"craftpreview",
|
||||
}
|
||||
local item_lists = {"main", "craft", "craftpreview"}
|
||||
|
||||
local function get_inv_items(player)
|
||||
local inv = player:get_inventory()
|
||||
|
@ -1870,6 +1889,34 @@ if progressive_mode then
|
|||
return inv_items
|
||||
end
|
||||
|
||||
local function init_hud(player, data)
|
||||
data.hud = {
|
||||
bg = player:hud_add{
|
||||
hud_elem_type = "image",
|
||||
position = {x = 0.78, y = 1},
|
||||
alignment = {x = 1, y = 1},
|
||||
scale = {x = 370, y = 112},
|
||||
text = PNG.bg,
|
||||
},
|
||||
|
||||
book = player:hud_add{
|
||||
hud_elem_type = "image",
|
||||
position = {x = 0.79, y = 1.02},
|
||||
alignment = {x = 1, y = 1},
|
||||
scale = {x = 4, y = 4},
|
||||
text = PNG.book,
|
||||
},
|
||||
|
||||
text = player:hud_add{
|
||||
hud_elem_type = "text",
|
||||
position = {x = 0.84, y = 1.04},
|
||||
alignment = {x = 1, y = 1},
|
||||
number = 0xffffff,
|
||||
text = "",
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
local function show_hud_success(player, data)
|
||||
-- It'd better to have an engine function `hud_move` to only need
|
||||
-- 2 calls for the notification's back and forth.
|
||||
|
@ -1917,8 +1964,9 @@ if progressive_mode then
|
|||
-- Workaround. Need an engine call to detect when the contents of
|
||||
-- the player inventory changed, instead.
|
||||
local function poll_new_items()
|
||||
for i = 1, #PLAYERS do
|
||||
local player = PLAYERS[i]
|
||||
local players = get_players()
|
||||
for i = 1, #players do
|
||||
local player = players[i]
|
||||
local name = player:get_player_name()
|
||||
local data = pdata[name]
|
||||
|
||||
|
@ -1951,12 +1999,13 @@ if progressive_mode then
|
|||
poll_new_items()
|
||||
|
||||
globalstep(function()
|
||||
for i = 1, #PLAYERS do
|
||||
local player = PLAYERS[i]
|
||||
local players = get_players()
|
||||
for i = 1, #players do
|
||||
local player = players[i]
|
||||
local name = player:get_player_name()
|
||||
local data = pdata[name]
|
||||
|
||||
if data.show_hud ~= nil then
|
||||
if data.show_hud ~= nil and singleplayer then
|
||||
show_hud_success(player, data)
|
||||
end
|
||||
end
|
||||
|
@ -1965,46 +2014,19 @@ if progressive_mode then
|
|||
craftguide.add_recipe_filter("Default progressive filter", progressive_filter)
|
||||
|
||||
on_joinplayer(function(player)
|
||||
PLAYERS = get_players()
|
||||
|
||||
local name = player:get_player_name()
|
||||
local data = pdata[name]
|
||||
|
||||
local meta = player:get_meta()
|
||||
data.inv_items = dslz(meta:get_string("inv_items")) or {}
|
||||
data.known_recipes = dslz(meta:get_string("known_recipes")) or 0
|
||||
data.inv_items = dslz(meta:get_string "inv_items") or {}
|
||||
data.known_recipes = dslz(meta:get_string "known_recipes") or 0
|
||||
|
||||
data.hud = {
|
||||
bg = player:hud_add{
|
||||
hud_elem_type = "image",
|
||||
position = {x = 0.78, y = 1},
|
||||
alignment = {x = 1, y = 1},
|
||||
scale = {x = 370, y = 112},
|
||||
text = PNG.bg,
|
||||
},
|
||||
|
||||
book = player:hud_add{
|
||||
hud_elem_type = "image",
|
||||
position = {x = 0.79, y = 1.02},
|
||||
alignment = {x = 1, y = 1},
|
||||
scale = {x = 4, y = 4},
|
||||
text = PNG.book,
|
||||
},
|
||||
|
||||
text = player:hud_add{
|
||||
hud_elem_type = "text",
|
||||
position = {x = 0.84, y = 1.04},
|
||||
alignment = {x = 1, y = 1},
|
||||
number = 0xffffff,
|
||||
text = "",
|
||||
},
|
||||
}
|
||||
if singleplayer then
|
||||
init_hud(player, data)
|
||||
end
|
||||
end)
|
||||
|
||||
local to_save = {
|
||||
"inv_items",
|
||||
"known_recipes",
|
||||
}
|
||||
local to_save = {"inv_items", "known_recipes"}
|
||||
|
||||
local function save_meta(player)
|
||||
local meta = player:get_meta()
|
||||
|
@ -2017,14 +2039,12 @@ if progressive_mode then
|
|||
end
|
||||
end
|
||||
|
||||
on_leaveplayer(function(player)
|
||||
PLAYERS = get_players()
|
||||
save_meta(player)
|
||||
end)
|
||||
on_leaveplayer(save_meta)
|
||||
|
||||
on_shutdown(function()
|
||||
for i = 1, #PLAYERS do
|
||||
local player = PLAYERS[i]
|
||||
local players = get_players()
|
||||
for i = 1, #players do
|
||||
local player = players[i]
|
||||
save_meta(player)
|
||||
end
|
||||
end)
|
||||
|
@ -2037,7 +2057,7 @@ end)
|
|||
|
||||
function craftguide.show(name, item, show_usages)
|
||||
if not true_str(name)then
|
||||
return err"craftguide.show(): player name missing"
|
||||
return err "craftguide.show(): player name missing"
|
||||
end
|
||||
|
||||
local data = pdata[name]
|
||||
|
@ -2052,13 +2072,11 @@ function craftguide.show(name, item, show_usages)
|
|||
if not recipes and not usages then
|
||||
if not recipes_cache[item] and not usages_cache[item] then
|
||||
return false, msg(name, fmt("%s: %s",
|
||||
S"No recipe or usage for this item",
|
||||
get_desc(item)))
|
||||
S"No recipe or usage for this item", get_desc(item)))
|
||||
end
|
||||
|
||||
return false, msg(name, fmt("%s: %s",
|
||||
S"You don't know a recipe or usage for this item",
|
||||
get_desc(item)))
|
||||
S"You don't know a recipe or usage for this item", get_desc(item)))
|
||||
end
|
||||
|
||||
data.query_item = item
|
||||
|
|
|
@ -3,17 +3,58 @@
|
|||
Craft Guide=Rezeptbuch
|
||||
Crafting Guide=Rezeptbuch
|
||||
Crafting Guide Sign=Rezepttafel
|
||||
Bookmarks=Lesezeichen
|
||||
Usage @1 of @2=Verwendung @1 von @2
|
||||
Recipe @1 of @2=Rezept @1 von @2
|
||||
No recipes=Keine Rezepte
|
||||
No usages=Keine Verwendungen
|
||||
Burning time: @1=Brennzeit: @1
|
||||
Cooking time: @1=Kochzeit: @1
|
||||
Replaced by @1 on smelting=Ersetzt durch @1 beim Schmelzen
|
||||
Replaced by @1 on burning=Ersetzt durch @1 beim Brennen
|
||||
Replaced by @1 on crafting=Ersetzt durch @1 beim Fertigen
|
||||
Repairable by step of @1=Reparierbar um @1
|
||||
Any item belonging to the group(s): @1=Beliebiger Gegenstand aus Gruppe(n): @1
|
||||
Any black dye=Beliebiger schwarzer Farbstoff
|
||||
Any black flower=Beliebige schwarze Blume
|
||||
Any blue dye=Beliebiger blauer Farbstoff
|
||||
Any blue flower=Beliebige blaue Blume
|
||||
Any brown dye=Beliebiger brauner Farbstoff
|
||||
Any coal=Beliebige Kohle
|
||||
Any cyan dye=Beliebiger türkiser Farbstoff
|
||||
Any dark green dye=Beliebiger dunkelgrüner Farbstoff
|
||||
Any dark grey dye=Beliebiger dunkelgrauer Farbstoff
|
||||
Any green dye=Beliebiger grüner Farbstoff
|
||||
Any green flower=Beliebige grüne Blume
|
||||
Any grey dye=Beliebiger grauer Farbstoff
|
||||
Any kind of stone block=Beliebiger Steinblock
|
||||
Any magenta dye=Beliebiger magenta Farbstoff
|
||||
Any orange dye=Beliebiger orange Farbstoff
|
||||
Any orange flower=Beliebige orange Blume
|
||||
Any pink dye=Beliebiger rosa Farbstoff
|
||||
Any red dye=Beliebiger roter Farbstoff
|
||||
Any red flower=Beliebige rote Blume
|
||||
Any sand=Beliebiger Sand
|
||||
Any stick=Beliebiger Stock
|
||||
Any tree=Beliebiger Baum
|
||||
Any vessel=Beliebiger Behälter
|
||||
Any violet dye=Beliebiger violetter Farbstoff
|
||||
Any violet flower=Beliebige violette Blume
|
||||
Any white dye=Beliebiger weißer Farbstoff
|
||||
Any white flower=Beliebige weiße Blume
|
||||
Any wood planks=Beliebige Holzplanken
|
||||
Any wool=Beliebige Wolle
|
||||
Any yellow dye=Beliebiger gelber Farbstoff
|
||||
Any yellow flower=Beliebige gelbe Blume
|
||||
Recipe's too big to be displayed (@1x@2)=Rezept ist zu groß für die Anzeige (@1×@2)
|
||||
Shapeless=Formlos
|
||||
Cooking=Kochen
|
||||
No item to show=Nichts anzuzeigen
|
||||
Collect items to reveal more recipes=Gegenstände aufsammeln, um mehr Rezepte aufzudecken
|
||||
Show recipe(s) of the pointed node=Rezept(e) des gezeigten Blocks anzeigen
|
||||
No node pointed=Auf keinen Block gezeigt
|
||||
You don't know a recipe for this node=Sie kennen kein Rezept für diesen Block
|
||||
No recipe for this node=Kein Rezept für diesen Block
|
||||
No node pointed=Auf keinem Block gezeigt
|
||||
You don't know a recipe or usage for this item=Sie kennen kein Rezept und keine Verwendung für diesen Gegenstand
|
||||
No recipe or usage for this item=Kein Rezept und keine Verwendung für diesen Gegenstand
|
||||
Digging=Graben
|
||||
Digging Chance=Grabechance
|
||||
@1 of chance to drop=@1 Abwurfwahrscheinlichkeit
|
||||
|
|
|
@ -2,8 +2,12 @@
|
|||
|
||||
Craft Guide=Guide de recettes
|
||||
Crafting Guide=Guide de recettes
|
||||
Crafting Guide Sign=Guide de recettes
|
||||
Bookmarks=Favoris
|
||||
Usage @1 of @2=Usage @1 de @2
|
||||
Recipe @1 of @2=Recette @1 de @2
|
||||
No recipes=Pas de recettes
|
||||
No usages=Pas d'usages
|
||||
Burning time: @1=Temps de combustion : @1
|
||||
Cooking time: @1=Temps de cuisson : @1
|
||||
Replaced by @1 on smelting=Remplacé par @1 lors de la cuisson
|
||||
|
@ -11,6 +15,37 @@ Replaced by @1 on burning=Remplacé par @1 lors de la combustion
|
|||
Replaced by @1 on crafting=Remplacé par @1 lors de la fabrication
|
||||
Repairable by step of @1=Réparable par étape de @1
|
||||
Any item belonging to the group(s): @1=Tout item appartenant au(x) groupe(s) : @1
|
||||
Any black dye=Quelconque colorant noir
|
||||
Any black flower=Quelconque fleur noire
|
||||
Any blue dye=Quelconque colorant bleu
|
||||
Any blue flower=Quelconque fleur bleue
|
||||
Any brown dye=Quelconque colorant marron
|
||||
Any coal=Quelconque charbon
|
||||
Any cyan dye=Quelconque colorant bleu ciel
|
||||
Any dark green dye=Quelconque colorant vert foncé
|
||||
Any dark grey dye=Quelconque colorant gris foncé
|
||||
Any green dye=Quelconque colorant vert
|
||||
Any green flower=Quelconque fleur verte
|
||||
Any grey dye=Quelconque colorant gris
|
||||
Any kind of stone block=Quelconque roche
|
||||
Any magenta dye=Quelconque colorant magenta
|
||||
Any orange dye=Quelconque colorant orange
|
||||
Any orange flower=Quelconque fleur orange
|
||||
Any pink dye=Quelconque colorant rose
|
||||
Any red dye=Quelconque colorant rouge
|
||||
Any red flower=Quelconque fleur rouge
|
||||
Any sand=Quelconque sable
|
||||
Any stick=Quelconque bâton
|
||||
Any tree=Quelconque tronc d'arbre
|
||||
Any vessel=Quelconque couvert
|
||||
Any violet dye=Quelconque colorant violet
|
||||
Any violet flower=Quelconque fleur violette
|
||||
Any white dye=Quelconque colorant blanc
|
||||
Any white flower=Quelconque fleur blanche
|
||||
Any wood planks=Quelconques planches de bois
|
||||
Any wool=Quelconque laine
|
||||
Any yellow dye=Quelconque colorant jaune
|
||||
Any yellow flower=Quelconque fleur jaune
|
||||
Recipe's too big to be displayed (@1x@2)=La recette est trop grande pour être affichée (@1x@2)
|
||||
Shapeless=Sans forme
|
||||
Cooking=Cuisson
|
||||
|
@ -18,5 +53,8 @@ No item to show=Aucun item à afficher
|
|||
Collect items to reveal more recipes=Collecte des items pour révéler plus de recettes
|
||||
Show recipe(s) of the pointed node=Affiche les recettes du bloc visé
|
||||
No node pointed=Aucun bloc visé
|
||||
You don't know a recipe for this node=Tu ne connais aucune recette pour ce bloc
|
||||
No recipe for this node=Aucune recette pour ce bloc
|
||||
You don't know a recipe or usage for this item=Tu ne connais aucune recette pour ce bloc
|
||||
No recipe or usage for this item=Aucune recette pour ce bloc
|
||||
Digging=Destruction
|
||||
Digging Chance=Destruction chanceuse
|
||||
@1 of chance to drop=@1 de chance de tomber
|
||||
|
|
|
@ -2,25 +2,59 @@
|
|||
|
||||
Craft Guide=Guida di assemblaggio
|
||||
Crafting Guide=Guida d'assemblaggio
|
||||
Crafting Guide Sign=Cartello della guida d'assemblaggio
|
||||
Crafting Guide Sign=Cartello della guida di assemblaggio
|
||||
Bookmarks=Segnalibri
|
||||
Usage @1 of @2=Utilizzo @1 di @2
|
||||
Recipe @1 of @2=Ricetta @1 di @2
|
||||
Burning time: @1=Tempo di bruciatura: @1
|
||||
No recipes=Nessuna ricetta
|
||||
No usages=Nessun utilizzo
|
||||
Burning time: @1=Tempo di combustione: @1
|
||||
Cooking time: @1=Tempo di cottura: @1
|
||||
Replaced by @1 on smelting=Sostituito da @1 alla fusione
|
||||
Replaced by @1 on burning=Sostituito da @1 alla bruciatura
|
||||
Replaced by @1 on burning=Sostituito da @1 alla combustione
|
||||
Replaced by @1 on crafting=Sostituito da @1 all'assemblaggio
|
||||
Repairable by step of @1=Riparabile per passo di @1
|
||||
Any item belonging to the group(s): @1=Qualunque oggetto appartenente al gruppo: @1
|
||||
Any item belonging to the group(s): @1=Qualunque oggetto appartenente al/ai gruppo/i: @1
|
||||
Any black dye=Qualunque tintura nera
|
||||
Any black flower=Qualunque fiore nero
|
||||
Any blue dye=Qualunque tintura blu
|
||||
Any blue flower=Qualunque fiore blu
|
||||
Any brown dye=Qualunque tintura marrone
|
||||
Any coal=Qualunque carbone
|
||||
Any cyan dye=Qualunque tintura ciano
|
||||
Any dark green dye=Qualunque tintura verde scura
|
||||
Any dark grey dye=Qualunque tintura grigio scura
|
||||
Any green dye=Qualunque tintura verde
|
||||
Any green flower=Qualunque fiore verde
|
||||
Any grey dye=Qualunque tintura grigia
|
||||
Any kind of stone block=Qualunque tipo di blocco di pietra
|
||||
Any magenta dye=Qualunque tintura magenta
|
||||
Any orange dye=Qualunque tintura arancione
|
||||
Any orange flower=Qualunque fiore arancione
|
||||
Any pink dye=Qualunque tintura rosa
|
||||
Any red dye=Qualunque tintura rossa
|
||||
Any red flower=Qualunque fiore rosso
|
||||
Any sand=Qualunque sabbia
|
||||
Any stick=Qualunque bastone
|
||||
Any tree=Qualunque albero
|
||||
Any vessel=Qualunque contenitore
|
||||
Any violet dye=Qualunque tintura viola
|
||||
Any violet flower=Qualunque fiore viola
|
||||
Any white dye=Qualunque tintura bianca
|
||||
Any white flower=Qualunque fiore bianco
|
||||
Any wood planks=Qualunque asse di legno
|
||||
Any wool=Qualunque lana
|
||||
Any yellow dye=Qualunque tintura gialla
|
||||
Any yellow flower=Qualunque fiore giallo
|
||||
Recipe's too big to be displayed (@1x@2)=La ricetta è troppo grande per essere mostrata (@1x@2)
|
||||
Shapeless=Senza forma
|
||||
Cooking=Cottura
|
||||
No item to show=Nessun oggetto da mostrare
|
||||
Collect items to reveal more recipes=Raccogli oggetti per svelare più ricette
|
||||
Show recipe(s) of the pointed node=Mostra la ricetta del nodo puntato
|
||||
Show recipe(s) of the pointed node=Mostra la/le ricetta/e del nodo puntato
|
||||
No node pointed=Nessun nodo puntato
|
||||
You don't know a recipe for this node=Non conosci una ricetta per questo nodo
|
||||
No recipe for this node=Nessuna ricetta per questo nodo
|
||||
You don't know a recipe or usage for this item=Non conosci una ricetta o un utilizzo per questo oggetto
|
||||
No recipe or usage for this item=Nessuna ricetta o utilizzo per questo oggetto
|
||||
Digging=Scavando
|
||||
Digging Chance=Probabilità di scavare
|
||||
@1 of chance to drop=@1 di probabilità di rilascio
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
Craft Guide=
|
||||
Crafting Guide=
|
||||
Crafting Guide Sign=
|
||||
Bookmarks=
|
||||
Usage @1 of @2=
|
||||
Recipe @1 of @2=
|
||||
No recipes=
|
||||
|
@ -14,6 +15,37 @@ Replaced by @1 on burning=
|
|||
Replaced by @1 on crafting=
|
||||
Repairable by step of @1=
|
||||
Any item belonging to the group(s): @1=
|
||||
Any black dye=
|
||||
Any black flower=
|
||||
Any blue dye=
|
||||
Any blue flower=
|
||||
Any brown dye=
|
||||
Any coal=
|
||||
Any cyan dye=
|
||||
Any dark green dye=
|
||||
Any dark grey dye=
|
||||
Any green dye=
|
||||
Any green flower=
|
||||
Any grey dye=
|
||||
Any kind of stone block=
|
||||
Any magenta dye=
|
||||
Any orange dye=
|
||||
Any orange flower=
|
||||
Any pink dye=
|
||||
Any red dye=
|
||||
Any red flower=
|
||||
Any sand=
|
||||
Any stick=
|
||||
Any tree=
|
||||
Any vessel=
|
||||
Any violet dye=
|
||||
Any violet flower=
|
||||
Any white dye=
|
||||
Any white flower=
|
||||
Any wood planks=
|
||||
Any wool=
|
||||
Any yellow dye=
|
||||
Any yellow flower=
|
||||
Recipe's too big to be displayed (@1x@2)=
|
||||
Shapeless=
|
||||
Cooking=
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
# The progressive mode shows recipes you can craft from items you ever had in your inventory.
|
||||
craftguide_progressive_mode (Progressive Mode) bool false
|
||||
craftguide_progressive_mode (Learn crafting recipes progressively) bool false
|
||||
|
||||
# Integration in the default Minetest Game inventory.
|
||||
craftguide_sfinv_only (Sfinv only) bool false
|
||||
craftguide_sfinv_only (Crafting Guide in inventory only) bool false
|
||||
|
||||
# Enable pre-caching of item recipes.
|
||||
# Do NOT disable the first time the mod loads.
|
||||
# Disabling the auto-caching will result in faster mod loading.
|
||||
# If you enable or disable mods, or edit the current mod recipes in your setup, you SHOULD re-enable this setting until the next caching at least.
|
||||
# Usage at your own risk.
|
||||
craftguide_autocache (Auto-caching of recipes) bool true
|
||||
|
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
@ -246,7 +246,7 @@ if minetest.get_modpath("bucket") then
|
|||
S("Dwarven Syrup Bucket")
|
||||
)
|
||||
|
||||
if minetest.get_modpath("crafting") then
|
||||
if minetest.get_modpath("simplecrafting_lib") then
|
||||
simplecrafting_lib.register("furnace", {
|
||||
input = {
|
||||
["bucket:bucket_empty"] = 1,
|
||||
|
|
|
@ -53,14 +53,6 @@ mobs:register_mob('mobs_creatures:astronaut', {
|
|||
on_rightclick = function(self, clicker)
|
||||
mobs_trader(self, clicker, entity, mobs.lunar_human)
|
||||
end,
|
||||
on_spawn = function(self)
|
||||
self.nametag = "Astronaut"
|
||||
self.object:set_properties({
|
||||
nametag = self.nametag,
|
||||
nametag_color = "#FFFFFF"
|
||||
})
|
||||
return true -- return true so on_spawn is run once only
|
||||
end,
|
||||
|
||||
})
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- TODO: Add drops.
|
||||
-- TODO: Add drops, mese-based trading npc.
|
||||
-- GREY CIVILIAN ALIEN
|
||||
mobs:register_mob("mobs_creatures:grey_civilian", {
|
||||
type = "animal",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
-- edited this version to not have nametags on the HUD.
|
||||
|
||||
local S = mobs.intllib
|
||||
|
||||
|
@ -103,14 +104,6 @@ mobs:register_mob("mobs_npc:trader", {
|
|||
on_rightclick = function(self, clicker)
|
||||
mobs_trader(self, clicker, entity, mobs.human)
|
||||
end,
|
||||
on_spawn = function(self)
|
||||
self.nametag = S("Trader")
|
||||
self.object:set_properties({
|
||||
nametag = self.nametag,
|
||||
nametag_color = "#FFFFFF"
|
||||
})
|
||||
return true -- return true so on_spawn is run once only
|
||||
end,
|
||||
})
|
||||
|
||||
--This code comes almost exclusively from the trader and inventory of mobf, by Sapier.
|
||||
|
@ -199,10 +192,6 @@ function mobs_trader(self, clicker, entity, race)
|
|||
self.game_name = tostring(race.names[math.random(1, #race.names)])
|
||||
self.nametag = S("Trader @1", self.game_name)
|
||||
|
||||
self.object:set_properties({
|
||||
nametag = self.nametag,
|
||||
nametag_color = "#00FF00"
|
||||
})
|
||||
end
|
||||
|
||||
if self.trades == nil then
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
# URGENT PRIORITY
|
||||
#### URGENT PRIORITY ####
|
||||
Exploit (Material duplication): Nether portal is exploitable for infinite portal-materials if a magic_mirror is used. Break exit portal, mirror home, then reinitiate portal entrance.
|
||||
Mobs are flashing on hit. Need to look into the API to see why this is.
|
||||
|
||||
# HIGHER PRIORITY
|
||||
#### HIGHER PRIORITY ####
|
||||
|
||||
# LOWER PRIORITY
|
||||
#### LOWER PRIORITY ####
|
||||
Add particles that go towards void chest.
|
||||
|
||||
### IDEAS ####
|
||||
Implement stargate-like portal with nether portal_api?
|
||||
|
|